diff options
417 files changed, 10891 insertions, 5914 deletions
diff --git a/api/current.txt b/api/current.txt index 4981c432efc8..f21d18faf9b2 100644 --- a/api/current.txt +++ b/api/current.txt @@ -751,6 +751,7 @@ package android { field public static final int isModifier = 16843334; // 0x1010246 field public static final int isRepeatable = 16843336; // 0x1010248 field public static final int isScrollContainer = 16843342; // 0x101024e + field public static final int isStatic = 16844125; // 0x101055d field public static final int isSticky = 16843335; // 0x1010247 field public static final int isolatedProcess = 16843689; // 0x10103a9 field public static final int isolatedSplits = 16844109; // 0x101054d @@ -1789,6 +1790,7 @@ package android { field public static final int accessibilityActionSetProgress = 16908349; // 0x102003d field public static final int accessibilityActionShowOnScreen = 16908342; // 0x1020036 field public static final int addToDictionary = 16908330; // 0x102002a + field public static final int autofill = 16908355; // 0x1020043 field public static final int background = 16908288; // 0x1020000 field public static final int button1 = 16908313; // 0x1020019 field public static final int button2 = 16908314; // 0x102001a @@ -5492,7 +5494,6 @@ package android.app { public final class NotificationChannel implements android.os.Parcelable { ctor public NotificationChannel(java.lang.String, java.lang.CharSequence, int); - ctor public NotificationChannel(java.lang.String, int, int); ctor protected NotificationChannel(android.os.Parcel); method public boolean canBypassDnd(); method public boolean canShowBadge(); @@ -5506,7 +5507,6 @@ package android.app { method public int getLightColor(); method public int getLockscreenVisibility(); method public java.lang.CharSequence getName(); - method public int getNameResId(); method public android.net.Uri getSound(); method public long[] getVibrationPattern(); method public void setBypassDnd(boolean); @@ -5514,6 +5514,7 @@ package android.app { method public void setImportance(int); method public void setLightColor(int); method public void setLockscreenVisibility(int); + method public void setName(java.lang.CharSequence); method public void setShowBadge(boolean); method public void setSound(android.net.Uri, android.media.AudioAttributes); method public void setVibrationPattern(long[]); @@ -5526,14 +5527,12 @@ package android.app { public final class NotificationChannelGroup implements android.os.Parcelable { ctor public NotificationChannelGroup(java.lang.String, java.lang.CharSequence); - ctor public NotificationChannelGroup(java.lang.String, int); ctor protected NotificationChannelGroup(android.os.Parcel); method public android.app.NotificationChannelGroup clone(); method public int describeContents(); method public java.util.List<android.app.NotificationChannel> getChannels(); method public java.lang.String getId(); method public java.lang.CharSequence getName(); - method public int getNameResId(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.app.NotificationChannelGroup> CREATOR; } @@ -5549,12 +5548,14 @@ package android.app { method public void createNotificationChannelGroups(java.util.List<android.app.NotificationChannelGroup>); method public void createNotificationChannels(java.util.List<android.app.NotificationChannel>); method public void deleteNotificationChannel(java.lang.String); + method public void deleteNotificationChannelGroup(java.lang.String); method public android.service.notification.StatusBarNotification[] getActiveNotifications(); method public android.app.AutomaticZenRule getAutomaticZenRule(java.lang.String); method public java.util.Map<java.lang.String, android.app.AutomaticZenRule> getAutomaticZenRules(); method public final int getCurrentInterruptionFilter(); method public int getImportance(); method public android.app.NotificationChannel getNotificationChannel(java.lang.String); + method public java.util.List<android.app.NotificationChannelGroup> getNotificationChannelGroups(); method public java.util.List<android.app.NotificationChannel> getNotificationChannels(); method public android.app.NotificationManager.Policy getNotificationPolicy(); method public boolean isNotificationPolicyAccessGranted(); @@ -6566,7 +6567,7 @@ package android.app.assist { public static class AssistStructure.ViewNode { method public float getAlpha(); - method public int getAutoFillHint(); + method public java.lang.String[] getAutoFillHint(); method public android.view.autofill.AutofillId getAutofillId(); method public java.lang.String[] getAutofillOptions(); method public int getAutofillType(); @@ -7121,6 +7122,7 @@ package android.bluetooth { method public android.bluetooth.le.BluetoothLeScanner getBluetoothLeScanner(); method public java.util.Set<android.bluetooth.BluetoothDevice> getBondedDevices(); method public static synchronized android.bluetooth.BluetoothAdapter getDefaultAdapter(); + method public int getLeMaximumAdvertisingDataLength(); method public java.lang.String getName(); method public android.bluetooth.le.PeriodicAdvertisingManager getPeriodicAdvertisingManager(); method public int getProfileConnectionState(int); @@ -10671,6 +10673,7 @@ package android.content.pm { field public static final java.lang.String FEATURE_USB_ACCESSORY = "android.hardware.usb.accessory"; field public static final java.lang.String FEATURE_USB_HOST = "android.hardware.usb.host"; field public static final java.lang.String FEATURE_VERIFIED_BOOT = "android.software.verified_boot"; + field public static final java.lang.String FEATURE_VR_HEADTRACKING = "android.hardware.vr.headtracking"; field public static final java.lang.String FEATURE_VR_MODE = "android.software.vr.mode"; field public static final java.lang.String FEATURE_VR_MODE_HIGH_PERFORMANCE = "android.hardware.vr.high_performance"; field public static final java.lang.String FEATURE_VULKAN_HARDWARE_COMPUTE = "android.hardware.vulkan.compute"; @@ -12756,7 +12759,7 @@ package android.graphics { } public class ColorFilter { - ctor public ColorFilter(); + ctor public deprecated ColorFilter(); } public class ColorMatrix { @@ -12780,6 +12783,9 @@ package android.graphics { public class ColorMatrixColorFilter extends android.graphics.ColorFilter { ctor public ColorMatrixColorFilter(android.graphics.ColorMatrix); ctor public ColorMatrixColorFilter(float[]); + method public void getColorMatrix(android.graphics.ColorMatrix); + method public void setColorMatrix(android.graphics.ColorMatrix); + method public void setColorMatrixArray(float[]); } public abstract class ColorSpace { @@ -13003,6 +13009,10 @@ package android.graphics { public class LightingColorFilter extends android.graphics.ColorFilter { ctor public LightingColorFilter(int, int); + method public int getColorAdd(); + method public int getColorMultiply(); + method public void setColorAdd(int); + method public void setColorMultiply(int); } public class LinearGradient extends android.graphics.Shader { @@ -13519,6 +13529,10 @@ package android.graphics { public class PorterDuffColorFilter extends android.graphics.ColorFilter { ctor public PorterDuffColorFilter(int, android.graphics.PorterDuff.Mode); + method public int getColor(); + method public android.graphics.PorterDuff.Mode getMode(); + method public void setColor(int); + method public void setMode(android.graphics.PorterDuff.Mode); } public class PorterDuffXfermode extends android.graphics.Xfermode { @@ -21826,7 +21840,7 @@ package android.media { method public deprecated java.nio.ByteBuffer[] getInputBuffers(); method public final android.media.MediaFormat getInputFormat(); method public android.media.Image getInputImage(int); - method public android.os.Bundle getMetrics(); + method public android.media.MediaMetricsSet getMetrics(); method public final java.lang.String getName(); method public java.nio.ByteBuffer getOutputBuffer(int); method public deprecated java.nio.ByteBuffer[] getOutputBuffers(); @@ -22378,7 +22392,7 @@ package android.media { method public boolean advance(); method public long getCachedDuration(); method public android.media.DrmInitData getDrmInitData(); - method public android.os.Bundle getMetrics(); + method public android.media.MediaMetricsSet getMetrics(); method public java.util.Map<java.util.UUID, byte[]> getPsshInfo(); method public boolean getSampleCryptoInfo(android.media.MediaCodec.CryptoInfo); method public int getSampleFlags(); @@ -22633,6 +22647,69 @@ package android.media { field public static final int OPTION_PREVIOUS_SYNC = 0; // 0x0 } + public final class MediaMetricsSet { + method public double getDouble(java.lang.String, double); + method public int getInt(java.lang.String, int); + method public long getLong(java.lang.String, long); + method public java.lang.String getString(java.lang.String, java.lang.String); + method public boolean isEmpty(); + method public java.util.Set<java.lang.String> keySet(); + method public int size(); + } + + public static final class MediaMetricsSet.MediaCodec { + field public static final java.lang.String KEY_CODEC = "android.media.mediacodec.codec"; + field public static final java.lang.String KEY_ENCODER = "android.media.mediacodec.encoder"; + field public static final java.lang.String KEY_HEIGHT = "android.media.mediacodec.height"; + field public static final java.lang.String KEY_MIME = "android.media.mediacodec.mime"; + field public static final java.lang.String KEY_MODE = "android.media.mediacodec.mode"; + field public static final java.lang.String KEY_ROTATION = "android.media.mediacodec.rotation"; + field public static final java.lang.String KEY_SECURE = "android.media.mediacodec.secure"; + field public static final java.lang.String KEY_WIDTH = "android.media.mediacodec.width"; + field public static final java.lang.String MODE_AUDIO = "audio"; + field public static final java.lang.String MODE_VIDEO = "video"; + } + + public static final class MediaMetricsSet.MediaExtractor { + field public static final java.lang.String KEY_FORMAT = "android.media.mediaextractor.fmt"; + field public static final java.lang.String KEY_MIME = "android.media.mediaextractor.mime"; + field public static final java.lang.String KEY_TRACKS = "android.media.mediaextractor.ntrk"; + } + + public static final class MediaMetricsSet.MediaPlayer { + field public static final java.lang.String KEY_CODEC_AUDIO = "android.media.mediaplayer.audio.codec"; + field public static final java.lang.String KEY_CODEC_VIDEO = "android.media.mediaplayer.video.codec"; + field public static final java.lang.String KEY_DURATION = "android.media.mediaplayer.durationMs"; + field public static final java.lang.String KEY_ERRORS = "android.media.mediaplayer.err"; + field public static final java.lang.String KEY_ERROR_CODE = "android.media.mediaplayer.errcode"; + field public static final java.lang.String KEY_FRAMES = "android.media.mediaplayer.frames"; + field public static final java.lang.String KEY_FRAMES_DROPPED = "android.media.mediaplayer.dropped"; + field public static final java.lang.String KEY_HEIGHT = "android.media.mediaplayer.height"; + field public static final java.lang.String KEY_MIME_AUDIO = "android.media.mediaplayer.audio.mime"; + field public static final java.lang.String KEY_MIME_VIDEO = "android.media.mediaplayer.video.mime"; + field public static final java.lang.String KEY_PLAYING = "android.media.mediaplayer.playingMs"; + field public static final java.lang.String KEY_WIDTH = "android.media.mediaplayer.width"; + } + + public static final class MediaMetricsSet.MediaRecorder { + field public static final java.lang.String KEY_AUDIO_BITRATE = "android.media.mediarecorder.audio-bitrate"; + field public static final java.lang.String KEY_AUDIO_CHANNELS = "android.media.mediarecorder.audio-channels"; + field public static final java.lang.String KEY_AUDIO_SAMPLERATE = "android.media.mediarecorder.audio-samplerate"; + field public static final java.lang.String KEY_AUDIO_TIMESCALE = "android.media.mediarecorder.audio-timescale"; + field public static final java.lang.String KEY_CAPTURE_FPS = "android.media.mediarecorder.capture-fps"; + field public static final java.lang.String KEY_CAPTURE_FPS_ENABLE = "android.media.mediarecorder.capture-fpsenable"; + field public static final java.lang.String KEY_FRAMERATE = "android.media.mediarecorder.frame-rate"; + field public static final java.lang.String KEY_HEIGHT = "android.media.mediarecorder.height"; + field public static final java.lang.String KEY_MOVIE_TIMESCALE = "android.media.mediarecorder.movie-timescale"; + field public static final java.lang.String KEY_ROTATION = "android.media.mediarecorder.rotation"; + field public static final java.lang.String KEY_VIDEO_BITRATE = "android.media.mediarecorder.video-bitrate"; + field public static final java.lang.String KEY_VIDEO_IFRAME_INTERVAL = "android.media.mediarecorder.video-iframe-interval"; + field public static final java.lang.String KEY_VIDEO_LEVEL = "android.media.mediarecorder.video-encoder-level"; + field public static final java.lang.String KEY_VIDEO_PROFILE = "android.media.mediarecorder.video-encoder-profile"; + field public static final java.lang.String KEY_VIDEO_TIMESCALE = "android.media.mediarecorder.video-timescale"; + field public static final java.lang.String KEY_WIDTH = "android.media.mediarecorder.width"; + } + 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; @@ -22673,7 +22750,7 @@ package android.media { method public java.lang.String getDrmPropertyString(java.lang.String) throws android.media.MediaPlayer.NoDrmSchemeException; method public int getDuration(); method public android.media.MediaDrm.KeyRequest getKeyRequest(byte[], java.lang.String, int, java.util.Map<java.lang.String, java.lang.String>) throws android.media.MediaPlayer.NoDrmSchemeException; - method public android.os.Bundle getMetrics(); + method public android.media.MediaMetricsSet getMetrics(); method public android.media.PlaybackParams getPlaybackParams(); method public int getSelectedTrack(int) throws java.lang.IllegalStateException; method public android.media.SyncParams getSyncParams(); @@ -22841,7 +22918,7 @@ package android.media { ctor public MediaRecorder(); method public static final int getAudioSourceMax(); method public int getMaxAmplitude() throws java.lang.IllegalStateException; - method public android.os.Bundle getMetrics(); + method public android.media.MediaMetricsSet getMetrics(); method public android.view.Surface getSurface(); method public void pause() throws java.lang.IllegalStateException; method public void prepare() throws java.io.IOException, java.lang.IllegalStateException; @@ -24652,7 +24729,7 @@ package android.media.tv { public final class TvInputInfo implements android.os.Parcelable { method public boolean canRecord(); - method public android.content.Intent createSettingsIntent(); + method public deprecated android.content.Intent createSettingsIntent(); method public android.content.Intent createSetupIntent(); method public int describeContents(); method public android.os.Bundle getExtras(); @@ -25193,8 +25270,8 @@ package android.net { method public void reportNetworkConnectivity(android.net.Network, boolean); method public boolean requestBandwidthUpdate(android.net.Network); method public void requestNetwork(android.net.NetworkRequest, android.net.ConnectivityManager.NetworkCallback); - method public void requestNetwork(android.net.NetworkRequest, int, android.net.ConnectivityManager.NetworkCallback); method public void requestNetwork(android.net.NetworkRequest, android.net.ConnectivityManager.NetworkCallback, android.os.Handler); + method public void requestNetwork(android.net.NetworkRequest, int, android.net.ConnectivityManager.NetworkCallback); method public void requestNetwork(android.net.NetworkRequest, int, android.net.ConnectivityManager.NetworkCallback, android.os.Handler); method public void requestNetwork(android.net.NetworkRequest, android.app.PendingIntent); method public deprecated void setNetworkPreference(int); @@ -33877,6 +33954,7 @@ package android.provider { field public static final java.lang.String EXTRA_RECIPIENT_CONTACT_CHAT_ID = "android.provider.extra.RECIPIENT_CONTACT_CHAT_ID"; field public static final java.lang.String EXTRA_RECIPIENT_CONTACT_NAME = "android.provider.extra.RECIPIENT_CONTACT_NAME"; field public static final java.lang.String EXTRA_RECIPIENT_CONTACT_URI = "android.provider.extra.RECIPIENT_CONTACT_URI"; + field public static final java.lang.String EXTRA_SENDER_ACCOUNT_HASH = "android.provider.extra.SENDER_ACCOUNT_HASH"; field public static final java.lang.String INVITE_CONTACT = "com.android.contacts.action.INVITE_CONTACT"; field public static final java.lang.String METADATA_ACCOUNT_TYPE = "android.provider.account_type"; field public static final java.lang.String METADATA_MIMETYPE = "android.provider.mimetype"; @@ -36741,7 +36819,7 @@ package android.service.autofill { method public final android.os.IBinder onBind(android.content.Intent); method public void onConnected(); method public void onDisconnected(); - method public abstract void onFillRequest(android.app.assist.AssistStructure, android.os.Bundle, android.os.CancellationSignal, android.service.autofill.FillCallback); + method public void onFillRequest(android.app.assist.AssistStructure, android.os.Bundle, int, android.os.CancellationSignal, android.service.autofill.FillCallback); method public abstract void onSaveRequest(android.app.assist.AssistStructure, android.os.Bundle, android.service.autofill.SaveCallback); field public static final java.lang.String SERVICE_INTERFACE = "android.service.autofill.AutofillService"; field public static final java.lang.String SERVICE_META_DATA = "android.autofill"; @@ -36796,11 +36874,11 @@ package android.service.autofill { } public static final class SaveInfo.Builder { - ctor public SaveInfo.Builder(int); - method public android.service.autofill.SaveInfo.Builder addSavableIds(android.view.autofill.AutofillId...); + ctor public SaveInfo.Builder(int, android.view.autofill.AutofillId[]); method public android.service.autofill.SaveInfo build(); method public android.service.autofill.SaveInfo.Builder setDescription(java.lang.CharSequence); method public android.service.autofill.SaveInfo.Builder setNegativeAction(java.lang.CharSequence, android.content.IntentSender); + method public android.service.autofill.SaveInfo.Builder setOptionalIds(android.view.autofill.AutofillId[]); } } @@ -37092,11 +37170,11 @@ package android.service.notification { field public static final int INTERRUPTION_FILTER_UNKNOWN = 0; // 0x0 field public static final int REASON_APP_CANCEL = 8; // 0x8 field public static final int REASON_APP_CANCEL_ALL = 9; // 0x9 + field public static final int REASON_CANCEL = 2; // 0x2 + field public static final int REASON_CANCEL_ALL = 3; // 0x3 field public static final int REASON_CHANNEL_BANNED = 17; // 0x11 - field public static final int REASON_DELEGATE_CANCEL = 2; // 0x2 - field public static final int REASON_DELEGATE_CANCEL_ALL = 3; // 0x3 - field public static final int REASON_DELEGATE_CLICK = 1; // 0x1 - field public static final int REASON_DELEGATE_ERROR = 4; // 0x4 + field public static final int REASON_CLICK = 1; // 0x1 + field public static final int REASON_ERROR = 4; // 0x4 field public static final int REASON_GROUP_OPTIMIZATION = 13; // 0xd field public static final int REASON_GROUP_SUMMARY_CANCELED = 12; // 0xc field public static final int REASON_LISTENER_CANCEL = 10; // 0xa @@ -39178,6 +39256,7 @@ package android.telephony { method public android.os.PersistableBundle getConfigForSubId(int); method public void notifyConfigChangedForSubId(int); field public static final java.lang.String ACTION_CARRIER_CONFIG_CHANGED = "android.telephony.action.CARRIER_CONFIG_CHANGED"; + field public static final int DATA_CYCLE_THRESHOLD_DISABLED = -2; // 0xfffffffe field public static final java.lang.String KEY_ADDITIONAL_CALL_SETTING_BOOL = "additional_call_setting_bool"; field public static final java.lang.String KEY_ALLOW_ADDING_APNS_BOOL = "allow_adding_apns_bool"; field public static final java.lang.String KEY_ALLOW_ADD_CALL_DURING_VIDEO_CALL_BOOL = "allow_add_call_during_video_call"; @@ -39220,6 +39299,8 @@ package android.telephony { field public static final java.lang.String KEY_CI_ACTION_ON_SYS_UPDATE_INTENT_STRING = "ci_action_on_sys_update_intent_string"; field public static final java.lang.String KEY_CONFIG_IMS_PACKAGE_OVERRIDE_STRING = "config_ims_package_override_string"; field public static final java.lang.String KEY_CSP_ENABLED_BOOL = "csp_enabled_bool"; + field public static final java.lang.String KEY_DATA_LIMIT_THRESHOLD_BYTES_LONG = "data_limit_threshold_bytes_long"; + field public static final java.lang.String KEY_DATA_WARNING_THRESHOLD_BYTES_LONG = "data_warning_threshold_bytes_long"; field public static final java.lang.String KEY_DEFAULT_SIM_CALL_MANAGER_STRING = "default_sim_call_manager_string"; field public static final java.lang.String KEY_DEFAULT_VM_NUMBER_STRING = "default_vm_number_string"; field public static final java.lang.String KEY_DIAL_STRING_REPLACE_STRING_ARRAY = "dial_string_replace_string_array"; @@ -39275,6 +39356,7 @@ package android.telephony { field public static final java.lang.String KEY_MMS_UA_PROF_TAG_NAME_STRING = "uaProfTagName"; field public static final java.lang.String KEY_MMS_UA_PROF_URL_STRING = "uaProfUrl"; field public static final java.lang.String KEY_MMS_USER_AGENT_STRING = "userAgent"; + field public static final java.lang.String KEY_MONTHLY_DATA_CYCLE_DAY_INT = "monthly_data_cycle_day_int"; field public static final java.lang.String KEY_ONLY_SINGLE_DC_ALLOWED_INT_ARRAY = "only_single_dc_allowed_int_array"; field public static final java.lang.String KEY_OPERATOR_SELECTION_EXPAND_BOOL = "operator_selection_expand_bool"; field public static final java.lang.String KEY_PREFER_2G_BOOL = "prefer_2g_bool"; @@ -39807,7 +39889,6 @@ package android.telephony { method public android.os.PersistableBundle getCarrierConfig(); method public android.telephony.CellLocation getCellLocation(); method public int getDataActivity(); - method public boolean getDataEnabled(); method public int getDataNetworkType(); method public int getDataState(); method public java.lang.String getDeviceId(); @@ -39848,6 +39929,7 @@ package android.telephony { method public java.lang.String iccTransmitApduBasicChannel(int, int, int, int, int, java.lang.String); method public java.lang.String iccTransmitApduLogicalChannel(int, int, int, int, int, int, java.lang.String); method public boolean isConcurrentVoiceAndDataAllowed(); + method public boolean isDataEnabled(); method public boolean isHearingAidCompatibilitySupported(); method public boolean isNetworkRoaming(); method public boolean isSmsCapable(); @@ -40885,11 +40967,10 @@ package android.text { } public final class FontConfig implements android.os.Parcelable { - ctor public FontConfig(); - ctor public FontConfig(android.text.FontConfig); + ctor public FontConfig(android.text.FontConfig.Family[], android.text.FontConfig.Alias[]); method public int describeContents(); - method public java.util.List<android.text.FontConfig.Alias> getAliases(); - method public java.util.List<android.text.FontConfig.Family> getFamilies(); + method public android.text.FontConfig.Alias[] getAliases(); + method public android.text.FontConfig.Family[] getFamilies(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.text.FontConfig> CREATOR; } @@ -40914,22 +40995,22 @@ package android.text { } public static final class FontConfig.Family implements android.os.Parcelable { - ctor public FontConfig.Family(java.lang.String, java.util.List<android.text.FontConfig.Font>, java.lang.String, java.lang.String); - ctor public FontConfig.Family(android.text.FontConfig.Family); + ctor public FontConfig.Family(java.lang.String, android.text.FontConfig.Font[], java.lang.String, int); method public int describeContents(); - method public java.util.List<android.text.FontConfig.Font> getFonts(); + method public android.text.FontConfig.Font[] getFonts(); method public java.lang.String getLanguage(); method public java.lang.String getName(); - method public java.lang.String getVariant(); + method public int getVariant(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.text.FontConfig.Family> CREATOR; + field public static final int VARIANT_COMPACT = 1; // 0x1 + field public static final int VARIANT_DEFAULT = 0; // 0x0 + field public static final int VARIANT_ELEGANT = 2; // 0x2 } public static final class FontConfig.Font implements android.os.Parcelable { - ctor public FontConfig.Font(java.lang.String, int, java.util.List<android.text.FontConfig.Axis>, int, boolean); - ctor public FontConfig.Font(android.text.FontConfig.Font); method public int describeContents(); - method public java.util.List<android.text.FontConfig.Axis> getAxes(); + method public android.text.FontConfig.Axis[] getAxes(); method public android.os.ParcelFileDescriptor getFd(); method public java.lang.String getFontName(); method public int getTtcIndex(); @@ -45007,8 +45088,8 @@ package android.view { method public void addTouchables(java.util.ArrayList<android.view.View>); method public android.view.ViewPropertyAnimator animate(); method public void announceForAccessibility(java.lang.CharSequence); - method public void autofill(android.view.autofill.AutofillValue); - method public void autofillVirtual(int, android.view.autofill.AutofillValue); + method public boolean autofill(android.view.autofill.AutofillValue); + method public boolean autofillVirtual(int, android.view.autofill.AutofillValue); method protected boolean awakenScrollBars(); method protected boolean awakenScrollBars(int); method protected boolean awakenScrollBars(int, boolean); @@ -45098,7 +45179,7 @@ package android.view { method public float getAlpha(); method public android.view.animation.Animation getAnimation(); method public android.os.IBinder getApplicationWindowToken(); - method public int getAutofillHint(); + method public java.lang.String[] getAutofillHint(); method public int getAutofillMode(); method public int getAutofillType(); method public android.view.autofill.AutofillValue getAutofillValue(); @@ -45419,7 +45500,7 @@ package android.view { method public void setActivated(boolean); method public void setAlpha(float); method public void setAnimation(android.view.animation.Animation); - method public void setAutofillHint(int); + method public void setAutofillHint(java.lang.String...); method public void setAutofillMode(int); method public void setBackground(android.graphics.drawable.Drawable); method public void setBackgroundColor(int); @@ -45562,20 +45643,19 @@ package android.view { field public static final int ACCESSIBILITY_LIVE_REGION_NONE = 0; // 0x0 field public static final int ACCESSIBILITY_LIVE_REGION_POLITE = 1; // 0x1 field public static final android.util.Property<android.view.View, java.lang.Float> ALPHA; - field public static final int AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_DATE = 512; // 0x200 - field public static final int AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_DAY = 4096; // 0x1000 - field public static final int AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_MONTH = 1024; // 0x400 - field public static final int AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_YEAR = 2048; // 0x800 - field public static final int AUTOFILL_HINT_CREDIT_CARD_NUMBER = 128; // 0x80 - field public static final int AUTOFILL_HINT_CREDIT_CARD_SECURITY_CODE = 256; // 0x100 - field public static final int AUTOFILL_HINT_EMAIL_ADDRESS = 1; // 0x1 - field public static final int AUTOFILL_HINT_NAME = 2; // 0x2 - field public static final int AUTOFILL_HINT_NONE = 0; // 0x0 - field public static final int AUTOFILL_HINT_PASSWORD = 8; // 0x8 - field public static final int AUTOFILL_HINT_PHONE = 16; // 0x10 - field public static final int AUTOFILL_HINT_POSTAL_ADDRESS = 32; // 0x20 - field public static final int AUTOFILL_HINT_POSTAL_CODE = 64; // 0x40 - field public static final int AUTOFILL_HINT_USERNAME = 4; // 0x4 + field public static final java.lang.String AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_DATE = "creditCardExpirationDate"; + field public static final java.lang.String AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_DAY = "creditCardExpirationDay"; + field public static final java.lang.String AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_MONTH = "creditCardExpirationMonth"; + field public static final java.lang.String AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_YEAR = "creditCardExpirationYear"; + field public static final java.lang.String AUTOFILL_HINT_CREDIT_CARD_NUMBER = "creditCardNumber"; + field public static final java.lang.String AUTOFILL_HINT_CREDIT_CARD_SECURITY_CODE = "creditCardSecurityCode"; + field public static final java.lang.String AUTOFILL_HINT_EMAIL_ADDRESS = "emailAddress"; + field public static final java.lang.String AUTOFILL_HINT_NAME = "name"; + field public static final java.lang.String AUTOFILL_HINT_PASSWORD = "password"; + field public static final java.lang.String AUTOFILL_HINT_PHONE = "phone"; + field public static final java.lang.String AUTOFILL_HINT_POSTAL_ADDRESS = "postalAddress"; + field public static final java.lang.String AUTOFILL_HINT_POSTAL_CODE = "postalCode"; + field public static final java.lang.String AUTOFILL_HINT_USERNAME = "username"; field public static final int AUTOFILL_MODE_AUTO = 1; // 0x1 field public static final int AUTOFILL_MODE_INHERIT = 0; // 0x0 field public static final int AUTOFILL_MODE_MANUAL = 2; // 0x2 @@ -46237,7 +46317,7 @@ package android.view { method public abstract void setAccessibilityFocused(boolean); method public abstract void setActivated(boolean); method public abstract void setAlpha(float); - method public abstract void setAutofillHint(int); + method public abstract void setAutofillHint(java.lang.String[]); method public abstract void setAutofillOptions(java.lang.String[]); method public abstract void setAutofillType(int); method public abstract void setAutofillValue(android.view.autofill.AutofillValue); @@ -46248,6 +46328,7 @@ package android.view { method public abstract void setClickable(boolean); method public abstract void setContentDescription(java.lang.CharSequence); method public abstract void setContextClickable(boolean); + method public abstract void setDataIsSensitive(boolean); method public abstract void setDimens(int, int, int, int, int, int); method public abstract void setElevation(float); method public abstract void setEnabled(boolean); @@ -46258,7 +46339,6 @@ package android.view { method public abstract void setInputType(int); method public abstract void setLongClickable(boolean); method public abstract void setOpaque(boolean); - method public abstract void setSanitized(boolean); method public abstract void setSelected(boolean); method public abstract void setText(java.lang.CharSequence); method public abstract void setText(java.lang.CharSequence, int, int); @@ -47503,6 +47583,7 @@ package android.view.autofill { public final class AutofillManager { method public void cancel(); method public void commit(); + method public boolean isEnabled(); method public void notifyValueChanged(android.view.View); method public void notifyViewEntered(android.view.View); method public void notifyViewExited(android.view.View); @@ -47510,9 +47591,12 @@ package android.view.autofill { method public void notifyVirtualViewEntered(android.view.View, int, android.graphics.Rect); method public void notifyVirtualViewExited(android.view.View, int); method public void registerCallback(android.view.autofill.AutofillManager.AutofillCallback); + method public void requestAutofill(android.view.View); + method public void requestAutofill(android.view.View, int, android.graphics.Rect); method public void unregisterCallback(android.view.autofill.AutofillManager.AutofillCallback); field public static final java.lang.String EXTRA_ASSIST_STRUCTURE = "android.view.autofill.extra.ASSIST_STRUCTURE"; field public static final java.lang.String EXTRA_AUTHENTICATION_RESULT = "android.view.autofill.extra.AUTHENTICATION_RESULT"; + field public static final int FLAG_MANUAL_REQUEST = 1; // 0x1 } public static abstract class AutofillManager.AutofillCallback { @@ -47521,6 +47605,7 @@ package android.view.autofill { method public void onAutofillEventVirtual(android.view.View, int, int); field public static final int EVENT_INPUT_HIDDEN = 2; // 0x2 field public static final int EVENT_INPUT_SHOWN = 1; // 0x1 + field public static final int EVENT_INPUT_UNAVAILABLE = 3; // 0x3 } public final class AutofillValue implements android.os.Parcelable { @@ -47533,6 +47618,10 @@ package android.view.autofill { method public int getListValue(); method public java.lang.CharSequence getTextValue(); method public boolean getToggleValue(); + method public boolean isDate(); + method public boolean isList(); + method public boolean isText(); + method public boolean isToggle(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.view.autofill.AutofillValue> CREATOR; } diff --git a/api/system-current.txt b/api/system-current.txt index fbfee83e67cd..26d43e389128 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -864,6 +864,7 @@ package android { field public static final int isModifier = 16843334; // 0x1010246 field public static final int isRepeatable = 16843336; // 0x1010248 field public static final int isScrollContainer = 16843342; // 0x101024e + field public static final int isStatic = 16844125; // 0x101055d field public static final int isSticky = 16843335; // 0x1010247 field public static final int isolatedProcess = 16843689; // 0x10103a9 field public static final int isolatedSplits = 16844109; // 0x101054d @@ -1906,6 +1907,7 @@ package android { field public static final int accessibilityActionSetProgress = 16908349; // 0x102003d field public static final int accessibilityActionShowOnScreen = 16908342; // 0x1020036 field public static final int addToDictionary = 16908330; // 0x102002a + field public static final int autofill = 16908355; // 0x1020043 field public static final int background = 16908288; // 0x1020000 field public static final int button1 = 16908313; // 0x1020019 field public static final int button2 = 16908314; // 0x102001a @@ -5679,7 +5681,6 @@ package android.app { public final class NotificationChannel implements android.os.Parcelable { ctor public NotificationChannel(java.lang.String, java.lang.CharSequence, int); - ctor public NotificationChannel(java.lang.String, int, int); ctor protected NotificationChannel(android.os.Parcel); method public boolean canBypassDnd(); method public boolean canShowBadge(); @@ -5693,19 +5694,17 @@ package android.app { method public int getLightColor(); method public int getLockscreenVisibility(); method public java.lang.CharSequence getName(); - method public int getNameResId(); method public android.net.Uri getSound(); method public int getUserLockedFields(); method public long[] getVibrationPattern(); method public boolean isDeleted(); - method public void lockFields(int); method public void populateFromXml(org.xmlpull.v1.XmlPullParser); method public void setBypassDnd(boolean); - method public void setDeleted(boolean); method public void setGroup(java.lang.String); method public void setImportance(int); method public void setLightColor(int); method public void setLockscreenVisibility(int); + method public void setName(java.lang.CharSequence); method public void setShowBadge(boolean); method public void setSound(android.net.Uri, android.media.AudioAttributes); method public void setVibrationPattern(long[]); @@ -5730,18 +5729,14 @@ package android.app { public final class NotificationChannelGroup implements android.os.Parcelable { ctor public NotificationChannelGroup(java.lang.String, java.lang.CharSequence); - ctor public NotificationChannelGroup(java.lang.String, int); ctor protected NotificationChannelGroup(android.os.Parcel); - method public void addChannel(android.app.NotificationChannel); method public android.app.NotificationChannelGroup clone(); method public int describeContents(); method public java.util.List<android.app.NotificationChannel> getChannels(); method public java.lang.String getId(); method public java.lang.CharSequence getName(); - method public int getNameResId(); method public org.json.JSONObject toJson() throws org.json.JSONException; method public void writeToParcel(android.os.Parcel, int); - method public void writeXml(org.xmlpull.v1.XmlSerializer) throws java.io.IOException; field public static final android.os.Parcelable.Creator<android.app.NotificationChannelGroup> CREATOR; } @@ -5756,12 +5751,14 @@ package android.app { method public void createNotificationChannelGroups(java.util.List<android.app.NotificationChannelGroup>); method public void createNotificationChannels(java.util.List<android.app.NotificationChannel>); method public void deleteNotificationChannel(java.lang.String); + method public void deleteNotificationChannelGroup(java.lang.String); method public android.service.notification.StatusBarNotification[] getActiveNotifications(); method public android.app.AutomaticZenRule getAutomaticZenRule(java.lang.String); method public java.util.Map<java.lang.String, android.app.AutomaticZenRule> getAutomaticZenRules(); method public final int getCurrentInterruptionFilter(); method public int getImportance(); method public android.app.NotificationChannel getNotificationChannel(java.lang.String); + method public java.util.List<android.app.NotificationChannelGroup> getNotificationChannelGroups(); method public java.util.List<android.app.NotificationChannel> getNotificationChannels(); method public android.app.NotificationManager.Policy getNotificationPolicy(); method public boolean isNotificationPolicyAccessGranted(); @@ -6816,7 +6813,7 @@ package android.app.assist { public static class AssistStructure.ViewNode { method public float getAlpha(); - method public int getAutoFillHint(); + method public java.lang.String[] getAutoFillHint(); method public android.view.autofill.AutofillId getAutofillId(); method public java.lang.String[] getAutofillOptions(); method public int getAutofillType(); @@ -7594,6 +7591,7 @@ package android.bluetooth { method public android.bluetooth.le.BluetoothLeScanner getBluetoothLeScanner(); method public java.util.Set<android.bluetooth.BluetoothDevice> getBondedDevices(); method public static synchronized android.bluetooth.BluetoothAdapter getDefaultAdapter(); + method public int getLeMaximumAdvertisingDataLength(); method public java.lang.String getName(); method public android.bluetooth.le.PeriodicAdvertisingManager getPeriodicAdvertisingManager(); method public int getProfileConnectionState(int); @@ -11241,6 +11239,7 @@ package android.content.pm { method public abstract boolean setDefaultBrowserPackageNameAsUser(java.lang.String, int); method public abstract void setInstallerPackageName(java.lang.String, java.lang.String); method public abstract boolean setInstantAppCookie(byte[]); + method public abstract void setUpdateAvailable(java.lang.String, boolean); method public abstract boolean updateIntentVerificationStatusAsUser(java.lang.String, int, int); method public abstract void updatePermissionFlags(java.lang.String, java.lang.String, int, int, android.os.UserHandle); method public abstract void verifyIntentFilter(int, int, java.util.List<java.lang.String>); @@ -11332,6 +11331,7 @@ package android.content.pm { field public static final java.lang.String FEATURE_USB_ACCESSORY = "android.hardware.usb.accessory"; field public static final java.lang.String FEATURE_USB_HOST = "android.hardware.usb.host"; field public static final java.lang.String FEATURE_VERIFIED_BOOT = "android.software.verified_boot"; + field public static final java.lang.String FEATURE_VR_HEADTRACKING = "android.hardware.vr.headtracking"; field public static final java.lang.String FEATURE_VR_MODE = "android.software.vr.mode"; field public static final java.lang.String FEATURE_VR_MODE_HIGH_PERFORMANCE = "android.hardware.vr.high_performance"; field public static final java.lang.String FEATURE_VULKAN_HARDWARE_COMPUTE = "android.hardware.vulkan.compute"; @@ -13493,7 +13493,7 @@ package android.graphics { } public class ColorFilter { - ctor public ColorFilter(); + ctor public deprecated ColorFilter(); } public class ColorMatrix { @@ -13517,6 +13517,9 @@ package android.graphics { public class ColorMatrixColorFilter extends android.graphics.ColorFilter { ctor public ColorMatrixColorFilter(android.graphics.ColorMatrix); ctor public ColorMatrixColorFilter(float[]); + method public void getColorMatrix(android.graphics.ColorMatrix); + method public void setColorMatrix(android.graphics.ColorMatrix); + method public void setColorMatrixArray(float[]); } public abstract class ColorSpace { @@ -13740,6 +13743,10 @@ package android.graphics { public class LightingColorFilter extends android.graphics.ColorFilter { ctor public LightingColorFilter(int, int); + method public int getColorAdd(); + method public int getColorMultiply(); + method public void setColorAdd(int); + method public void setColorMultiply(int); } public class LinearGradient extends android.graphics.Shader { @@ -14256,6 +14263,10 @@ package android.graphics { public class PorterDuffColorFilter extends android.graphics.ColorFilter { ctor public PorterDuffColorFilter(int, android.graphics.PorterDuff.Mode); + method public int getColor(); + method public android.graphics.PorterDuff.Mode getMode(); + method public void setColor(int); + method public void setMode(android.graphics.PorterDuff.Mode); } public class PorterDuffXfermode extends android.graphics.Xfermode { @@ -23621,7 +23632,7 @@ package android.media { method public deprecated java.nio.ByteBuffer[] getInputBuffers(); method public final android.media.MediaFormat getInputFormat(); method public android.media.Image getInputImage(int); - method public android.os.Bundle getMetrics(); + method public android.media.MediaMetricsSet getMetrics(); method public final java.lang.String getName(); method public java.nio.ByteBuffer getOutputBuffer(int); method public deprecated java.nio.ByteBuffer[] getOutputBuffers(); @@ -24173,7 +24184,7 @@ package android.media { method public boolean advance(); method public long getCachedDuration(); method public android.media.DrmInitData getDrmInitData(); - method public android.os.Bundle getMetrics(); + method public android.media.MediaMetricsSet getMetrics(); method public java.util.Map<java.util.UUID, byte[]> getPsshInfo(); method public boolean getSampleCryptoInfo(android.media.MediaCodec.CryptoInfo); method public int getSampleFlags(); @@ -24428,6 +24439,69 @@ package android.media { field public static final int OPTION_PREVIOUS_SYNC = 0; // 0x0 } + public final class MediaMetricsSet { + method public double getDouble(java.lang.String, double); + method public int getInt(java.lang.String, int); + method public long getLong(java.lang.String, long); + method public java.lang.String getString(java.lang.String, java.lang.String); + method public boolean isEmpty(); + method public java.util.Set<java.lang.String> keySet(); + method public int size(); + } + + public static final class MediaMetricsSet.MediaCodec { + field public static final java.lang.String KEY_CODEC = "android.media.mediacodec.codec"; + field public static final java.lang.String KEY_ENCODER = "android.media.mediacodec.encoder"; + field public static final java.lang.String KEY_HEIGHT = "android.media.mediacodec.height"; + field public static final java.lang.String KEY_MIME = "android.media.mediacodec.mime"; + field public static final java.lang.String KEY_MODE = "android.media.mediacodec.mode"; + field public static final java.lang.String KEY_ROTATION = "android.media.mediacodec.rotation"; + field public static final java.lang.String KEY_SECURE = "android.media.mediacodec.secure"; + field public static final java.lang.String KEY_WIDTH = "android.media.mediacodec.width"; + field public static final java.lang.String MODE_AUDIO = "audio"; + field public static final java.lang.String MODE_VIDEO = "video"; + } + + public static final class MediaMetricsSet.MediaExtractor { + field public static final java.lang.String KEY_FORMAT = "android.media.mediaextractor.fmt"; + field public static final java.lang.String KEY_MIME = "android.media.mediaextractor.mime"; + field public static final java.lang.String KEY_TRACKS = "android.media.mediaextractor.ntrk"; + } + + public static final class MediaMetricsSet.MediaPlayer { + field public static final java.lang.String KEY_CODEC_AUDIO = "android.media.mediaplayer.audio.codec"; + field public static final java.lang.String KEY_CODEC_VIDEO = "android.media.mediaplayer.video.codec"; + field public static final java.lang.String KEY_DURATION = "android.media.mediaplayer.durationMs"; + field public static final java.lang.String KEY_ERRORS = "android.media.mediaplayer.err"; + field public static final java.lang.String KEY_ERROR_CODE = "android.media.mediaplayer.errcode"; + field public static final java.lang.String KEY_FRAMES = "android.media.mediaplayer.frames"; + field public static final java.lang.String KEY_FRAMES_DROPPED = "android.media.mediaplayer.dropped"; + field public static final java.lang.String KEY_HEIGHT = "android.media.mediaplayer.height"; + field public static final java.lang.String KEY_MIME_AUDIO = "android.media.mediaplayer.audio.mime"; + field public static final java.lang.String KEY_MIME_VIDEO = "android.media.mediaplayer.video.mime"; + field public static final java.lang.String KEY_PLAYING = "android.media.mediaplayer.playingMs"; + field public static final java.lang.String KEY_WIDTH = "android.media.mediaplayer.width"; + } + + public static final class MediaMetricsSet.MediaRecorder { + field public static final java.lang.String KEY_AUDIO_BITRATE = "android.media.mediarecorder.audio-bitrate"; + field public static final java.lang.String KEY_AUDIO_CHANNELS = "android.media.mediarecorder.audio-channels"; + field public static final java.lang.String KEY_AUDIO_SAMPLERATE = "android.media.mediarecorder.audio-samplerate"; + field public static final java.lang.String KEY_AUDIO_TIMESCALE = "android.media.mediarecorder.audio-timescale"; + field public static final java.lang.String KEY_CAPTURE_FPS = "android.media.mediarecorder.capture-fps"; + field public static final java.lang.String KEY_CAPTURE_FPS_ENABLE = "android.media.mediarecorder.capture-fpsenable"; + field public static final java.lang.String KEY_FRAMERATE = "android.media.mediarecorder.frame-rate"; + field public static final java.lang.String KEY_HEIGHT = "android.media.mediarecorder.height"; + field public static final java.lang.String KEY_MOVIE_TIMESCALE = "android.media.mediarecorder.movie-timescale"; + field public static final java.lang.String KEY_ROTATION = "android.media.mediarecorder.rotation"; + field public static final java.lang.String KEY_VIDEO_BITRATE = "android.media.mediarecorder.video-bitrate"; + field public static final java.lang.String KEY_VIDEO_IFRAME_INTERVAL = "android.media.mediarecorder.video-iframe-interval"; + field public static final java.lang.String KEY_VIDEO_LEVEL = "android.media.mediarecorder.video-encoder-level"; + field public static final java.lang.String KEY_VIDEO_PROFILE = "android.media.mediarecorder.video-encoder-profile"; + field public static final java.lang.String KEY_VIDEO_TIMESCALE = "android.media.mediarecorder.video-timescale"; + field public static final java.lang.String KEY_WIDTH = "android.media.mediarecorder.width"; + } + 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; @@ -24468,7 +24542,7 @@ package android.media { method public java.lang.String getDrmPropertyString(java.lang.String) throws android.media.MediaPlayer.NoDrmSchemeException; method public int getDuration(); method public android.media.MediaDrm.KeyRequest getKeyRequest(byte[], java.lang.String, int, java.util.Map<java.lang.String, java.lang.String>) throws android.media.MediaPlayer.NoDrmSchemeException; - method public android.os.Bundle getMetrics(); + method public android.media.MediaMetricsSet getMetrics(); method public android.media.PlaybackParams getPlaybackParams(); method public int getSelectedTrack(int) throws java.lang.IllegalStateException; method public android.media.SyncParams getSyncParams(); @@ -24636,7 +24710,7 @@ package android.media { ctor public MediaRecorder(); method public static final int getAudioSourceMax(); method public int getMaxAmplitude() throws java.lang.IllegalStateException; - method public android.os.Bundle getMetrics(); + method public android.media.MediaMetricsSet getMetrics(); method public android.view.Surface getSurface(); method public void pause() throws java.lang.IllegalStateException; method public void prepare() throws java.io.IOException, java.lang.IllegalStateException; @@ -26616,11 +26690,15 @@ package android.media.tv { method public int describeContents(); method public java.lang.String getAudioAddress(); method public int getAudioType(); + method public int getCableConnectionStatus(); method public int getDeviceId(); method public int getHdmiPortId(); method public int getType(); method public void readFromParcel(android.os.Parcel); method public void writeToParcel(android.os.Parcel, int); + field public static final int CABLE_CONNECTION_STATUS_CONNECTED = 1; // 0x1 + field public static final int CABLE_CONNECTION_STATUS_DISCONNECTED = 2; // 0x2 + field public static final int CABLE_CONNECTION_STATUS_UNKNOWN = 0; // 0x0 field public static final android.os.Parcelable.Creator<android.media.tv.TvInputHardwareInfo> CREATOR; field public static final int TV_INPUT_TYPE_COMPONENT = 6; // 0x6 field public static final int TV_INPUT_TYPE_COMPOSITE = 3; // 0x3 @@ -26639,6 +26717,7 @@ package android.media.tv { method public android.media.tv.TvInputHardwareInfo.Builder audioAddress(java.lang.String); method public android.media.tv.TvInputHardwareInfo.Builder audioType(int); method public android.media.tv.TvInputHardwareInfo build(); + method public android.media.tv.TvInputHardwareInfo.Builder cableConnectionStatus(int); method public android.media.tv.TvInputHardwareInfo.Builder deviceId(int); method public android.media.tv.TvInputHardwareInfo.Builder hdmiPortId(int); method public android.media.tv.TvInputHardwareInfo.Builder type(int); @@ -26646,7 +26725,7 @@ package android.media.tv { public final class TvInputInfo implements android.os.Parcelable { method public boolean canRecord(); - method public android.content.Intent createSettingsIntent(); + method public deprecated android.content.Intent createSettingsIntent(); method public android.content.Intent createSetupIntent(); method public static deprecated android.media.tv.TvInputInfo createTvInputInfo(android.content.Context, android.content.pm.ResolveInfo, android.hardware.hdmi.HdmiDeviceInfo, java.lang.String, java.lang.String, android.net.Uri) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException; method public static deprecated android.media.tv.TvInputInfo createTvInputInfo(android.content.Context, android.content.pm.ResolveInfo, android.hardware.hdmi.HdmiDeviceInfo, java.lang.String, int, android.graphics.drawable.Icon) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException; @@ -27312,8 +27391,8 @@ package android.net { method public void reportNetworkConnectivity(android.net.Network, boolean); method public boolean requestBandwidthUpdate(android.net.Network); method public void requestNetwork(android.net.NetworkRequest, android.net.ConnectivityManager.NetworkCallback); - method public void requestNetwork(android.net.NetworkRequest, int, android.net.ConnectivityManager.NetworkCallback); method public void requestNetwork(android.net.NetworkRequest, android.net.ConnectivityManager.NetworkCallback, android.os.Handler); + method public void requestNetwork(android.net.NetworkRequest, int, android.net.ConnectivityManager.NetworkCallback); method public void requestNetwork(android.net.NetworkRequest, int, android.net.ConnectivityManager.NetworkCallback, android.os.Handler); method public void requestNetwork(android.net.NetworkRequest, android.app.PendingIntent); method public deprecated void setNetworkPreference(int); @@ -36751,6 +36830,7 @@ package android.provider { field public static final java.lang.String EXTRA_RECIPIENT_CONTACT_CHAT_ID = "android.provider.extra.RECIPIENT_CONTACT_CHAT_ID"; field public static final java.lang.String EXTRA_RECIPIENT_CONTACT_NAME = "android.provider.extra.RECIPIENT_CONTACT_NAME"; field public static final java.lang.String EXTRA_RECIPIENT_CONTACT_URI = "android.provider.extra.RECIPIENT_CONTACT_URI"; + field public static final java.lang.String EXTRA_SENDER_ACCOUNT_HASH = "android.provider.extra.SENDER_ACCOUNT_HASH"; field public static final java.lang.String INVITE_CONTACT = "com.android.contacts.action.INVITE_CONTACT"; field public static final java.lang.String METADATA_ACCOUNT_TYPE = "android.provider.account_type"; field public static final java.lang.String METADATA_MIMETYPE = "android.provider.mimetype"; @@ -39773,7 +39853,7 @@ package android.service.autofill { method public final android.os.IBinder onBind(android.content.Intent); method public void onConnected(); method public void onDisconnected(); - method public abstract void onFillRequest(android.app.assist.AssistStructure, android.os.Bundle, android.os.CancellationSignal, android.service.autofill.FillCallback); + method public void onFillRequest(android.app.assist.AssistStructure, android.os.Bundle, int, android.os.CancellationSignal, android.service.autofill.FillCallback); method public abstract void onSaveRequest(android.app.assist.AssistStructure, android.os.Bundle, android.service.autofill.SaveCallback); field public static final java.lang.String SERVICE_INTERFACE = "android.service.autofill.AutofillService"; field public static final java.lang.String SERVICE_META_DATA = "android.autofill"; @@ -39828,11 +39908,11 @@ package android.service.autofill { } public static final class SaveInfo.Builder { - ctor public SaveInfo.Builder(int); - method public android.service.autofill.SaveInfo.Builder addSavableIds(android.view.autofill.AutofillId...); + ctor public SaveInfo.Builder(int, android.view.autofill.AutofillId[]); method public android.service.autofill.SaveInfo build(); method public android.service.autofill.SaveInfo.Builder setDescription(java.lang.CharSequence); method public android.service.autofill.SaveInfo.Builder setNegativeAction(java.lang.CharSequence, android.content.IntentSender); + method public android.service.autofill.SaveInfo.Builder setOptionalIds(android.view.autofill.AutofillId[]); } } @@ -40161,11 +40241,11 @@ package android.service.notification { field public static final int INTERRUPTION_FILTER_UNKNOWN = 0; // 0x0 field public static final int REASON_APP_CANCEL = 8; // 0x8 field public static final int REASON_APP_CANCEL_ALL = 9; // 0x9 + field public static final int REASON_CANCEL = 2; // 0x2 + field public static final int REASON_CANCEL_ALL = 3; // 0x3 field public static final int REASON_CHANNEL_BANNED = 17; // 0x11 - field public static final int REASON_DELEGATE_CANCEL = 2; // 0x2 - field public static final int REASON_DELEGATE_CANCEL_ALL = 3; // 0x3 - field public static final int REASON_DELEGATE_CLICK = 1; // 0x1 - field public static final int REASON_DELEGATE_ERROR = 4; // 0x4 + field public static final int REASON_CLICK = 1; // 0x1 + field public static final int REASON_ERROR = 4; // 0x4 field public static final int REASON_GROUP_OPTIMIZATION = 13; // 0xd field public static final int REASON_GROUP_SUMMARY_CANCELED = 12; // 0xc field public static final int REASON_LISTENER_CANCEL = 10; // 0xa @@ -42536,6 +42616,7 @@ package android.telephony { method public void notifyConfigChangedForSubId(int); method public void updateConfigForPhoneId(int, java.lang.String); field public static final java.lang.String ACTION_CARRIER_CONFIG_CHANGED = "android.telephony.action.CARRIER_CONFIG_CHANGED"; + field public static final int DATA_CYCLE_THRESHOLD_DISABLED = -2; // 0xfffffffe field public static final java.lang.String KEY_ADDITIONAL_CALL_SETTING_BOOL = "additional_call_setting_bool"; field public static final java.lang.String KEY_ALLOW_ADDING_APNS_BOOL = "allow_adding_apns_bool"; field public static final java.lang.String KEY_ALLOW_ADD_CALL_DURING_VIDEO_CALL_BOOL = "allow_add_call_during_video_call"; @@ -42578,6 +42659,8 @@ package android.telephony { field public static final java.lang.String KEY_CI_ACTION_ON_SYS_UPDATE_INTENT_STRING = "ci_action_on_sys_update_intent_string"; field public static final java.lang.String KEY_CONFIG_IMS_PACKAGE_OVERRIDE_STRING = "config_ims_package_override_string"; field public static final java.lang.String KEY_CSP_ENABLED_BOOL = "csp_enabled_bool"; + field public static final java.lang.String KEY_DATA_LIMIT_THRESHOLD_BYTES_LONG = "data_limit_threshold_bytes_long"; + field public static final java.lang.String KEY_DATA_WARNING_THRESHOLD_BYTES_LONG = "data_warning_threshold_bytes_long"; field public static final java.lang.String KEY_DEFAULT_SIM_CALL_MANAGER_STRING = "default_sim_call_manager_string"; field public static final java.lang.String KEY_DEFAULT_VM_NUMBER_STRING = "default_vm_number_string"; field public static final java.lang.String KEY_DIAL_STRING_REPLACE_STRING_ARRAY = "dial_string_replace_string_array"; @@ -42633,6 +42716,7 @@ package android.telephony { field public static final java.lang.String KEY_MMS_UA_PROF_TAG_NAME_STRING = "uaProfTagName"; field public static final java.lang.String KEY_MMS_UA_PROF_URL_STRING = "uaProfUrl"; field public static final java.lang.String KEY_MMS_USER_AGENT_STRING = "userAgent"; + field public static final java.lang.String KEY_MONTHLY_DATA_CYCLE_DAY_INT = "monthly_data_cycle_day_int"; field public static final java.lang.String KEY_ONLY_SINGLE_DC_ALLOWED_INT_ARRAY = "only_single_dc_allowed_int_array"; field public static final java.lang.String KEY_OPERATOR_SELECTION_EXPAND_BOOL = "operator_selection_expand_bool"; field public static final java.lang.String KEY_PREFER_2G_BOOL = "prefer_2g_bool"; @@ -43205,8 +43289,8 @@ package android.telephony { method public int getCurrentPhoneType(); method public int getCurrentPhoneType(int); method public int getDataActivity(); - method public boolean getDataEnabled(); - method public boolean getDataEnabled(int); + method public deprecated boolean getDataEnabled(); + method public deprecated boolean getDataEnabled(int); method public int getDataNetworkType(); method public int getDataState(); method public java.lang.String getDeviceId(); @@ -43253,6 +43337,7 @@ package android.telephony { method public java.lang.String iccTransmitApduLogicalChannel(int, int, int, int, int, int, java.lang.String); method public boolean isConcurrentVoiceAndDataAllowed(); method public boolean isDataConnectivityPossible(); + method public boolean isDataEnabled(); method public boolean isHearingAidCompatibilitySupported(); method public boolean isIdle(); method public boolean isNetworkRoaming(); @@ -44138,6 +44223,7 @@ package android.test.mock { method public boolean setDefaultBrowserPackageNameAsUser(java.lang.String, int); method public void setInstallerPackageName(java.lang.String, java.lang.String); method public boolean setInstantAppCookie(byte[]); + method public void setUpdateAvailable(java.lang.String, boolean); method public boolean updateIntentVerificationStatusAsUser(java.lang.String, int, int); method public void updatePermissionFlags(java.lang.String, java.lang.String, int, int, android.os.UserHandle); method public void verifyIntentFilter(int, int, java.util.List<java.lang.String>); @@ -44343,11 +44429,10 @@ package android.text { } public final class FontConfig implements android.os.Parcelable { - ctor public FontConfig(); - ctor public FontConfig(android.text.FontConfig); + ctor public FontConfig(android.text.FontConfig.Family[], android.text.FontConfig.Alias[]); method public int describeContents(); - method public java.util.List<android.text.FontConfig.Alias> getAliases(); - method public java.util.List<android.text.FontConfig.Family> getFamilies(); + method public android.text.FontConfig.Alias[] getAliases(); + method public android.text.FontConfig.Family[] getFamilies(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.text.FontConfig> CREATOR; } @@ -44372,22 +44457,22 @@ package android.text { } public static final class FontConfig.Family implements android.os.Parcelable { - ctor public FontConfig.Family(java.lang.String, java.util.List<android.text.FontConfig.Font>, java.lang.String, java.lang.String); - ctor public FontConfig.Family(android.text.FontConfig.Family); + ctor public FontConfig.Family(java.lang.String, android.text.FontConfig.Font[], java.lang.String, int); method public int describeContents(); - method public java.util.List<android.text.FontConfig.Font> getFonts(); + method public android.text.FontConfig.Font[] getFonts(); method public java.lang.String getLanguage(); method public java.lang.String getName(); - method public java.lang.String getVariant(); + method public int getVariant(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.text.FontConfig.Family> CREATOR; + field public static final int VARIANT_COMPACT = 1; // 0x1 + field public static final int VARIANT_DEFAULT = 0; // 0x0 + field public static final int VARIANT_ELEGANT = 2; // 0x2 } public static final class FontConfig.Font implements android.os.Parcelable { - ctor public FontConfig.Font(java.lang.String, int, java.util.List<android.text.FontConfig.Axis>, int, boolean); - ctor public FontConfig.Font(android.text.FontConfig.Font); method public int describeContents(); - method public java.util.List<android.text.FontConfig.Axis> getAxes(); + method public android.text.FontConfig.Axis[] getAxes(); method public android.os.ParcelFileDescriptor getFd(); method public java.lang.String getFontName(); method public int getTtcIndex(); @@ -48466,8 +48551,8 @@ package android.view { method public void addTouchables(java.util.ArrayList<android.view.View>); method public android.view.ViewPropertyAnimator animate(); method public void announceForAccessibility(java.lang.CharSequence); - method public void autofill(android.view.autofill.AutofillValue); - method public void autofillVirtual(int, android.view.autofill.AutofillValue); + method public boolean autofill(android.view.autofill.AutofillValue); + method public boolean autofillVirtual(int, android.view.autofill.AutofillValue); method protected boolean awakenScrollBars(); method protected boolean awakenScrollBars(int); method protected boolean awakenScrollBars(int, boolean); @@ -48557,7 +48642,7 @@ package android.view { method public float getAlpha(); method public android.view.animation.Animation getAnimation(); method public android.os.IBinder getApplicationWindowToken(); - method public int getAutofillHint(); + method public java.lang.String[] getAutofillHint(); method public int getAutofillMode(); method public int getAutofillType(); method public android.view.autofill.AutofillValue getAutofillValue(); @@ -48878,7 +48963,7 @@ package android.view { method public void setActivated(boolean); method public void setAlpha(float); method public void setAnimation(android.view.animation.Animation); - method public void setAutofillHint(int); + method public void setAutofillHint(java.lang.String...); method public void setAutofillMode(int); method public void setBackground(android.graphics.drawable.Drawable); method public void setBackgroundColor(int); @@ -49021,20 +49106,19 @@ package android.view { field public static final int ACCESSIBILITY_LIVE_REGION_NONE = 0; // 0x0 field public static final int ACCESSIBILITY_LIVE_REGION_POLITE = 1; // 0x1 field public static final android.util.Property<android.view.View, java.lang.Float> ALPHA; - field public static final int AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_DATE = 512; // 0x200 - field public static final int AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_DAY = 4096; // 0x1000 - field public static final int AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_MONTH = 1024; // 0x400 - field public static final int AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_YEAR = 2048; // 0x800 - field public static final int AUTOFILL_HINT_CREDIT_CARD_NUMBER = 128; // 0x80 - field public static final int AUTOFILL_HINT_CREDIT_CARD_SECURITY_CODE = 256; // 0x100 - field public static final int AUTOFILL_HINT_EMAIL_ADDRESS = 1; // 0x1 - field public static final int AUTOFILL_HINT_NAME = 2; // 0x2 - field public static final int AUTOFILL_HINT_NONE = 0; // 0x0 - field public static final int AUTOFILL_HINT_PASSWORD = 8; // 0x8 - field public static final int AUTOFILL_HINT_PHONE = 16; // 0x10 - field public static final int AUTOFILL_HINT_POSTAL_ADDRESS = 32; // 0x20 - field public static final int AUTOFILL_HINT_POSTAL_CODE = 64; // 0x40 - field public static final int AUTOFILL_HINT_USERNAME = 4; // 0x4 + field public static final java.lang.String AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_DATE = "creditCardExpirationDate"; + field public static final java.lang.String AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_DAY = "creditCardExpirationDay"; + field public static final java.lang.String AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_MONTH = "creditCardExpirationMonth"; + field public static final java.lang.String AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_YEAR = "creditCardExpirationYear"; + field public static final java.lang.String AUTOFILL_HINT_CREDIT_CARD_NUMBER = "creditCardNumber"; + field public static final java.lang.String AUTOFILL_HINT_CREDIT_CARD_SECURITY_CODE = "creditCardSecurityCode"; + field public static final java.lang.String AUTOFILL_HINT_EMAIL_ADDRESS = "emailAddress"; + field public static final java.lang.String AUTOFILL_HINT_NAME = "name"; + field public static final java.lang.String AUTOFILL_HINT_PASSWORD = "password"; + field public static final java.lang.String AUTOFILL_HINT_PHONE = "phone"; + field public static final java.lang.String AUTOFILL_HINT_POSTAL_ADDRESS = "postalAddress"; + field public static final java.lang.String AUTOFILL_HINT_POSTAL_CODE = "postalCode"; + field public static final java.lang.String AUTOFILL_HINT_USERNAME = "username"; field public static final int AUTOFILL_MODE_AUTO = 1; // 0x1 field public static final int AUTOFILL_MODE_INHERIT = 0; // 0x0 field public static final int AUTOFILL_MODE_MANUAL = 2; // 0x2 @@ -49696,7 +49780,7 @@ package android.view { method public abstract void setAccessibilityFocused(boolean); method public abstract void setActivated(boolean); method public abstract void setAlpha(float); - method public abstract void setAutofillHint(int); + method public abstract void setAutofillHint(java.lang.String[]); method public abstract void setAutofillOptions(java.lang.String[]); method public abstract void setAutofillType(int); method public abstract void setAutofillValue(android.view.autofill.AutofillValue); @@ -49707,6 +49791,7 @@ package android.view { method public abstract void setClickable(boolean); method public abstract void setContentDescription(java.lang.CharSequence); method public abstract void setContextClickable(boolean); + method public abstract void setDataIsSensitive(boolean); method public abstract void setDimens(int, int, int, int, int, int); method public abstract void setElevation(float); method public abstract void setEnabled(boolean); @@ -49717,7 +49802,6 @@ package android.view { method public abstract void setInputType(int); method public abstract void setLongClickable(boolean); method public abstract void setOpaque(boolean); - method public abstract void setSanitized(boolean); method public abstract void setSelected(boolean); method public abstract void setText(java.lang.CharSequence); method public abstract void setText(java.lang.CharSequence, int, int); @@ -50965,6 +51049,7 @@ package android.view.autofill { public final class AutofillManager { method public void cancel(); method public void commit(); + method public boolean isEnabled(); method public void notifyValueChanged(android.view.View); method public void notifyViewEntered(android.view.View); method public void notifyViewExited(android.view.View); @@ -50972,9 +51057,12 @@ package android.view.autofill { method public void notifyVirtualViewEntered(android.view.View, int, android.graphics.Rect); method public void notifyVirtualViewExited(android.view.View, int); method public void registerCallback(android.view.autofill.AutofillManager.AutofillCallback); + method public void requestAutofill(android.view.View); + method public void requestAutofill(android.view.View, int, android.graphics.Rect); method public void unregisterCallback(android.view.autofill.AutofillManager.AutofillCallback); field public static final java.lang.String EXTRA_ASSIST_STRUCTURE = "android.view.autofill.extra.ASSIST_STRUCTURE"; field public static final java.lang.String EXTRA_AUTHENTICATION_RESULT = "android.view.autofill.extra.AUTHENTICATION_RESULT"; + field public static final int FLAG_MANUAL_REQUEST = 1; // 0x1 } public static abstract class AutofillManager.AutofillCallback { @@ -50983,6 +51071,7 @@ package android.view.autofill { method public void onAutofillEventVirtual(android.view.View, int, int); field public static final int EVENT_INPUT_HIDDEN = 2; // 0x2 field public static final int EVENT_INPUT_SHOWN = 1; // 0x1 + field public static final int EVENT_INPUT_UNAVAILABLE = 3; // 0x3 } public final class AutofillValue implements android.os.Parcelable { @@ -50995,6 +51084,10 @@ package android.view.autofill { method public int getListValue(); method public java.lang.CharSequence getTextValue(); method public boolean getToggleValue(); + method public boolean isDate(); + method public boolean isList(); + method public boolean isText(); + method public boolean isToggle(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.view.autofill.AutofillValue> CREATOR; } diff --git a/api/test-current.txt b/api/test-current.txt index 3be7f6793001..49bab6f67f81 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -751,6 +751,7 @@ package android { field public static final int isModifier = 16843334; // 0x1010246 field public static final int isRepeatable = 16843336; // 0x1010248 field public static final int isScrollContainer = 16843342; // 0x101024e + field public static final int isStatic = 16844125; // 0x101055d field public static final int isSticky = 16843335; // 0x1010247 field public static final int isolatedProcess = 16843689; // 0x10103a9 field public static final int isolatedSplits = 16844109; // 0x101054d @@ -1789,6 +1790,7 @@ package android { field public static final int accessibilityActionSetProgress = 16908349; // 0x102003d field public static final int accessibilityActionShowOnScreen = 16908342; // 0x1020036 field public static final int addToDictionary = 16908330; // 0x102002a + field public static final int autofill = 16908355; // 0x1020043 field public static final int background = 16908288; // 0x1020000 field public static final int button1 = 16908313; // 0x1020019 field public static final int button2 = 16908314; // 0x102001a @@ -5502,7 +5504,6 @@ package android.app { public final class NotificationChannel implements android.os.Parcelable { ctor public NotificationChannel(java.lang.String, java.lang.CharSequence, int); - ctor public NotificationChannel(java.lang.String, int, int); ctor protected NotificationChannel(android.os.Parcel); method public boolean canBypassDnd(); method public boolean canShowBadge(); @@ -5516,7 +5517,6 @@ package android.app { method public int getLightColor(); method public int getLockscreenVisibility(); method public java.lang.CharSequence getName(); - method public int getNameResId(); method public android.net.Uri getSound(); method public long[] getVibrationPattern(); method public void setBypassDnd(boolean); @@ -5524,6 +5524,7 @@ package android.app { method public void setImportance(int); method public void setLightColor(int); method public void setLockscreenVisibility(int); + method public void setName(java.lang.CharSequence); method public void setShowBadge(boolean); method public void setSound(android.net.Uri, android.media.AudioAttributes); method public void setVibrationPattern(long[]); @@ -5536,14 +5537,12 @@ package android.app { public final class NotificationChannelGroup implements android.os.Parcelable { ctor public NotificationChannelGroup(java.lang.String, java.lang.CharSequence); - ctor public NotificationChannelGroup(java.lang.String, int); ctor protected NotificationChannelGroup(android.os.Parcel); method public android.app.NotificationChannelGroup clone(); method public int describeContents(); method public java.util.List<android.app.NotificationChannel> getChannels(); method public java.lang.String getId(); method public java.lang.CharSequence getName(); - method public int getNameResId(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.app.NotificationChannelGroup> CREATOR; } @@ -5559,6 +5558,7 @@ package android.app { method public void createNotificationChannelGroups(java.util.List<android.app.NotificationChannelGroup>); method public void createNotificationChannels(java.util.List<android.app.NotificationChannel>); method public void deleteNotificationChannel(java.lang.String); + method public void deleteNotificationChannelGroup(java.lang.String); method public android.service.notification.StatusBarNotification[] getActiveNotifications(); method public android.app.AutomaticZenRule getAutomaticZenRule(java.lang.String); method public java.util.Map<java.lang.String, android.app.AutomaticZenRule> getAutomaticZenRules(); @@ -5566,6 +5566,7 @@ package android.app { method public android.content.ComponentName getEffectsSuppressor(); method public int getImportance(); method public android.app.NotificationChannel getNotificationChannel(java.lang.String); + method public java.util.List<android.app.NotificationChannelGroup> getNotificationChannelGroups(); method public java.util.List<android.app.NotificationChannel> getNotificationChannels(); method public android.app.NotificationManager.Policy getNotificationPolicy(); method public boolean isNotificationPolicyAccessGranted(); @@ -6593,7 +6594,7 @@ package android.app.assist { public static class AssistStructure.ViewNode { method public float getAlpha(); - method public int getAutoFillHint(); + method public java.lang.String[] getAutoFillHint(); method public android.view.autofill.AutofillId getAutofillId(); method public java.lang.String[] getAutofillOptions(); method public int getAutofillType(); @@ -7148,6 +7149,7 @@ package android.bluetooth { method public android.bluetooth.le.BluetoothLeScanner getBluetoothLeScanner(); method public java.util.Set<android.bluetooth.BluetoothDevice> getBondedDevices(); method public static synchronized android.bluetooth.BluetoothAdapter getDefaultAdapter(); + method public int getLeMaximumAdvertisingDataLength(); method public java.lang.String getName(); method public android.bluetooth.le.PeriodicAdvertisingManager getPeriodicAdvertisingManager(); method public int getProfileConnectionState(int); @@ -10707,6 +10709,7 @@ package android.content.pm { field public static final java.lang.String FEATURE_USB_ACCESSORY = "android.hardware.usb.accessory"; field public static final java.lang.String FEATURE_USB_HOST = "android.hardware.usb.host"; field public static final java.lang.String FEATURE_VERIFIED_BOOT = "android.software.verified_boot"; + field public static final java.lang.String FEATURE_VR_HEADTRACKING = "android.hardware.vr.headtracking"; field public static final java.lang.String FEATURE_VR_MODE = "android.software.vr.mode"; field public static final java.lang.String FEATURE_VR_MODE_HIGH_PERFORMANCE = "android.hardware.vr.high_performance"; field public static final java.lang.String FEATURE_VULKAN_HARDWARE_COMPUTE = "android.hardware.vulkan.compute"; @@ -12794,7 +12797,7 @@ package android.graphics { } public class ColorFilter { - ctor public ColorFilter(); + ctor public deprecated ColorFilter(); } public class ColorMatrix { @@ -12818,6 +12821,9 @@ package android.graphics { public class ColorMatrixColorFilter extends android.graphics.ColorFilter { ctor public ColorMatrixColorFilter(android.graphics.ColorMatrix); ctor public ColorMatrixColorFilter(float[]); + method public void getColorMatrix(android.graphics.ColorMatrix); + method public void setColorMatrix(android.graphics.ColorMatrix); + method public void setColorMatrixArray(float[]); } public abstract class ColorSpace { @@ -13041,6 +13047,10 @@ package android.graphics { public class LightingColorFilter extends android.graphics.ColorFilter { ctor public LightingColorFilter(int, int); + method public int getColorAdd(); + method public int getColorMultiply(); + method public void setColorAdd(int); + method public void setColorMultiply(int); } public class LinearGradient extends android.graphics.Shader { @@ -13557,6 +13567,10 @@ package android.graphics { public class PorterDuffColorFilter extends android.graphics.ColorFilter { ctor public PorterDuffColorFilter(int, android.graphics.PorterDuff.Mode); + method public int getColor(); + method public android.graphics.PorterDuff.Mode getMode(); + method public void setColor(int); + method public void setMode(android.graphics.PorterDuff.Mode); } public class PorterDuffXfermode extends android.graphics.Xfermode { @@ -21927,7 +21941,7 @@ package android.media { method public deprecated java.nio.ByteBuffer[] getInputBuffers(); method public final android.media.MediaFormat getInputFormat(); method public android.media.Image getInputImage(int); - method public android.os.Bundle getMetrics(); + method public android.media.MediaMetricsSet getMetrics(); method public final java.lang.String getName(); method public java.nio.ByteBuffer getOutputBuffer(int); method public deprecated java.nio.ByteBuffer[] getOutputBuffers(); @@ -22479,7 +22493,7 @@ package android.media { method public boolean advance(); method public long getCachedDuration(); method public android.media.DrmInitData getDrmInitData(); - method public android.os.Bundle getMetrics(); + method public android.media.MediaMetricsSet getMetrics(); method public java.util.Map<java.util.UUID, byte[]> getPsshInfo(); method public boolean getSampleCryptoInfo(android.media.MediaCodec.CryptoInfo); method public int getSampleFlags(); @@ -22734,6 +22748,69 @@ package android.media { field public static final int OPTION_PREVIOUS_SYNC = 0; // 0x0 } + public final class MediaMetricsSet { + method public double getDouble(java.lang.String, double); + method public int getInt(java.lang.String, int); + method public long getLong(java.lang.String, long); + method public java.lang.String getString(java.lang.String, java.lang.String); + method public boolean isEmpty(); + method public java.util.Set<java.lang.String> keySet(); + method public int size(); + } + + public static final class MediaMetricsSet.MediaCodec { + field public static final java.lang.String KEY_CODEC = "android.media.mediacodec.codec"; + field public static final java.lang.String KEY_ENCODER = "android.media.mediacodec.encoder"; + field public static final java.lang.String KEY_HEIGHT = "android.media.mediacodec.height"; + field public static final java.lang.String KEY_MIME = "android.media.mediacodec.mime"; + field public static final java.lang.String KEY_MODE = "android.media.mediacodec.mode"; + field public static final java.lang.String KEY_ROTATION = "android.media.mediacodec.rotation"; + field public static final java.lang.String KEY_SECURE = "android.media.mediacodec.secure"; + field public static final java.lang.String KEY_WIDTH = "android.media.mediacodec.width"; + field public static final java.lang.String MODE_AUDIO = "audio"; + field public static final java.lang.String MODE_VIDEO = "video"; + } + + public static final class MediaMetricsSet.MediaExtractor { + field public static final java.lang.String KEY_FORMAT = "android.media.mediaextractor.fmt"; + field public static final java.lang.String KEY_MIME = "android.media.mediaextractor.mime"; + field public static final java.lang.String KEY_TRACKS = "android.media.mediaextractor.ntrk"; + } + + public static final class MediaMetricsSet.MediaPlayer { + field public static final java.lang.String KEY_CODEC_AUDIO = "android.media.mediaplayer.audio.codec"; + field public static final java.lang.String KEY_CODEC_VIDEO = "android.media.mediaplayer.video.codec"; + field public static final java.lang.String KEY_DURATION = "android.media.mediaplayer.durationMs"; + field public static final java.lang.String KEY_ERRORS = "android.media.mediaplayer.err"; + field public static final java.lang.String KEY_ERROR_CODE = "android.media.mediaplayer.errcode"; + field public static final java.lang.String KEY_FRAMES = "android.media.mediaplayer.frames"; + field public static final java.lang.String KEY_FRAMES_DROPPED = "android.media.mediaplayer.dropped"; + field public static final java.lang.String KEY_HEIGHT = "android.media.mediaplayer.height"; + field public static final java.lang.String KEY_MIME_AUDIO = "android.media.mediaplayer.audio.mime"; + field public static final java.lang.String KEY_MIME_VIDEO = "android.media.mediaplayer.video.mime"; + field public static final java.lang.String KEY_PLAYING = "android.media.mediaplayer.playingMs"; + field public static final java.lang.String KEY_WIDTH = "android.media.mediaplayer.width"; + } + + public static final class MediaMetricsSet.MediaRecorder { + field public static final java.lang.String KEY_AUDIO_BITRATE = "android.media.mediarecorder.audio-bitrate"; + field public static final java.lang.String KEY_AUDIO_CHANNELS = "android.media.mediarecorder.audio-channels"; + field public static final java.lang.String KEY_AUDIO_SAMPLERATE = "android.media.mediarecorder.audio-samplerate"; + field public static final java.lang.String KEY_AUDIO_TIMESCALE = "android.media.mediarecorder.audio-timescale"; + field public static final java.lang.String KEY_CAPTURE_FPS = "android.media.mediarecorder.capture-fps"; + field public static final java.lang.String KEY_CAPTURE_FPS_ENABLE = "android.media.mediarecorder.capture-fpsenable"; + field public static final java.lang.String KEY_FRAMERATE = "android.media.mediarecorder.frame-rate"; + field public static final java.lang.String KEY_HEIGHT = "android.media.mediarecorder.height"; + field public static final java.lang.String KEY_MOVIE_TIMESCALE = "android.media.mediarecorder.movie-timescale"; + field public static final java.lang.String KEY_ROTATION = "android.media.mediarecorder.rotation"; + field public static final java.lang.String KEY_VIDEO_BITRATE = "android.media.mediarecorder.video-bitrate"; + field public static final java.lang.String KEY_VIDEO_IFRAME_INTERVAL = "android.media.mediarecorder.video-iframe-interval"; + field public static final java.lang.String KEY_VIDEO_LEVEL = "android.media.mediarecorder.video-encoder-level"; + field public static final java.lang.String KEY_VIDEO_PROFILE = "android.media.mediarecorder.video-encoder-profile"; + field public static final java.lang.String KEY_VIDEO_TIMESCALE = "android.media.mediarecorder.video-timescale"; + field public static final java.lang.String KEY_WIDTH = "android.media.mediarecorder.width"; + } + 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; @@ -22774,7 +22851,7 @@ package android.media { method public java.lang.String getDrmPropertyString(java.lang.String) throws android.media.MediaPlayer.NoDrmSchemeException; method public int getDuration(); method public android.media.MediaDrm.KeyRequest getKeyRequest(byte[], java.lang.String, int, java.util.Map<java.lang.String, java.lang.String>) throws android.media.MediaPlayer.NoDrmSchemeException; - method public android.os.Bundle getMetrics(); + method public android.media.MediaMetricsSet getMetrics(); method public android.media.PlaybackParams getPlaybackParams(); method public int getSelectedTrack(int) throws java.lang.IllegalStateException; method public android.media.SyncParams getSyncParams(); @@ -22942,7 +23019,7 @@ package android.media { ctor public MediaRecorder(); method public static final int getAudioSourceMax(); method public int getMaxAmplitude() throws java.lang.IllegalStateException; - method public android.os.Bundle getMetrics(); + method public android.media.MediaMetricsSet getMetrics(); method public android.view.Surface getSurface(); method public void pause() throws java.lang.IllegalStateException; method public void prepare() throws java.io.IOException, java.lang.IllegalStateException; @@ -24753,7 +24830,7 @@ package android.media.tv { public final class TvInputInfo implements android.os.Parcelable { method public boolean canRecord(); - method public android.content.Intent createSettingsIntent(); + method public deprecated android.content.Intent createSettingsIntent(); method public android.content.Intent createSetupIntent(); method public int describeContents(); method public android.os.Bundle getExtras(); @@ -25294,8 +25371,8 @@ package android.net { method public void reportNetworkConnectivity(android.net.Network, boolean); method public boolean requestBandwidthUpdate(android.net.Network); method public void requestNetwork(android.net.NetworkRequest, android.net.ConnectivityManager.NetworkCallback); - method public void requestNetwork(android.net.NetworkRequest, int, android.net.ConnectivityManager.NetworkCallback); method public void requestNetwork(android.net.NetworkRequest, android.net.ConnectivityManager.NetworkCallback, android.os.Handler); + method public void requestNetwork(android.net.NetworkRequest, int, android.net.ConnectivityManager.NetworkCallback); method public void requestNetwork(android.net.NetworkRequest, int, android.net.ConnectivityManager.NetworkCallback, android.os.Handler); method public void requestNetwork(android.net.NetworkRequest, android.app.PendingIntent); method public deprecated void setNetworkPreference(int); @@ -34004,6 +34081,7 @@ package android.provider { field public static final java.lang.String EXTRA_RECIPIENT_CONTACT_CHAT_ID = "android.provider.extra.RECIPIENT_CONTACT_CHAT_ID"; field public static final java.lang.String EXTRA_RECIPIENT_CONTACT_NAME = "android.provider.extra.RECIPIENT_CONTACT_NAME"; field public static final java.lang.String EXTRA_RECIPIENT_CONTACT_URI = "android.provider.extra.RECIPIENT_CONTACT_URI"; + field public static final java.lang.String EXTRA_SENDER_ACCOUNT_HASH = "android.provider.extra.SENDER_ACCOUNT_HASH"; field public static final java.lang.String INVITE_CONTACT = "com.android.contacts.action.INVITE_CONTACT"; field public static final java.lang.String METADATA_ACCOUNT_TYPE = "android.provider.account_type"; field public static final java.lang.String METADATA_MIMETYPE = "android.provider.mimetype"; @@ -36885,7 +36963,7 @@ package android.service.autofill { method public final android.os.IBinder onBind(android.content.Intent); method public void onConnected(); method public void onDisconnected(); - method public abstract void onFillRequest(android.app.assist.AssistStructure, android.os.Bundle, android.os.CancellationSignal, android.service.autofill.FillCallback); + method public void onFillRequest(android.app.assist.AssistStructure, android.os.Bundle, int, android.os.CancellationSignal, android.service.autofill.FillCallback); method public abstract void onSaveRequest(android.app.assist.AssistStructure, android.os.Bundle, android.service.autofill.SaveCallback); field public static final java.lang.String SERVICE_INTERFACE = "android.service.autofill.AutofillService"; field public static final java.lang.String SERVICE_META_DATA = "android.autofill"; @@ -36940,11 +37018,11 @@ package android.service.autofill { } public static final class SaveInfo.Builder { - ctor public SaveInfo.Builder(int); - method public android.service.autofill.SaveInfo.Builder addSavableIds(android.view.autofill.AutofillId...); + ctor public SaveInfo.Builder(int, android.view.autofill.AutofillId[]); method public android.service.autofill.SaveInfo build(); method public android.service.autofill.SaveInfo.Builder setDescription(java.lang.CharSequence); method public android.service.autofill.SaveInfo.Builder setNegativeAction(java.lang.CharSequence, android.content.IntentSender); + method public android.service.autofill.SaveInfo.Builder setOptionalIds(android.view.autofill.AutofillId[]); } } @@ -37268,11 +37346,11 @@ package android.service.notification { field public static final int INTERRUPTION_FILTER_UNKNOWN = 0; // 0x0 field public static final int REASON_APP_CANCEL = 8; // 0x8 field public static final int REASON_APP_CANCEL_ALL = 9; // 0x9 + field public static final int REASON_CANCEL = 2; // 0x2 + field public static final int REASON_CANCEL_ALL = 3; // 0x3 field public static final int REASON_CHANNEL_BANNED = 17; // 0x11 - field public static final int REASON_DELEGATE_CANCEL = 2; // 0x2 - field public static final int REASON_DELEGATE_CANCEL_ALL = 3; // 0x3 - field public static final int REASON_DELEGATE_CLICK = 1; // 0x1 - field public static final int REASON_DELEGATE_ERROR = 4; // 0x4 + field public static final int REASON_CLICK = 1; // 0x1 + field public static final int REASON_ERROR = 4; // 0x4 field public static final int REASON_GROUP_OPTIMIZATION = 13; // 0xd field public static final int REASON_GROUP_SUMMARY_CANCELED = 12; // 0xc field public static final int REASON_LISTENER_CANCEL = 10; // 0xa @@ -39367,6 +39445,7 @@ package android.telephony { method public android.os.PersistableBundle getConfigForSubId(int); method public void notifyConfigChangedForSubId(int); field public static final java.lang.String ACTION_CARRIER_CONFIG_CHANGED = "android.telephony.action.CARRIER_CONFIG_CHANGED"; + field public static final int DATA_CYCLE_THRESHOLD_DISABLED = -2; // 0xfffffffe field public static final java.lang.String KEY_ADDITIONAL_CALL_SETTING_BOOL = "additional_call_setting_bool"; field public static final java.lang.String KEY_ALLOW_ADDING_APNS_BOOL = "allow_adding_apns_bool"; field public static final java.lang.String KEY_ALLOW_ADD_CALL_DURING_VIDEO_CALL_BOOL = "allow_add_call_during_video_call"; @@ -39409,6 +39488,8 @@ package android.telephony { field public static final java.lang.String KEY_CI_ACTION_ON_SYS_UPDATE_INTENT_STRING = "ci_action_on_sys_update_intent_string"; field public static final java.lang.String KEY_CONFIG_IMS_PACKAGE_OVERRIDE_STRING = "config_ims_package_override_string"; field public static final java.lang.String KEY_CSP_ENABLED_BOOL = "csp_enabled_bool"; + field public static final java.lang.String KEY_DATA_LIMIT_THRESHOLD_BYTES_LONG = "data_limit_threshold_bytes_long"; + field public static final java.lang.String KEY_DATA_WARNING_THRESHOLD_BYTES_LONG = "data_warning_threshold_bytes_long"; field public static final java.lang.String KEY_DEFAULT_SIM_CALL_MANAGER_STRING = "default_sim_call_manager_string"; field public static final java.lang.String KEY_DEFAULT_VM_NUMBER_STRING = "default_vm_number_string"; field public static final java.lang.String KEY_DIAL_STRING_REPLACE_STRING_ARRAY = "dial_string_replace_string_array"; @@ -39464,6 +39545,7 @@ package android.telephony { field public static final java.lang.String KEY_MMS_UA_PROF_TAG_NAME_STRING = "uaProfTagName"; field public static final java.lang.String KEY_MMS_UA_PROF_URL_STRING = "uaProfUrl"; field public static final java.lang.String KEY_MMS_USER_AGENT_STRING = "userAgent"; + field public static final java.lang.String KEY_MONTHLY_DATA_CYCLE_DAY_INT = "monthly_data_cycle_day_int"; field public static final java.lang.String KEY_ONLY_SINGLE_DC_ALLOWED_INT_ARRAY = "only_single_dc_allowed_int_array"; field public static final java.lang.String KEY_OPERATOR_SELECTION_EXPAND_BOOL = "operator_selection_expand_bool"; field public static final java.lang.String KEY_PREFER_2G_BOOL = "prefer_2g_bool"; @@ -39996,7 +40078,6 @@ package android.telephony { method public android.os.PersistableBundle getCarrierConfig(); method public android.telephony.CellLocation getCellLocation(); method public int getDataActivity(); - method public boolean getDataEnabled(); method public int getDataNetworkType(); method public int getDataState(); method public java.lang.String getDeviceId(); @@ -40037,6 +40118,7 @@ package android.telephony { method public java.lang.String iccTransmitApduBasicChannel(int, int, int, int, int, java.lang.String); method public java.lang.String iccTransmitApduLogicalChannel(int, int, int, int, int, int, java.lang.String); method public boolean isConcurrentVoiceAndDataAllowed(); + method public boolean isDataEnabled(); method public boolean isHearingAidCompatibilitySupported(); method public boolean isNetworkRoaming(); method public boolean isSmsCapable(); @@ -41078,11 +41160,10 @@ package android.text { } public final class FontConfig implements android.os.Parcelable { - ctor public FontConfig(); - ctor public FontConfig(android.text.FontConfig); + ctor public FontConfig(android.text.FontConfig.Family[], android.text.FontConfig.Alias[]); method public int describeContents(); - method public java.util.List<android.text.FontConfig.Alias> getAliases(); - method public java.util.List<android.text.FontConfig.Family> getFamilies(); + method public android.text.FontConfig.Alias[] getAliases(); + method public android.text.FontConfig.Family[] getFamilies(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.text.FontConfig> CREATOR; } @@ -41107,22 +41188,22 @@ package android.text { } public static final class FontConfig.Family implements android.os.Parcelable { - ctor public FontConfig.Family(java.lang.String, java.util.List<android.text.FontConfig.Font>, java.lang.String, java.lang.String); - ctor public FontConfig.Family(android.text.FontConfig.Family); + ctor public FontConfig.Family(java.lang.String, android.text.FontConfig.Font[], java.lang.String, int); method public int describeContents(); - method public java.util.List<android.text.FontConfig.Font> getFonts(); + method public android.text.FontConfig.Font[] getFonts(); method public java.lang.String getLanguage(); method public java.lang.String getName(); - method public java.lang.String getVariant(); + method public int getVariant(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.text.FontConfig.Family> CREATOR; + field public static final int VARIANT_COMPACT = 1; // 0x1 + field public static final int VARIANT_DEFAULT = 0; // 0x0 + field public static final int VARIANT_ELEGANT = 2; // 0x2 } public static final class FontConfig.Font implements android.os.Parcelable { - ctor public FontConfig.Font(java.lang.String, int, java.util.List<android.text.FontConfig.Axis>, int, boolean); - ctor public FontConfig.Font(android.text.FontConfig.Font); method public int describeContents(); - method public java.util.List<android.text.FontConfig.Axis> getAxes(); + method public android.text.FontConfig.Axis[] getAxes(); method public android.os.ParcelFileDescriptor getFd(); method public java.lang.String getFontName(); method public int getTtcIndex(); @@ -45367,8 +45448,8 @@ package android.view { method public void addTouchables(java.util.ArrayList<android.view.View>); method public android.view.ViewPropertyAnimator animate(); method public void announceForAccessibility(java.lang.CharSequence); - method public void autofill(android.view.autofill.AutofillValue); - method public void autofillVirtual(int, android.view.autofill.AutofillValue); + method public boolean autofill(android.view.autofill.AutofillValue); + method public boolean autofillVirtual(int, android.view.autofill.AutofillValue); method protected boolean awakenScrollBars(); method protected boolean awakenScrollBars(int); method protected boolean awakenScrollBars(int, boolean); @@ -45458,7 +45539,7 @@ package android.view { method public float getAlpha(); method public android.view.animation.Animation getAnimation(); method public android.os.IBinder getApplicationWindowToken(); - method public int getAutofillHint(); + method public java.lang.String[] getAutofillHint(); method public int getAutofillMode(); method public int getAutofillType(); method public android.view.autofill.AutofillValue getAutofillValue(); @@ -45782,7 +45863,7 @@ package android.view { method public void setActivated(boolean); method public void setAlpha(float); method public void setAnimation(android.view.animation.Animation); - method public void setAutofillHint(int); + method public void setAutofillHint(java.lang.String...); method public void setAutofillMode(int); method public void setBackground(android.graphics.drawable.Drawable); method public void setBackgroundColor(int); @@ -45925,20 +46006,19 @@ package android.view { field public static final int ACCESSIBILITY_LIVE_REGION_NONE = 0; // 0x0 field public static final int ACCESSIBILITY_LIVE_REGION_POLITE = 1; // 0x1 field public static final android.util.Property<android.view.View, java.lang.Float> ALPHA; - field public static final int AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_DATE = 512; // 0x200 - field public static final int AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_DAY = 4096; // 0x1000 - field public static final int AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_MONTH = 1024; // 0x400 - field public static final int AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_YEAR = 2048; // 0x800 - field public static final int AUTOFILL_HINT_CREDIT_CARD_NUMBER = 128; // 0x80 - field public static final int AUTOFILL_HINT_CREDIT_CARD_SECURITY_CODE = 256; // 0x100 - field public static final int AUTOFILL_HINT_EMAIL_ADDRESS = 1; // 0x1 - field public static final int AUTOFILL_HINT_NAME = 2; // 0x2 - field public static final int AUTOFILL_HINT_NONE = 0; // 0x0 - field public static final int AUTOFILL_HINT_PASSWORD = 8; // 0x8 - field public static final int AUTOFILL_HINT_PHONE = 16; // 0x10 - field public static final int AUTOFILL_HINT_POSTAL_ADDRESS = 32; // 0x20 - field public static final int AUTOFILL_HINT_POSTAL_CODE = 64; // 0x40 - field public static final int AUTOFILL_HINT_USERNAME = 4; // 0x4 + field public static final java.lang.String AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_DATE = "creditCardExpirationDate"; + field public static final java.lang.String AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_DAY = "creditCardExpirationDay"; + field public static final java.lang.String AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_MONTH = "creditCardExpirationMonth"; + field public static final java.lang.String AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_YEAR = "creditCardExpirationYear"; + field public static final java.lang.String AUTOFILL_HINT_CREDIT_CARD_NUMBER = "creditCardNumber"; + field public static final java.lang.String AUTOFILL_HINT_CREDIT_CARD_SECURITY_CODE = "creditCardSecurityCode"; + field public static final java.lang.String AUTOFILL_HINT_EMAIL_ADDRESS = "emailAddress"; + field public static final java.lang.String AUTOFILL_HINT_NAME = "name"; + field public static final java.lang.String AUTOFILL_HINT_PASSWORD = "password"; + field public static final java.lang.String AUTOFILL_HINT_PHONE = "phone"; + field public static final java.lang.String AUTOFILL_HINT_POSTAL_ADDRESS = "postalAddress"; + field public static final java.lang.String AUTOFILL_HINT_POSTAL_CODE = "postalCode"; + field public static final java.lang.String AUTOFILL_HINT_USERNAME = "username"; field public static final int AUTOFILL_MODE_AUTO = 1; // 0x1 field public static final int AUTOFILL_MODE_INHERIT = 0; // 0x0 field public static final int AUTOFILL_MODE_MANUAL = 2; // 0x2 @@ -46604,7 +46684,7 @@ package android.view { method public abstract void setAccessibilityFocused(boolean); method public abstract void setActivated(boolean); method public abstract void setAlpha(float); - method public abstract void setAutofillHint(int); + method public abstract void setAutofillHint(java.lang.String[]); method public abstract void setAutofillOptions(java.lang.String[]); method public abstract void setAutofillType(int); method public abstract void setAutofillValue(android.view.autofill.AutofillValue); @@ -46615,6 +46695,7 @@ package android.view { method public abstract void setClickable(boolean); method public abstract void setContentDescription(java.lang.CharSequence); method public abstract void setContextClickable(boolean); + method public abstract void setDataIsSensitive(boolean); method public abstract void setDimens(int, int, int, int, int, int); method public abstract void setElevation(float); method public abstract void setEnabled(boolean); @@ -46625,7 +46706,6 @@ package android.view { method public abstract void setInputType(int); method public abstract void setLongClickable(boolean); method public abstract void setOpaque(boolean); - method public abstract void setSanitized(boolean); method public abstract void setSelected(boolean); method public abstract void setText(java.lang.CharSequence); method public abstract void setText(java.lang.CharSequence, int, int); @@ -47872,6 +47952,7 @@ package android.view.autofill { public final class AutofillManager { method public void cancel(); method public void commit(); + method public boolean isEnabled(); method public void notifyValueChanged(android.view.View); method public void notifyViewEntered(android.view.View); method public void notifyViewExited(android.view.View); @@ -47879,9 +47960,12 @@ package android.view.autofill { method public void notifyVirtualViewEntered(android.view.View, int, android.graphics.Rect); method public void notifyVirtualViewExited(android.view.View, int); method public void registerCallback(android.view.autofill.AutofillManager.AutofillCallback); + method public void requestAutofill(android.view.View); + method public void requestAutofill(android.view.View, int, android.graphics.Rect); method public void unregisterCallback(android.view.autofill.AutofillManager.AutofillCallback); field public static final java.lang.String EXTRA_ASSIST_STRUCTURE = "android.view.autofill.extra.ASSIST_STRUCTURE"; field public static final java.lang.String EXTRA_AUTHENTICATION_RESULT = "android.view.autofill.extra.AUTHENTICATION_RESULT"; + field public static final int FLAG_MANUAL_REQUEST = 1; // 0x1 } public static abstract class AutofillManager.AutofillCallback { @@ -47890,6 +47974,7 @@ package android.view.autofill { method public void onAutofillEventVirtual(android.view.View, int, int); field public static final int EVENT_INPUT_HIDDEN = 2; // 0x2 field public static final int EVENT_INPUT_SHOWN = 1; // 0x1 + field public static final int EVENT_INPUT_UNAVAILABLE = 3; // 0x3 } public final class AutofillValue implements android.os.Parcelable { @@ -47902,6 +47987,10 @@ package android.view.autofill { method public int getListValue(); method public java.lang.CharSequence getTextValue(); method public boolean getToggleValue(); + method public boolean isDate(); + method public boolean isList(); + method public boolean isText(); + method public boolean isToggle(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.view.autofill.AutofillValue> CREATOR; } diff --git a/cmds/idmap/Android.mk b/cmds/idmap/Android.mk index 50ccb07a3826..aeb8a0c001ec 100644 --- a/cmds/idmap/Android.mk +++ b/cmds/idmap/Android.mk @@ -17,7 +17,7 @@ include $(CLEAR_VARS) LOCAL_SRC_FILES := idmap.cpp create.cpp scan.cpp inspect.cpp -LOCAL_SHARED_LIBRARIES := liblog libutils libandroidfw +LOCAL_SHARED_LIBRARIES := liblog libutils libandroidfw libcutils LOCAL_MODULE := idmap diff --git a/cmds/idmap/idmap.cpp b/cmds/idmap/idmap.cpp index 3ab191553625..3a237ffda775 100644 --- a/cmds/idmap/idmap.cpp +++ b/cmds/idmap/idmap.cpp @@ -49,8 +49,8 @@ OPTIONS \n\ --path: create idmap for target package 'target' (path to apk) and overlay package \n\ 'overlay' (path to apk); write results to 'idmap' (path). \n\ \n\ - --scan: non-recursively search directory 'dir-to-scan' (path) for overlay packages with \n\ - target package 'target-package-name-to-look-for' (package name) present at\n\ + --scan: non-recursively search directory 'dir-to-scan' (path) for static overlay packages \n\ + with target package 'target-package-name-to-look-for' (package name) present at\n\ 'path-to-target-apk' (path to apk). For each overlay package found, create an\n\ idmap file in 'dir-to-hold-idmaps' (path). \n\ \n\ diff --git a/cmds/idmap/scan.cpp b/cmds/idmap/scan.cpp index 8122395d8061..67874a8b9c02 100644 --- a/cmds/idmap/scan.cpp +++ b/cmds/idmap/scan.cpp @@ -9,6 +9,7 @@ #include <androidfw/ResourceTypes.h> #include <androidfw/StreamingZipInflater.h> #include <androidfw/ZipFileRO.h> +#include <cutils/jstring.h> #include <private/android_filesystem_config.h> // for AID_SYSTEM #include <utils/SortedVector.h> #include <utils/String16.h> @@ -81,7 +82,8 @@ namespace { return String8(tmp); } - int parse_overlay_tag(const ResXMLTree& parser, const char *target_package_name) + int parse_overlay_tag(const ResXMLTree& parser, const char *target_package_name, + bool* is_static_overlay) { const size_t N = parser.getAttributeCount(); String16 target; @@ -102,6 +104,11 @@ namespace { return -1; } } + } else if (key == String16("isStatic")) { + Res_value v; + if (parser.getAttributeValue(i, &v) == sizeof(Res_value)) { + *is_static_overlay = (v.data != 0); + } } } if (target == String16(target_package_name)) { @@ -110,6 +117,28 @@ namespace { return NO_OVERLAY_TAG; } + String16 parse_package_name(const ResXMLTree& parser) + { + const size_t N = parser.getAttributeCount(); + String16 package_name; + for (size_t i = 0; i < N; ++i) { + size_t len; + String16 key(parser.getAttributeName(i, &len)); + if (key == String16("package")) { + const char16_t *p = parser.getAttributeStringValue(i, &len); + if (p != NULL) { + package_name = String16(p, len); + } + } + } + return package_name; + } + + bool isValidStaticOverlayPackage(const String16& package_name) { + // TODO(b/35742444): Need to support selection method based on a package name. + return package_name.size() > 0; + } + int parse_manifest(const void *data, size_t size, const char *target_package_name) { ResXMLTree parser; @@ -120,17 +149,26 @@ namespace { } ResXMLParser::event_code_t type; + String16 package_name; + bool is_static_overlay = false; + int priority = NO_OVERLAY_TAG; do { type = parser.next(); if (type == ResXMLParser::START_TAG) { size_t len; String16 tag(parser.getElementName(&len)); - if (tag == String16("overlay")) { - return parse_overlay_tag(parser, target_package_name); + if (tag == String16("manifest")) { + package_name = parse_package_name(parser); + } else if (tag == String16("overlay")) { + priority = parse_overlay_tag(parser, target_package_name, &is_static_overlay); + break; } } } while (type != ResXMLParser::BAD_DOCUMENT && type != ResXMLParser::END_DOCUMENT); + if (is_static_overlay && isValidStaticOverlayPackage(package_name)) { + return priority; + } return NO_OVERLAY_TAG; } diff --git a/core/java/android/accounts/AccountManager.java b/core/java/android/accounts/AccountManager.java index e5df278f2cc1..75d4f32055e2 100644 --- a/core/java/android/accounts/AccountManager.java +++ b/core/java/android/accounts/AccountManager.java @@ -21,8 +21,11 @@ import static android.Manifest.permission.GET_ACCOUNTS; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.RequiresPermission; +import android.annotation.SdkConstant; import android.annotation.Size; import android.annotation.SystemApi; +import android.annotation.SdkConstant.SdkConstantType; +import android.annotation.BroadcastBehavior; import android.app.Activity; import android.content.BroadcastReceiver; import android.content.ComponentName; @@ -335,6 +338,8 @@ public class AccountManager { * * @deprecated use #addOnAccountsUpdatedListener to get account updates in runtime. */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + @BroadcastBehavior(includeBackground = true) public static final String LOGIN_ACCOUNTS_CHANGED_ACTION = "android.accounts.LOGIN_ACCOUNTS_CHANGED"; diff --git a/core/java/android/animation/AnimatorSet.java b/core/java/android/animation/AnimatorSet.java index 86adbb002dc4..5c7a12cf5eb9 100644 --- a/core/java/android/animation/AnimatorSet.java +++ b/core/java/android/animation/AnimatorSet.java @@ -431,31 +431,28 @@ public final class AnimatorSet extends Animator implements AnimationHandler.Anim // Force all the animations to end when the duration scale is 0. private void forceToEnd() { - if (mEndCanBeCalled) { - end(); + // TODO: Below is commented out to temp work around b/36241584, uncomment this when it's + // fixed. +// if (mEndCanBeCalled) { +// end(); +// return; +// } + + // Note: we don't want to combine this case with the end() method below because in + // the case of developer calling end(), we still need to make sure end() is explicitly + // called on the child animators to maintain the old behavior. + if (mReversing) { + handleAnimationEvents(mLastEventId, 0, getTotalDuration()); } else { - // Note: we don't want to combine this case with the end() method below because in - // the case of developer calling end(), we still need to make sure end() is explicitly - // called on the child animators to maintain the old behavior. - if (mReversing) { - mLastEventId = mLastEventId == -1 ? mEvents.size() : mLastEventId; - for (int j = mLastEventId - 1; j >= 0; j--) { - AnimationEvent event = mEvents.get(j); - if (event.mEvent == AnimationEvent.ANIMATION_END) { - event.mNode.mAnimation.reverse(); - } - } - } else { - for (int j = mLastEventId + 1; j < mEvents.size(); j++) { - AnimationEvent event = mEvents.get(j); - if (event.mEvent == AnimationEvent.ANIMATION_START) { - event.mNode.mAnimation.start(); - } - } + long zeroScalePlayTime = getTotalDuration(); + if (zeroScalePlayTime == DURATION_INFINITE) { + // Use a large number for the play time. + zeroScalePlayTime = Integer.MAX_VALUE; } - mPlayingSet.clear(); - endAnimation(); + handleAnimationEvents(mLastEventId, mEvents.size() - 1, zeroScalePlayTime); } + mPlayingSet.clear(); + endAnimation(); } /** @@ -730,7 +727,7 @@ public final class AnimatorSet extends Animator implements AnimationHandler.Anim if (isEmptySet) { // In the case of empty AnimatorSet, or 0 duration scale, we will trigger the // onAnimationEnd() right away. - forceToEnd(); + end(); } } @@ -1130,8 +1127,10 @@ public final class AnimatorSet extends Animator implements AnimationHandler.Anim */ private void pulseFrame(Node node, long animPlayTime) { if (!node.mEnded) { + float durationScale = ValueAnimator.getDurationScale(); + durationScale = durationScale == 0 ? 1 : durationScale; node.mEnded = node.mAnimation.pulseAnimationFrame( - (long) (animPlayTime * ValueAnimator.getDurationScale())); + (long) (animPlayTime * durationScale)); } } diff --git a/core/java/android/annotation/BroadcastBehavior.java b/core/java/android/annotation/BroadcastBehavior.java new file mode 100644 index 000000000000..9b2ca3120290 --- /dev/null +++ b/core/java/android/annotation/BroadcastBehavior.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.annotation; + +import android.content.Intent; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Description of how the annotated broadcast action behaves. + * + * @hide + */ +@Target({ ElementType.FIELD }) +@Retention(RetentionPolicy.SOURCE) +public @interface BroadcastBehavior { + /** + * This broadcast will only be delivered to an explicit target. + * + * @see Intent#setPackage(String) + * @see Intent#setComponent(android.content.ComponentName) + */ + boolean explicitOnly() default false; + + /** + * This broadcast will only be delivered to registered receivers. + * + * @see Intent#FLAG_RECEIVER_REGISTERED_ONLY + */ + boolean registeredOnly() default false; + + /** + * This broadcast will include all {@code AndroidManifest.xml} receivers + * regardless of process state. + * + * @see Intent#FLAG_RECEIVER_INCLUDE_BACKGROUND + */ + boolean includeBackground() default false; +} diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index 78c29e840b9a..e65c7c815960 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -16,6 +16,7 @@ package android.app; +import android.metrics.LogMaker; import android.view.autofill.AutofillId; import android.view.autofill.AutofillManager; import android.view.autofill.AutofillValue; @@ -23,6 +24,8 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.app.IVoiceInteractor; import com.android.internal.app.ToolbarActionBar; import com.android.internal.app.WindowDecorActionBar; +import com.android.internal.logging.MetricsLogger; +import com.android.internal.logging.nano.MetricsProto; import com.android.internal.policy.PhoneWindow; import android.annotation.CallSuper; @@ -765,6 +768,7 @@ public class Activity extends ContextThemeWrapper /*package*/ Configuration mCurrentConfig; private SearchManager mSearchManager; private MenuInflater mMenuInflater; + private final MetricsLogger mMetricsLogger = new MetricsLogger(); static final class NonConfigurationInstances { Object activity; @@ -7188,6 +7192,8 @@ public class Activity extends ContextThemeWrapper public void autofill(List<AutofillId> ids, List<AutofillValue> values) { final View root = getWindow().getDecorView(); final int itemCount = ids.size(); + int numApplied = 0; + for (int i = 0; i < itemCount; i++) { final AutofillId id = ids.get(i); final AutofillValue value = values.get(i); @@ -7197,12 +7203,22 @@ public class Activity extends ContextThemeWrapper Log.w(TAG, "autofill(): no View with id " + viewId); continue; } + final boolean wasApplied; if (id.isVirtual()) { - view.autofillVirtual(id.getVirtualChildId(), value); + wasApplied = view.autofillVirtual(id.getVirtualChildId(), value); } else { - view.autofill(value); + wasApplied = view.autofill(value); + } + + if (wasApplied) { + numApplied++; } } + + LogMaker log = new LogMaker(MetricsProto.MetricsEvent.AUTOFILL_DATASET_APPLIED); + log.addTaggedData(MetricsProto.MetricsEvent.FIELD_AUTOFILL_NUM_VALUES, itemCount); + log.addTaggedData(MetricsProto.MetricsEvent.FIELD_AUTOFILL_NUM_VIEWS_FILLED, numApplied); + mMetricsLogger.write(log); } /** @hide */ diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 44cc5b408f50..6b53cd841dff 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -50,6 +50,7 @@ import android.database.sqlite.SQLiteDebug; import android.database.sqlite.SQLiteDebug.DbStats; import android.graphics.Bitmap; import android.graphics.Canvas; +import android.graphics.Typeface; import android.hardware.display.DisplayManagerGlobal; import android.net.ConnectivityManager; import android.net.IConnectivityManager; @@ -148,6 +149,7 @@ import libcore.io.DropBox; import libcore.io.EventLogger; import libcore.io.IoUtils; import libcore.net.event.NetworkEventDispatcher; +import dalvik.system.BaseDexClassLoader; import dalvik.system.CloseGuard; import dalvik.system.VMDebug; import dalvik.system.VMRuntime; @@ -335,6 +337,8 @@ public final class ActivityThread { Configuration overrideConfig; // Used for consolidating configs before sending on to Activity. private Configuration tmpConfig = new Configuration(); + // Callback used for updating activity override config. + ViewRootImpl.ActivityConfigCallback configCallback; ActivityClientRecord nextIdle; ProfilerInfo profilerInfo; @@ -370,6 +374,14 @@ public final class ActivityThread { stopped = false; hideForNow = false; nextIdle = null; + configCallback = (Configuration overrideConfig, int newDisplayId) -> { + if (activity == null) { + throw new IllegalStateException( + "Received config update for non-existing activity"); + } + activity.mMainThread.handleActivityConfigurationChanged( + new ActivityConfigChangeData(token, overrideConfig), newDisplayId); + }; } public boolean isPreHoneycomb() { @@ -3679,6 +3691,12 @@ public final class ActivityThread { if (r.activity.mVisibleFromClient) { r.activity.makeVisible(); } + final ViewRootImpl viewRoot = r.activity.mDecor.getViewRootImpl(); + if (viewRoot != null) { + // TODO: Figure out the best place to set the callback. + // This looks like a place where decor view is already initialized. + viewRoot.setActivityConfigCallback(r.configCallback); + } } if (!r.onlyLocalRequest) { @@ -5027,7 +5045,7 @@ public final class ActivityThread { * @param displayId Id of the display where activity was moved to, -1 if there was no move and * value didn't change. */ - private void handleActivityConfigurationChanged(ActivityConfigChangeData data, int displayId) { + void handleActivityConfigurationChanged(ActivityConfigChangeData data, int displayId) { ActivityClientRecord r = mActivities.get(data.activityToken); // Check input params. if (r == null || r.activity == null) { @@ -5044,6 +5062,7 @@ public final class ActivityThread { // Perform updates. r.overrideConfig = data.overrideConfig; + final ViewRootImpl viewRoot = r.activity.mDecor.getViewRootImpl(); if (movedToDifferentDisplay) { if (DEBUG_CONFIGURATION) Slog.v(TAG, "Handle activity moved to display, activity:" + r.activityInfo.name + ", displayId=" + displayId @@ -5051,13 +5070,15 @@ public final class ActivityThread { performConfigurationChangedForActivity(r, mCompatConfiguration, displayId, true /* movedToDifferentDisplay */); - final ViewRootImpl viewRoot = r.activity.mDecor.getViewRootImpl(); viewRoot.onMovedToDisplay(displayId); } else { if (DEBUG_CONFIGURATION) Slog.v(TAG, "Handle activity config changed: " + r.activityInfo.name + ", config=" + data.overrideConfig); performConfigurationChangedForActivity(r, mCompatConfiguration); } + // Notify the ViewRootImpl instance about configuration changes. It may have initiated this + // update to make sure that resources are updated before updating itself. + viewRoot.updateConfiguration(); mSomeActivitiesChanged = true; } @@ -5570,6 +5591,16 @@ public final class ActivityThread { } } + // If we use profiles, setup the dex reporter to notify package manager + // of any relevant dex loads. The idle maintenance job will use the information + // reported to optimize the loaded dex files. + // Note that we only need one global reporter per app. + // Make sure we do this before calling onCreate so that we can capture the + // complete application startup. + if (SystemProperties.getBoolean("dalvik.vm.usejitprofiles", false)) { + BaseDexClassLoader.setReporter(DexLoadReporter.getInstance()); + } + // Install the Network Security Config Provider. This must happen before the application // code is loaded to prevent issues with instances of TLS objects being created before // the provider is installed. @@ -5665,6 +5696,7 @@ public final class ActivityThread { } // Preload fonts resources + Typeface.setApplicationContext(appContext); try { final ApplicationInfo info = getPackageManager().getApplicationInfo( @@ -6282,35 +6314,26 @@ public final class ActivityThread { // add dropbox logging to libcore DropBox.setReporter(new DropBoxReporter()); - ViewRootImpl.addConfigCallback(new ComponentCallbacks2() { - @Override - public void onConfigurationChanged(Configuration newConfig) { - synchronized (mResourcesManager) { - // We need to apply this change to the resources - // immediately, because upon returning the view - // hierarchy will be informed about it. - if (mResourcesManager.applyConfigurationToResourcesLocked(newConfig, null)) { - updateLocaleListFromAppContext(mInitialApplication.getApplicationContext(), - mResourcesManager.getConfiguration().getLocales()); - - // This actually changed the resources! Tell - // everyone about it. - if (mPendingConfiguration == null || - mPendingConfiguration.isOtherSeqNewer(newConfig)) { - mPendingConfiguration = newConfig; - - sendMessage(H.CONFIGURATION_CHANGED, newConfig); - } + ViewRootImpl.ConfigChangedCallback configChangedCallback + = (Configuration globalConfig) -> { + synchronized (mResourcesManager) { + // We need to apply this change to the resources immediately, because upon returning + // the view hierarchy will be informed about it. + if (mResourcesManager.applyConfigurationToResourcesLocked(globalConfig, + null /* compat */)) { + updateLocaleListFromAppContext(mInitialApplication.getApplicationContext(), + mResourcesManager.getConfiguration().getLocales()); + + // This actually changed the resources! Tell everyone about it. + if (mPendingConfiguration == null + || mPendingConfiguration.isOtherSeqNewer(globalConfig)) { + mPendingConfiguration = globalConfig; + sendMessage(H.CONFIGURATION_CHANGED, globalConfig); } } } - @Override - public void onLowMemory() { - } - @Override - public void onTrimMemory(int level) { - } - }); + }; + ViewRootImpl.addConfigCallback(configChangedCallback); } public static ActivityThread systemMain() { diff --git a/core/java/android/app/ActivityTransitionCoordinator.java b/core/java/android/app/ActivityTransitionCoordinator.java index a5123504a4a1..21a7ca733eb9 100644 --- a/core/java/android/app/ActivityTransitionCoordinator.java +++ b/core/java/android/app/ActivityTransitionCoordinator.java @@ -28,6 +28,7 @@ import android.transition.TransitionListenerAdapter; import android.transition.TransitionSet; import android.transition.Visibility; import android.util.ArrayMap; +import android.util.ArraySet; import android.view.GhostView; import android.view.View; import android.view.ViewGroup; @@ -394,6 +395,60 @@ abstract class ActivityTransitionCoordinator extends ResultReceiver { return transition; } + /** + * Looks through the transition to see which Views have been included and which have been + * excluded. {@code views} will be modified to contain only those Views that are included + * in the transition. If {@code transition} is a TransitionSet, it will search through all + * contained Transitions to find targeted Views. + * + * @param transition The transition to look through for inclusion of Views + * @param views The list of Views that are to be checked for inclusion. Will be modified + * to remove all excluded Views, possibly leaving an empty list. + */ + protected static void removeExcludedViews(Transition transition, ArrayList<View> views) { + ArraySet<View> included = new ArraySet<>(); + findIncludedViews(transition, views, included); + views.clear(); + views.addAll(included); + } + + /** + * Looks through the transition to see which Views have been included. Only {@code views} + * will be examined for inclusion. If {@code transition} is a TransitionSet, it will search + * through all contained Transitions to find targeted Views. + * + * @param transition The transition to look through for inclusion of Views + * @param views The list of Views that are to be checked for inclusion. + * @param included Modified to contain all Views in views that have at least one Transition + * that affects it. + */ + private static void findIncludedViews(Transition transition, ArrayList<View> views, + ArraySet<View> included) { + if (transition instanceof TransitionSet) { + TransitionSet set = (TransitionSet) transition; + ArrayList<View> includedViews = new ArrayList<>(); + final int numViews = views.size(); + for (int i = 0; i < numViews; i++) { + final View view = views.get(i); + if (transition.isValidTarget(view)) { + includedViews.add(view); + } + } + final int count = set.getTransitionCount(); + for (int i = 0; i < count; i++) { + findIncludedViews(set.getTransitionAt(i), includedViews, included); + } + } else { + final int numViews = views.size(); + for (int i = 0; i < numViews; i++) { + final View view = views.get(i); + if (transition.isValidTarget(view)) { + included.add(view); + } + } + } + } + protected static Transition mergeTransitions(Transition transition1, Transition transition2) { if (transition1 == null) { return transition2; diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index 0f2ce3c09f00..09e7595242af 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -362,6 +362,8 @@ public class AppOpsManager { public static final String OPSTR_ANSWER_PHONE_CALLS = "android:answer_phone_calls"; + // Warning: If an permission is added here it also has to be added to + // com.android.packageinstaller.permission.utils.EventLogger private static final int[] RUNTIME_AND_APPOP_PERMISSIONS_OPS = { // RUNTIME PERMISSIONS // Contacts diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java index 97992cafee0e..55407e6d7365 100644 --- a/core/java/android/app/ApplicationPackageManager.java +++ b/core/java/android/app/ApplicationPackageManager.java @@ -1814,6 +1814,15 @@ public class ApplicationPackageManager extends PackageManager { } @Override + public void setUpdateAvailable(String packageName, boolean updateAvailable) { + try { + mPM.setUpdateAvailable(packageName, updateAvailable); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + @Override public String getInstallerPackageName(String packageName) { try { return mPM.getInstallerPackageName(packageName); diff --git a/core/java/android/app/DexLoadReporter.java b/core/java/android/app/DexLoadReporter.java new file mode 100644 index 000000000000..13f288ab7454 --- /dev/null +++ b/core/java/android/app/DexLoadReporter.java @@ -0,0 +1,168 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app; + +import android.os.FileUtils; +import android.os.RemoteException; +import android.os.SystemProperties; +import android.util.Slog; + +import com.android.internal.annotations.GuardedBy; + +import dalvik.system.BaseDexClassLoader; +import dalvik.system.VMRuntime; + +import java.io.File; +import java.io.IOException; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * A dex load reporter which will notify package manager of any dex file loaded + * with {@code BaseDexClassLoader}. + * The goals are: + * 1) discover secondary dex files so that they can be optimized during the + * idle maintenance job. + * 2) determine whether or not a dex file is used by an app which does not + * own it (in order to select the optimal compilation method). + * @hide + */ +/*package*/ class DexLoadReporter implements BaseDexClassLoader.Reporter { + private static final String TAG = "DexLoadReporter"; + + private static final DexLoadReporter INSTANCE = new DexLoadReporter(); + + private static final boolean DEBUG = false; + + // We must guard the access to the list of data directories because + // we might have concurrent accesses. Apps might load dex files while + // new data dirs are registered (due to creation of LoadedApks via + // create createApplicationContext). + @GuardedBy("mDataDirs") + private final Set<String> mDataDirs; + + private DexLoadReporter() { + mDataDirs = new HashSet<>(); + } + + /*package*/ static DexLoadReporter getInstance() { + return INSTANCE; + } + + /** + * Register an application data directory with the reporter. + * The data directories are used to determine if a dex file is secondary dex or not. + * Note that this method may be called multiple times for the same app, registering + * different data directories. This may happen when apps share the same user id + * ({@code android:sharedUserId}). For example, if app1 and app2 share the same user + * id, and app1 loads app2 apk, then both data directories will be registered. + */ + /*package*/ void registerAppDataDir(String packageName, String dataDir) { + if (DEBUG) { + Slog.i(TAG, "Package " + packageName + " registering data dir: " + dataDir); + } + // TODO(calin): A few code paths imply that the data dir + // might be null. Investigate when that can happen. + if (dataDir != null) { + synchronized (mDataDirs) { + mDataDirs.add(dataDir); + } + } + } + + @Override + public void report(List<String> dexPaths) { + if (dexPaths.isEmpty()) { + return; + } + // Notify the package manager about the dex loads unconditionally. + // The load might be for either a primary or secondary dex file. + notifyPackageManager(dexPaths); + // Check for secondary dex files and register them for profiling if + // possible. + registerSecondaryDexForProfiling(dexPaths); + } + + private void notifyPackageManager(List<String> dexPaths) { + String packageName = ActivityThread.currentPackageName(); + try { + ActivityThread.getPackageManager().notifyDexLoad( + packageName, dexPaths, VMRuntime.getRuntime().vmInstructionSet()); + } catch (RemoteException re) { + Slog.e(TAG, "Failed to notify PM about dex load for package " + packageName, re); + } + } + + private void registerSecondaryDexForProfiling(List<String> dexPaths) { + if (!SystemProperties.getBoolean("dalvik.vm.dexopt.secondary", false)) { + return; + } + // Make a copy of the current data directories so that we don't keep the lock + // while registering for profiling. The registration will perform I/O to + // check for or create the profile. + String[] dataDirs; + synchronized (mDataDirs) { + dataDirs = mDataDirs.toArray(new String[0]); + } + for (String dexPath : dexPaths) { + registerSecondaryDexForProfiling(dexPath, dataDirs); + } + } + + private void registerSecondaryDexForProfiling(String dexPath, String[] dataDirs) { + if (!isSecondaryDexFile(dexPath, dataDirs)) { + // The dex path is not a secondary dex file. Nothing to do. + return; + } + File secondaryProfile = getSecondaryProfileFile(dexPath); + try { + // Create the profile if not already there. + // Returns true if the file was created, false if the file already exists. + // or throws exceptions in case of errors. + boolean created = secondaryProfile.createNewFile(); + if (DEBUG && created) { + Slog.i(TAG, "Created profile for secondary dex: " + secondaryProfile); + } + } catch (IOException ex) { + Slog.e(TAG, "Failed to create profile for secondary dex " + secondaryProfile + + ":" + ex.getMessage()); + // Don't move forward with the registration if we failed to create the profile. + return; + } + + VMRuntime.registerAppInfo(secondaryProfile.getPath(), new String[] { dexPath }); + } + + // A dex file is a secondary dex file if it is in any of the registered app + // data directories. + private boolean isSecondaryDexFile(String dexPath, String[] dataDirs) { + for (String dataDir : dataDirs) { + if (FileUtils.contains(dataDir, dexPath)) { + return true; + } + } + return false; + } + + // Secondary dex profiles are stored next to the dex file and have the same + // name with '.prof' appended. + // NOTE: Keep in sync with installd. + private File getSecondaryProfileFile(String dexPath) { + return new File(dexPath + ".prof"); + } +} diff --git a/core/java/android/app/EnterTransitionCoordinator.java b/core/java/android/app/EnterTransitionCoordinator.java index 445b6871ddac..ab847fd562a4 100644 --- a/core/java/android/app/EnterTransitionCoordinator.java +++ b/core/java/android/app/EnterTransitionCoordinator.java @@ -132,7 +132,9 @@ class EnterTransitionCoordinator extends ActivityTransitionCoordinator { super.viewsReady(sharedElements); mIsReadyForTransition = true; hideViews(mSharedElements); - if (getViewsTransition() != null && mTransitioningViews != null) { + Transition viewsTransition = getViewsTransition(); + if (viewsTransition != null && mTransitioningViews != null) { + removeExcludedViews(viewsTransition, mTransitioningViews); stripOffscreenViews(); hideViews(mTransitioningViews); } diff --git a/core/java/android/app/ExitTransitionCoordinator.java b/core/java/android/app/ExitTransitionCoordinator.java index 29e10d81c95d..df31da9183f1 100644 --- a/core/java/android/app/ExitTransitionCoordinator.java +++ b/core/java/android/app/ExitTransitionCoordinator.java @@ -321,6 +321,10 @@ class ExitTransitionCoordinator extends ActivityTransitionCoordinator { Transition viewsTransition = null; if (mTransitioningViews != null && !mTransitioningViews.isEmpty()) { viewsTransition = configureTransition(getViewsTransition(), true); + removeExcludedViews(viewsTransition, mTransitioningViews); + if (mTransitioningViews.isEmpty()) { + viewsTransition = null; + } } if (viewsTransition == null) { viewsTransitionComplete(); diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl index 5ab767ba9676..5ea24804e0fd 100644 --- a/core/java/android/app/INotificationManager.aidl +++ b/core/java/android/app/INotificationManager.aidl @@ -66,6 +66,9 @@ interface INotificationManager ParceledListSlice getNotificationChannels(String pkg); ParceledListSlice getNotificationChannelsForPackage(String pkg, int uid, boolean includeDeleted); int getNumNotificationChannelsForPackage(String pkg, int uid, boolean includeDeleted); + int getDeletedChannelCount(String pkg, int uid); + void deleteNotificationChannelGroup(String pkg, String channelGroupId); + ParceledListSlice getNotificationChannelGroups(String pkg); // TODO: Remove this when callers have been migrated to the equivalent // INotificationListener method. diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java index be38f42490e5..cf41e4e5dc9c 100644 --- a/core/java/android/app/LoadedApk.java +++ b/core/java/android/app/LoadedApk.java @@ -55,7 +55,6 @@ import android.view.DisplayAdjustments; import com.android.internal.util.ArrayUtils; -import dalvik.system.BaseDexClassLoader; import dalvik.system.VMRuntime; import java.io.File; @@ -597,8 +596,7 @@ public final class LoadedApk { // Avoid the binder call when the package is the current application package. // The activity manager will perform ensure that dexopt is performed before // spinning up the process. - if (!Objects.equals(mPackageName, ActivityThread.currentPackageName())) { - VMRuntime.getRuntime().vmInstructionSet(); + if (!Objects.equals(mPackageName, ActivityThread.currentPackageName()) && mIncludeCode) { try { ActivityThread.getPackageManager().notifyPackageUse(mPackageName, PackageManager.NOTIFY_PACKAGE_USE_CROSS_PACKAGE); @@ -753,39 +751,10 @@ public final class LoadedApk { VMRuntime.registerAppInfo(profileFile.getPath(), codePaths.toArray(new String[codePaths.size()])); - // Setup the reporter to notify package manager of any relevant dex loads. - // At this point the primary apk is loaded and will not be reported. - // Anything loaded from now on will be tracked as a potential secondary - // or foreign dex file. The goal is to enable: - // 1) monitoring and compilation of secondary dex file - // 2) track whether or not a dex file is used by other apps (used to - // determined the compilation filter of apks). - if (BaseDexClassLoader.getReporter() != DexLoadReporter.INSTANCE) { - // Set the dex load reporter if not already set. - // Note that during the app's life cycle different LoadedApks may be - // created and loaded (e.g. if two different apps share the same runtime). - BaseDexClassLoader.setReporter(DexLoadReporter.INSTANCE); - } - } - - private static class DexLoadReporter implements BaseDexClassLoader.Reporter { - private static final DexLoadReporter INSTANCE = new DexLoadReporter(); - - private DexLoadReporter() {} - - @Override - public void report(List<String> dexPaths) { - if (dexPaths.isEmpty()) { - return; - } - String packageName = ActivityThread.currentPackageName(); - try { - ActivityThread.getPackageManager().notifyDexLoad( - packageName, dexPaths, VMRuntime.getRuntime().vmInstructionSet()); - } catch (RemoteException re) { - Slog.e(TAG, "Failed to notify PM about dex load for package " + packageName, re); - } - } + // Register the app data directory with the reporter. It will + // help deciding whether or not a dex file is the primary apk or a + // secondary dex. + DexLoadReporter.getInstance().registerAppDataDir(mPackageName, mDataDir); } /** diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index a098591c8f44..870c41238710 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -1935,7 +1935,9 @@ public class Notification implements Parcelable if (this.actions != null) { that.actions = new Action[this.actions.length]; for(int i=0; i<this.actions.length; i++) { - that.actions[i] = this.actions[i].clone(); + if ( this.actions[i] != null) { + that.actions[i] = this.actions[i].clone(); + } } } @@ -3432,7 +3434,9 @@ public class Notification implements Parcelable * @param action The action to add. */ public Builder addAction(Action action) { - mActions.add(action); + if (action != null) { + mActions.add(action); + } return this; } @@ -3446,7 +3450,9 @@ public class Notification implements Parcelable public Builder setActions(Action... actions) { mActions.clear(); for (int i = 0; i < actions.length; i++) { - mActions.add(actions[i]); + if (actions[i] != null) { + mActions.add(actions[i]); + } } return this; } diff --git a/core/java/android/app/NotificationChannel.java b/core/java/android/app/NotificationChannel.java index 16c85f587956..29c4520ffdcb 100644 --- a/core/java/android/app/NotificationChannel.java +++ b/core/java/android/app/NotificationChannel.java @@ -24,6 +24,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.StringRes; import android.annotation.SystemApi; +import android.content.Intent; import android.media.AudioAttributes; import android.net.Uri; import android.os.Parcel; @@ -47,7 +48,6 @@ public final class NotificationChannel implements Parcelable { private static final String TAG_CHANNEL = "channel"; private static final String ATT_NAME = "name"; - private static final String ATT_NAME_RES_ID = "name_res_id"; private static final String ATT_ID = "id"; private static final String ATT_DELETED = "deleted"; private static final String ATT_PRIORITY = "priority"; @@ -141,7 +141,6 @@ public final class NotificationChannel implements Parcelable { private final String mId; private CharSequence mName; - private int mNameResId = 0; private int mImportance = DEFAULT_IMPORTANCE; private boolean mBypassDnd; private int mLockscreenVisibility = DEFAULT_VISIBILITY; @@ -162,7 +161,9 @@ public final class NotificationChannel implements Parcelable { * @param id The id of the channel. Must be unique per package. * @param name The user visible name of the channel. Unchangeable once created; use this * constructor if the channel represents a user-defined category that does not - * need to be translated. + * need to be translated. You can rename this channel when the system + * locale changes by listening for the {@link Intent#ACTION_LOCALE_CHANGED} + * broadcast. * @param importance The importance of the channel. This controls how interruptive notifications * posted to this channel are. See e.g. * {@link NotificationManager#IMPORTANCE_DEFAULT}. @@ -173,21 +174,6 @@ public final class NotificationChannel implements Parcelable { this.mImportance = importance; } - /** - * Creates a notification channel. - * - * @param id The id of the channel. Must be unique per package. - * @param nameResId The resource id of the string containing the channel name. - * @param importance The importance of the channel. This controls how interruptive notifications - * posted to this channel are. See e.g. - * {@link NotificationManager#IMPORTANCE_DEFAULT}. - */ - public NotificationChannel(String id, @StringRes int nameResId, int importance) { - this.mId = id; - this.mNameResId = nameResId; - this.mImportance = importance; - } - protected NotificationChannel(Parcel in) { if (in.readByte() != 0) { mId = in.readString(); @@ -195,7 +181,6 @@ public final class NotificationChannel implements Parcelable { mId = null; } mName = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); - mNameResId = in.readInt(); mImportance = in.readInt(); mBypassDnd = in.readByte() != 0; mLockscreenVisibility = in.readInt(); @@ -228,7 +213,6 @@ public final class NotificationChannel implements Parcelable { dest.writeByte((byte) 0); } TextUtils.writeToParcel(mName, dest, flags); - dest.writeInt(mNameResId); dest.writeInt(mImportance); dest.writeByte(mBypassDnd ? (byte) 1 : (byte) 0); dest.writeInt(mLockscreenVisibility); @@ -262,7 +246,6 @@ public final class NotificationChannel implements Parcelable { /** * @hide */ - @SystemApi public void lockFields(int field) { mUserLockedFields |= field; } @@ -270,16 +253,15 @@ public final class NotificationChannel implements Parcelable { /** * @hide */ - @SystemApi public void setDeleted(boolean deleted) { mDeleted = deleted; } /** - * @hide + * Sets the name of this channel. */ - public void setNameResId(@StringRes int nameResId) { - this.mNameResId = nameResId; + public void setName(CharSequence name) { + mName = name; } // Modifiable by a notification ranker. @@ -417,13 +399,6 @@ public final class NotificationChannel implements Parcelable { } /** - * Returns the resource id of the user visible name of this channel. - */ - public int getNameResId() { - return mNameResId; - } - - /** * Returns the user specified importance {e.g. @link NotificationManager#IMPORTANCE_LOW} for * notifications posted to this channel. */ @@ -556,7 +531,6 @@ public final class NotificationChannel implements Parcelable { if (getName() != null) { out.attribute(null, ATT_NAME, getName().toString()); } - out.attribute(null, ATT_NAME_RES_ID, Integer.toString(getNameResId())); if (getImportance() != DEFAULT_IMPORTANCE) { out.attribute( null, ATT_IMPORTANCE, Integer.toString(getImportance())); @@ -614,7 +588,6 @@ public final class NotificationChannel implements Parcelable { JSONObject record = new JSONObject(); record.put(ATT_ID, getId()); record.put(ATT_NAME, getName()); - record.put(ATT_NAME_RES_ID, getNameResId()); if (getImportance() != DEFAULT_IMPORTANCE) { record.put(ATT_IMPORTANCE, NotificationListenerService.Ranking.importanceToString(getImportance())); @@ -732,7 +705,6 @@ public final class NotificationChannel implements Parcelable { NotificationChannel that = (NotificationChannel) o; - if (getNameResId() != that.getNameResId()) return false; if (getImportance() != that.getImportance()) return false; if (mBypassDnd != that.mBypassDnd) return false; if (getLockscreenVisibility() != that.getLockscreenVisibility()) return false; @@ -762,7 +734,6 @@ public final class NotificationChannel implements Parcelable { public int hashCode() { int result = getId() != null ? getId().hashCode() : 0; result = 31 * result + (getName() != null ? getName().hashCode() : 0); - result = 31 * result + getNameResId(); result = 31 * result + getImportance(); result = 31 * result + (mBypassDnd ? 1 : 0); result = 31 * result + getLockscreenVisibility(); @@ -784,7 +755,6 @@ public final class NotificationChannel implements Parcelable { return "NotificationChannel{" + "mId='" + mId + '\'' + ", mName=" + mName + - ", mNameResId=" + mNameResId + ", mImportance=" + mImportance + ", mBypassDnd=" + mBypassDnd + ", mLockscreenVisibility=" + mLockscreenVisibility + diff --git a/core/java/android/app/NotificationChannelGroup.java b/core/java/android/app/NotificationChannelGroup.java index 288d39a706f9..2b0cd0442a0a 100644 --- a/core/java/android/app/NotificationChannelGroup.java +++ b/core/java/android/app/NotificationChannelGroup.java @@ -17,6 +17,7 @@ package android.app; import android.annotation.StringRes; import android.annotation.SystemApi; +import android.content.Intent; import android.net.Uri; import android.os.Parcel; import android.os.Parcelable; @@ -41,38 +42,25 @@ public final class NotificationChannelGroup implements Parcelable { private static final String TAG_GROUP = "channelGroup"; private static final String ATT_NAME = "name"; - private static final String ATT_NAME_RES_ID = "name_res_id"; private static final String ATT_ID = "id"; private final String mId; private CharSequence mName; - private int mNameResId = 0; private List<NotificationChannel> mChannels = new ArrayList<>(); /** - * Creates a notification channel. + * Creates a notification channel group. * * @param id The id of the group. Must be unique per package. - * @param name The user visible name of the group. Unchangeable once created; use this - * constructor if the group represents something user-defined that does not - * need to be translated. + * @param name The user visible name of the group. You can rename this group when the system + * locale changes by listening for the {@link Intent#ACTION_LOCALE_CHANGED} + * broadcast. */ public NotificationChannelGroup(String id, CharSequence name) { this.mId = id; this.mName = name; } - /** - * Creates a notification channel. - * - * @param id The id of the group. Must be unique per package. - * @param nameResId String resource id of the user visible name of the group. - */ - public NotificationChannelGroup(String id, @StringRes int nameResId) { - this.mId = id; - this.mNameResId = nameResId; - } - protected NotificationChannelGroup(Parcel in) { if (in.readByte() != 0) { mId = in.readString(); @@ -80,7 +68,6 @@ public final class NotificationChannelGroup implements Parcelable { mId = null; } mName = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); - mNameResId = in.readInt(); in.readParcelableList(mChannels, NotificationChannel.class.getClassLoader()); } @@ -93,7 +80,6 @@ public final class NotificationChannelGroup implements Parcelable { dest.writeByte((byte) 0); } TextUtils.writeToParcel(mName, dest, flags); - dest.writeInt(mNameResId); dest.writeParcelableList(mChannels, flags); } @@ -111,19 +97,11 @@ public final class NotificationChannelGroup implements Parcelable { return mName; } - /** - * Returns the resource id of the user visible name of this group. - */ - public @StringRes int getNameResId() { - return mNameResId; - } - /* * Returns the list of channels that belong to this group * * @hide */ - @SystemApi public List<NotificationChannel> getChannels() { return mChannels; } @@ -131,7 +109,6 @@ public final class NotificationChannelGroup implements Parcelable { /** * @hide */ - @SystemApi public void addChannel(NotificationChannel channel) { mChannels.add(channel); } @@ -139,7 +116,6 @@ public final class NotificationChannelGroup implements Parcelable { /** * @hide */ - @SystemApi public void writeXml(XmlSerializer out) throws IOException { out.startTag(null, TAG_GROUP); @@ -147,9 +123,6 @@ public final class NotificationChannelGroup implements Parcelable { if (getName() != null) { out.attribute(null, ATT_NAME, getName().toString()); } - if (getNameResId() != 0) { - out.attribute(null, ATT_NAME_RES_ID, Integer.toString(getNameResId())); - } out.endTag(null, TAG_GROUP); } @@ -162,7 +135,6 @@ public final class NotificationChannelGroup implements Parcelable { JSONObject record = new JSONObject(); record.put(ATT_ID, getId()); record.put(ATT_NAME, getName()); - record.put(ATT_NAME_RES_ID, getNameResId()); return record; } @@ -191,31 +163,22 @@ public final class NotificationChannelGroup implements Parcelable { NotificationChannelGroup that = (NotificationChannelGroup) o; - if (getNameResId() != that.getNameResId()) return false; if (getId() != null ? !getId().equals(that.getId()) : that.getId() != null) return false; if (getName() != null ? !getName().equals(that.getName()) : that.getName() != null) { return false; } - return getChannels() != null ? getChannels().equals(that.getChannels()) - : that.getChannels() == null; - + return true; } @Override public NotificationChannelGroup clone() { - if (getName() != null) { - return new NotificationChannelGroup(getId(), getName()); - } else { - return new NotificationChannelGroup(getId(), getNameResId()); - } + return new NotificationChannelGroup(getId(), getName()); } @Override public int hashCode() { int result = getId() != null ? getId().hashCode() : 0; result = 31 * result + (getName() != null ? getName().hashCode() : 0); - result = 31 * result + getNameResId(); - result = 31 * result + (getChannels() != null ? getChannels().hashCode() : 0); return result; } @@ -224,7 +187,6 @@ public final class NotificationChannelGroup implements Parcelable { return "NotificationChannelGroup{" + "mId='" + mId + '\'' + ", mName=" + mName + - ", mNameResId=" + mNameResId + ", mChannels=" + mChannels + '}'; } diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java index 2296838eddb3..0379970762ab 100644 --- a/core/java/android/app/NotificationManager.java +++ b/core/java/android/app/NotificationManager.java @@ -385,7 +385,7 @@ public class NotificationManager /** * Creates a group container for {@link NotificationChannel} objects. * - * This is a no-op for groups that already exist. + * This can be used to rename an existing group. * <p> * Group information is only used for presentation, not for behavior. Groups are optional * for channels, and you can have a mix of channels that belong to groups and channels @@ -421,21 +421,22 @@ public class NotificationManager /** * Creates a notification channel that notifications can be posted to. * - * This is a no-op for channels that already exist. + * This can also be used to restore a deleted channel and to rename an existing channel. All + * other fields are ignored for channels that already exist. * * @param channel the channel to create. Note that the created channel may differ from this * value. If the provided channel is malformed, a RemoteException will be - * thrown. If the channel already exists, it will not be modified. + * thrown. */ public void createNotificationChannel(@NonNull NotificationChannel channel) { createNotificationChannels(Arrays.asList(channel)); } /** - * Creates multiple notification channels that different notifications can be posted to. + * Creates multiple notification channels that different notifications can be posted to. See + * {@link #createNotificationChannel(NotificationChannel)}. * - * @param channels the list of channels to attempt to create. If any of these channels already - * exist, they will not be modified. + * @param channels the list of channels to attempt to create. */ public void createNotificationChannels(@NonNull List<NotificationChannel> channels) { INotificationManager service = getService(); @@ -497,6 +498,30 @@ public class NotificationManager } /** + * Returns all notification channel groups belonging to the calling app. + */ + public List<NotificationChannelGroup> getNotificationChannelGroups() { + INotificationManager service = getService(); + try { + return service.getNotificationChannelGroups(mContext.getPackageName()).getList(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Deletes the given notification channel group. + */ + public void deleteNotificationChannelGroup(String groupId) { + INotificationManager service = getService(); + try { + service.deleteNotificationChannelGroup(mContext.getPackageName(), groupId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * @hide */ @TestApi diff --git a/core/java/android/app/TimePickerDialog.java b/core/java/android/app/TimePickerDialog.java index b219f2afd016..8a4f8a626c71 100644 --- a/core/java/android/app/TimePickerDialog.java +++ b/core/java/android/app/TimePickerDialog.java @@ -164,6 +164,15 @@ public class TimePickerDialog extends AlertDialog implements OnClickListener, @Override public void onClick(DialogInterface dialog, int which) { switch (which) { + case BUTTON_POSITIVE: + // Note this skips input validation and just uses the last valid time and hour + // entry. This will only be invoked programmatically. User clicks on BUTTON_POSITIVE + // are handled in show(). + if (mTimeSetListener != null) { + mTimeSetListener.onTimeSet(mTimePicker, mTimePicker.getCurrentHour(), + mTimePicker.getCurrentMinute()); + } + break; case BUTTON_NEGATIVE: cancel(); break; diff --git a/core/java/android/app/admin/DeviceAdminReceiver.java b/core/java/android/app/admin/DeviceAdminReceiver.java index 0fb59668a201..d9b6eed4fc4d 100644 --- a/core/java/android/app/admin/DeviceAdminReceiver.java +++ b/core/java/android/app/admin/DeviceAdminReceiver.java @@ -17,6 +17,7 @@ package android.app.admin; import android.accounts.AccountManager; +import android.annotation.BroadcastBehavior; import android.annotation.IntDef; import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; @@ -81,6 +82,7 @@ public class DeviceAdminReceiver extends BroadcastReceiver { * that other applications can not abuse it. */ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + @BroadcastBehavior(explicitOnly = true) public static final String ACTION_DEVICE_ADMIN_ENABLED = "android.app.action.DEVICE_ADMIN_ENABLED"; @@ -94,6 +96,7 @@ public class DeviceAdminReceiver extends BroadcastReceiver { * to the user before they disable your admin. */ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + @BroadcastBehavior(explicitOnly = true) public static final String ACTION_DEVICE_ADMIN_DISABLE_REQUESTED = "android.app.action.DEVICE_ADMIN_DISABLE_REQUESTED"; @@ -115,6 +118,7 @@ public class DeviceAdminReceiver extends BroadcastReceiver { * its intent filter. */ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + @BroadcastBehavior(explicitOnly = true) public static final String ACTION_DEVICE_ADMIN_DISABLED = "android.app.action.DEVICE_ADMIN_DISABLED"; @@ -131,6 +135,7 @@ public class DeviceAdminReceiver extends BroadcastReceiver { * this broadcast. */ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + @BroadcastBehavior(explicitOnly = true) public static final String ACTION_PASSWORD_CHANGED = "android.app.action.ACTION_PASSWORD_CHANGED"; @@ -147,6 +152,7 @@ public class DeviceAdminReceiver extends BroadcastReceiver { * this broadcast. */ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + @BroadcastBehavior(explicitOnly = true) public static final String ACTION_PASSWORD_FAILED = "android.app.action.ACTION_PASSWORD_FAILED"; @@ -160,6 +166,7 @@ public class DeviceAdminReceiver extends BroadcastReceiver { * this broadcast. */ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + @BroadcastBehavior(explicitOnly = true) public static final String ACTION_PASSWORD_SUCCEEDED = "android.app.action.ACTION_PASSWORD_SUCCEEDED"; @@ -173,6 +180,7 @@ public class DeviceAdminReceiver extends BroadcastReceiver { * this broadcast. */ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + @BroadcastBehavior(explicitOnly = true) public static final String ACTION_PASSWORD_EXPIRING = "android.app.action.ACTION_PASSWORD_EXPIRING"; @@ -187,6 +195,7 @@ public class DeviceAdminReceiver extends BroadcastReceiver { * @see DevicePolicyManager#isLockTaskPermitted(String) */ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + @BroadcastBehavior(explicitOnly = true) public static final String ACTION_LOCK_TASK_ENTERING = "android.app.action.LOCK_TASK_ENTERING"; @@ -200,6 +209,7 @@ public class DeviceAdminReceiver extends BroadcastReceiver { * @see DevicePolicyManager#isLockTaskPermitted(String) */ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + @BroadcastBehavior(explicitOnly = true) public static final String ACTION_LOCK_TASK_EXITING = "android.app.action.LOCK_TASK_EXITING"; @@ -232,6 +242,7 @@ public class DeviceAdminReceiver extends BroadcastReceiver { * <p>Output: Nothing</p> */ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + @BroadcastBehavior(explicitOnly = true) public static final String ACTION_PROFILE_PROVISIONING_COMPLETE = "android.app.action.PROFILE_PROVISIONING_COMPLETE"; @@ -244,6 +255,7 @@ public class DeviceAdminReceiver extends BroadcastReceiver { * @hide */ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + @BroadcastBehavior(explicitOnly = true) public static final String ACTION_BUGREPORT_SHARING_DECLINED = "android.app.action.BUGREPORT_SHARING_DECLINED"; @@ -256,6 +268,7 @@ public class DeviceAdminReceiver extends BroadcastReceiver { * @hide */ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + @BroadcastBehavior(explicitOnly = true) public static final String ACTION_BUGREPORT_FAILED = "android.app.action.BUGREPORT_FAILED"; /** @@ -266,6 +279,7 @@ public class DeviceAdminReceiver extends BroadcastReceiver { * @hide */ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + @BroadcastBehavior(explicitOnly = true) public static final String ACTION_BUGREPORT_SHARE = "android.app.action.BUGREPORT_SHARE"; @@ -274,6 +288,7 @@ public class DeviceAdminReceiver extends BroadcastReceiver { * @hide */ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + @BroadcastBehavior(explicitOnly = true) public static final String ACTION_SECURITY_LOGS_AVAILABLE = "android.app.action.SECURITY_LOGS_AVAILABLE"; @@ -283,6 +298,7 @@ public class DeviceAdminReceiver extends BroadcastReceiver { * @hide */ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + @BroadcastBehavior(explicitOnly = true) public static final String ACTION_NETWORK_LOGS_AVAILABLE = "android.app.action.NETWORK_LOGS_AVAILABLE"; @@ -314,7 +330,8 @@ public class DeviceAdminReceiver extends BroadcastReceiver { * @hide */ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_USER_ADDED = "android.app.action.USER_ADDED"; + @BroadcastBehavior(explicitOnly = true) + public static final String ACTION_USER_ADDED = "android.app.action.USER_ADDED"; /** * Broadcast action: notify the device owner that a user or profile has been removed. @@ -323,6 +340,7 @@ public class DeviceAdminReceiver extends BroadcastReceiver { * @hide */ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + @BroadcastBehavior(explicitOnly = true) public static final String ACTION_USER_REMOVED = "android.app.action.USER_REMOVED"; /** @@ -401,6 +419,7 @@ public class DeviceAdminReceiver extends BroadcastReceiver { * @hide */ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + @BroadcastBehavior(explicitOnly = true) public static final String ACTION_NOTIFY_PENDING_SYSTEM_UPDATE = "android.app.action.NOTIFY_PENDING_SYSTEM_UPDATE"; diff --git a/core/java/android/app/assist/AssistStructure.java b/core/java/android/app/assist/AssistStructure.java index 1f2ed00fa79d..b1fbc8f18818 100644 --- a/core/java/android/app/assist/AssistStructure.java +++ b/core/java/android/app/assist/AssistStructure.java @@ -1,5 +1,6 @@ package android.app.assist; +import android.annotation.Nullable; import android.app.Activity; import android.content.ComponentName; import android.graphics.Matrix; @@ -590,7 +591,7 @@ public class AssistStructure implements Parcelable { // fields (viewId and childId) of the field. AutofillId mAutofillId; @View.AutofillType int mAutofillType; - @View.AutofillHint int mAutofillHint; + @Nullable String[] mAutofillHint; AutofillValue mAutofillValue; String[] mAutofillOptions; boolean mSanitized; @@ -676,7 +677,7 @@ public class AssistStructure implements Parcelable { mSanitized = in.readInt() == 1; mAutofillId = in.readParcelable(null); mAutofillType = in.readInt(); - mAutofillHint = in.readInt(); + mAutofillHint = in.readStringArray(); mAutofillValue = in.readParcelable(null); mAutofillOptions = in.readStringArray(); } @@ -810,7 +811,7 @@ public class AssistStructure implements Parcelable { out.writeInt(mSanitized ? 1 : 0); out.writeParcelable(mAutofillId, 0); out.writeInt(mAutofillType); - out.writeInt(mAutofillHint); + out.writeStringArray(mAutofillHint); final AutofillValue sanitizedValue = writeSensitive ? mAutofillValue : null; out.writeParcelable(sanitizedValue, 0); out.writeStringArray(mAutofillOptions); @@ -949,7 +950,7 @@ public class AssistStructure implements Parcelable { * * @return The hint for this view */ - @View.AutofillHint public int getAutoFillHint() { + @Nullable public String[] getAutoFillHint() { return mAutofillHint; } @@ -1012,9 +1013,8 @@ public class AssistStructure implements Parcelable { mAutofillValue = value; // TODO(b/33197203, b/33802548): decide whether to set text as well (so it would work // with "legacy" views) or just the autofill value - final CharSequence text = value.getTextValue(); - if (text != null) { - mText.mText = text; + if (value.isText()) { + mText.mText = value.getTextValue(); } } @@ -1663,7 +1663,7 @@ public class AssistStructure implements Parcelable { } @Override - public void setAutofillHint(@View.AutofillHint int hint) { + public void setAutofillHint(@Nullable String[] hint) { mNode.mAutofillHint = hint; } @@ -1683,8 +1683,8 @@ public class AssistStructure implements Parcelable { } @Override - public void setSanitized(boolean sanitized) { - mNode.mSanitized = sanitized; + public void setDataIsSensitive(boolean sensitive) { + mNode.mSanitized = !sensitive; } @Override @@ -1812,7 +1812,7 @@ public class AssistStructure implements Parcelable { + ", type=" + node.getAutofillType() + ", options=" + Arrays.toString(node.getAutofillOptions()) + ", inputType=" + node.getInputType() - + ", hint=" + Integer.toHexString(node.getAutoFillHint()) + + ", hint=" + Arrays.toString(node.getAutoFillHint()) + ", value=" + node.getAutofillValue() + ", sanitized=" + node.isSanitized()); } diff --git a/core/java/android/app/usage/StorageStatsManager.java b/core/java/android/app/usage/StorageStatsManager.java index 82762293a241..b808c2b75203 100644 --- a/core/java/android/app/usage/StorageStatsManager.java +++ b/core/java/android/app/usage/StorageStatsManager.java @@ -81,9 +81,9 @@ public class StorageStatsManager { /** * Return the free space on the requested storage volume. * <p> - * The free space is equivalent to {@link File#getFreeSpace()} plus the size - * of any cached data that can be automatically deleted by the system as - * additional space is needed. + * The free space is equivalent to {@link File#getUsableSpace()} plus the + * size of any cached data that can be automatically deleted by the system + * as additional space is needed. * <p> * This method may take several seconds to calculate the requested values, * so it should only be called from a worker thread. diff --git a/core/java/android/appwidget/AppWidgetManager.java b/core/java/android/appwidget/AppWidgetManager.java index 67c791df3c9a..74a39e85e029 100644 --- a/core/java/android/appwidget/AppWidgetManager.java +++ b/core/java/android/appwidget/AppWidgetManager.java @@ -16,8 +16,11 @@ package android.appwidget; +import android.annotation.BroadcastBehavior; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SdkConstant; +import android.annotation.SdkConstant.SdkConstantType; import android.app.PendingIntent; import android.content.ComponentName; import android.content.Context; @@ -81,12 +84,14 @@ public class AppWidgetManager { * * @see #ACTION_APPWIDGET_CONFIGURE */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) public static final String ACTION_APPWIDGET_PICK = "android.appwidget.action.APPWIDGET_PICK"; /** * Similar to ACTION_APPWIDGET_PICK, but used from keyguard * @hide */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) public static final String ACTION_KEYGUARD_APPWIDGET_PICK = "android.appwidget.action.KEYGUARD_APPWIDGET_PICK"; @@ -133,6 +138,7 @@ public class AppWidgetManager { * @see #ACTION_APPWIDGET_CONFIGURE * */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) public static final String ACTION_APPWIDGET_BIND = "android.appwidget.action.APPWIDGET_BIND"; /** @@ -157,6 +163,7 @@ public class AppWidgetManager { * and not display this AppWidget, and you will receive a {@link #ACTION_APPWIDGET_DELETED} * broadcast. */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) public static final String ACTION_APPWIDGET_CONFIGURE = "android.appwidget.action.APPWIDGET_CONFIGURE"; /** @@ -290,6 +297,8 @@ public class AppWidgetManager { * * @see AppWidgetProvider#onUpdate AppWidgetProvider.onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + @BroadcastBehavior(explicitOnly = true) public static final String ACTION_APPWIDGET_UPDATE = "android.appwidget.action.APPWIDGET_UPDATE"; /** @@ -302,6 +311,8 @@ public class AppWidgetManager { * AppWidgetProvider.onAppWidgetOptionsChanged(Context context, * AppWidgetManager appWidgetManager, int appWidgetId, Bundle newExtras) */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + @BroadcastBehavior(explicitOnly = true) public static final String ACTION_APPWIDGET_OPTIONS_CHANGED = "android.appwidget.action.APPWIDGET_UPDATE_OPTIONS"; /** @@ -312,6 +323,8 @@ public class AppWidgetManager { * * @see AppWidgetProvider#onDeleted AppWidgetProvider.onDeleted(Context context, int[] appWidgetIds) */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + @BroadcastBehavior(explicitOnly = true) public static final String ACTION_APPWIDGET_DELETED = "android.appwidget.action.APPWIDGET_DELETED"; /** @@ -322,6 +335,8 @@ public class AppWidgetManager { * * @see AppWidgetProvider#onEnabled AppWidgetProvider.onDisabled(Context context) */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + @BroadcastBehavior(explicitOnly = true) public static final String ACTION_APPWIDGET_DISABLED = "android.appwidget.action.APPWIDGET_DISABLED"; /** @@ -334,6 +349,8 @@ public class AppWidgetManager { * * @see AppWidgetProvider#onEnabled AppWidgetProvider.onEnabled(Context context) */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + @BroadcastBehavior(explicitOnly = true) public static final String ACTION_APPWIDGET_ENABLED = "android.appwidget.action.APPWIDGET_ENABLED"; /** @@ -365,6 +382,8 @@ public class AppWidgetManager { * * @see #ACTION_APPWIDGET_HOST_RESTORED */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + @BroadcastBehavior(explicitOnly = true) public static final String ACTION_APPWIDGET_RESTORED = "android.appwidget.action.APPWIDGET_RESTORED"; @@ -402,6 +421,8 @@ public class AppWidgetManager { * * @see #ACTION_APPWIDGET_RESTORED */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + @BroadcastBehavior(explicitOnly = true) public static final String ACTION_APPWIDGET_HOST_RESTORED = "android.appwidget.action.APPWIDGET_HOST_RESTORED"; diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java index 488511b49b88..4e1e42da4fe7 100644 --- a/core/java/android/bluetooth/BluetoothAdapter.java +++ b/core/java/android/bluetooth/BluetoothAdapter.java @@ -1483,6 +1483,25 @@ public final class BluetoothAdapter { } /** + * Return the maximum LE advertising data length, + * if LE Extended Advertising feature is supported. + * + * @return the maximum LE advertising data length. + */ + public int getLeMaximumAdvertisingDataLength() { + if (!getLeAccess()) return 0; + try { + mServiceLock.readLock().lock(); + if (mService != null) return mService.getLeMaximumAdvertisingDataLength(); + } catch (RemoteException e) { + Log.e(TAG, "failed to get getLeMaximumAdvertisingDataLength, error: ", e); + } finally { + mServiceLock.readLock().unlock(); + } + return 0; + } + + /** * Return true if hardware has entries available for matching beacons * * @return true if there are hw entries available for matching beacons diff --git a/core/java/android/bluetooth/IBluetooth.aidl b/core/java/android/bluetooth/IBluetooth.aidl index 76ca554e5984..b33781729b62 100644 --- a/core/java/android/bluetooth/IBluetooth.aidl +++ b/core/java/android/bluetooth/IBluetooth.aidl @@ -108,6 +108,7 @@ interface IBluetooth boolean isLeCodedPhySupported(); boolean isLeExtendedAdvertisingSupported(); boolean isLePeriodicAdvertisingSupported(); + int getLeMaximumAdvertisingDataLength(); BluetoothActivityEnergyInfo reportActivityInfo(); /** diff --git a/core/java/android/bluetooth/IBluetoothGatt.aidl b/core/java/android/bluetooth/IBluetoothGatt.aidl index 33fedc718980..29f29e7dba4e 100644 --- a/core/java/android/bluetooth/IBluetoothGatt.aidl +++ b/core/java/android/bluetooth/IBluetoothGatt.aidl @@ -50,14 +50,6 @@ interface IBluetoothGatt { void stopScan(in int scannerId); void flushPendingBatchResults(in int scannerId); - void registerAdvertiser(in IAdvertiserCallback callback); - void unregisterAdvertiser(in int advertiserId); - void startMultiAdvertising(in int advertiserId, - in AdvertiseData advertiseData, - in AdvertiseData scanResponse, - in AdvertiseSettings settings); - void stopMultiAdvertising(in int advertiserId); - void startAdvertisingSet(in AdvertisingSetParameters parameters, in AdvertiseData advertiseData, in AdvertiseData scanResponse, in PeriodicAdvertisingParameters periodicParameters, in AdvertiseData periodicData, in IAdvertisingSetCallback callback); diff --git a/core/java/android/bluetooth/le/BluetoothLeAdvertiser.java b/core/java/android/bluetooth/le/BluetoothLeAdvertiser.java index e03c9477a6a3..c9f1d7a32efb 100644 --- a/core/java/android/bluetooth/le/BluetoothLeAdvertiser.java +++ b/core/java/android/bluetooth/le/BluetoothLeAdvertiser.java @@ -28,6 +28,7 @@ import android.os.ParcelUuid; import android.os.RemoteException; import android.util.Log; +import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.UUID; @@ -60,11 +61,12 @@ public final class BluetoothLeAdvertiser { private final IBluetoothManager mBluetoothManager; private final Handler mHandler; private BluetoothAdapter mBluetoothAdapter; - private final Map<AdvertiseCallback, AdvertiseCallbackWrapper> - mLeAdvertisers = new HashMap<AdvertiseCallback, AdvertiseCallbackWrapper>(); + private final Map<AdvertiseCallback, AdvertisingSetCallback> + mLegacyAdvertisers = new HashMap<>(); private final Map<AdvertisingSetCallback, IAdvertisingSetCallback> - advertisingSetCallbackWrappers = new HashMap<>(); - private final Map<Integer, AdvertisingSet> advertisingSets = new HashMap<>(); + mCallbackWrappers = Collections.synchronizedMap(new HashMap<>()); + private final Map<Integer, AdvertisingSet> + mAdvertisingSets = Collections.synchronizedMap(new HashMap<>()); /** * Use BluetoothAdapter.getLeAdvertiser() instead. @@ -109,7 +111,7 @@ public final class BluetoothLeAdvertiser { public void startAdvertising(AdvertiseSettings settings, AdvertiseData advertiseData, AdvertiseData scanResponse, final AdvertiseCallback callback) { - synchronized (mLeAdvertisers) { + synchronized (mLegacyAdvertisers) { BluetoothLeUtils.checkAdapterStateOn(mBluetoothAdapter); if (callback == null) { throw new IllegalArgumentException("callback cannot be null"); @@ -120,25 +122,65 @@ public final class BluetoothLeAdvertiser { postStartFailure(callback, AdvertiseCallback.ADVERTISE_FAILED_DATA_TOO_LARGE); return; } - if (mLeAdvertisers.containsKey(callback)) { + if (mLegacyAdvertisers.containsKey(callback)) { postStartFailure(callback, AdvertiseCallback.ADVERTISE_FAILED_ALREADY_STARTED); return; } - IBluetoothGatt gatt; - try { - gatt = mBluetoothManager.getBluetoothGatt(); - } catch (RemoteException e) { - Log.e(TAG, "Failed to get Bluetooth gatt - ", e); - postStartFailure(callback, AdvertiseCallback.ADVERTISE_FAILED_INTERNAL_ERROR); - return; + AdvertisingSetParameters.Builder parameters = new AdvertisingSetParameters.Builder(); + parameters.setLegacyMode(true); + parameters.setConnectable(isConnectable); + parameters.setTimeout(settings.getTimeout()); + if (settings.getMode() == AdvertiseSettings.ADVERTISE_MODE_LOW_POWER) { + parameters.setInterval(1600); // 1s + } else if (settings.getMode() == AdvertiseSettings.ADVERTISE_MODE_BALANCED) { + parameters.setInterval(400); // 250ms + } else if (settings.getMode() == AdvertiseSettings.ADVERTISE_MODE_LOW_LATENCY) { + parameters.setInterval(160); // 100ms + } + + if (settings.getTxPowerLevel() == AdvertiseSettings.ADVERTISE_TX_POWER_ULTRA_LOW) { + parameters.setTxPowerLevel(-21); + } else if (settings.getTxPowerLevel() == AdvertiseSettings.ADVERTISE_TX_POWER_LOW) { + parameters.setTxPowerLevel(-15); + } else if (settings.getTxPowerLevel() == AdvertiseSettings.ADVERTISE_TX_POWER_MEDIUM) { + parameters.setTxPowerLevel(-7); + } else if (settings.getTxPowerLevel() == AdvertiseSettings.ADVERTISE_TX_POWER_HIGH) { + parameters.setTxPowerLevel(1); } - AdvertiseCallbackWrapper wrapper = new AdvertiseCallbackWrapper(callback, advertiseData, - scanResponse, settings, gatt); - wrapper.startRegisteration(); + + AdvertisingSetCallback wrapped = wrapOldCallback(callback, settings); + mLegacyAdvertisers.put(callback, wrapped); + startAdvertisingSet(parameters.build(), advertiseData, scanResponse, null, null, + wrapped); } } + AdvertisingSetCallback wrapOldCallback(AdvertiseCallback callback, AdvertiseSettings settings) { + return new AdvertisingSetCallback() { + public void onAdvertisingSetStarted(AdvertisingSet advertisingSet, int status) { + if (status != AdvertisingSetCallback.ADVERTISE_SUCCESS) { + postStartFailure(callback, status); + return; + } + + postStartSuccess(callback, settings); + } + + /* Legacy advertiser is disabled on timeout */ + public void onAdvertisingEnabled(int advertiserId, boolean enabled, int status) { + if (enabled == true) { + Log.e(TAG, "Legacy advertiser should be only disabled on timeout," + + " but was enabled!"); + return; + } + + stopAdvertising(callback); + } + + }; + } + /** * Stop Bluetooth LE advertising. The {@code callback} must be the same one use in * {@link BluetoothLeAdvertiser#startAdvertising}. @@ -148,20 +190,21 @@ public final class BluetoothLeAdvertiser { * @param callback {@link AdvertiseCallback} identifies the advertising instance to stop. */ public void stopAdvertising(final AdvertiseCallback callback) { - synchronized (mLeAdvertisers) { + synchronized (mLegacyAdvertisers) { if (callback == null) { throw new IllegalArgumentException("callback cannot be null"); } - AdvertiseCallbackWrapper wrapper = mLeAdvertisers.get(callback); + AdvertisingSetCallback wrapper = mLegacyAdvertisers.get(callback); if (wrapper == null) return; - wrapper.stopAdvertising(); + + stopAdvertisingSet(wrapper); } } /** * Creates a new advertising set. If operation succeed, device will start advertising. This * method returns immediately, the operation status is delivered through - * {@code callback.onNewAdvertisingSet()}. + * {@code callback.onAdvertisingSetStarted()}. * <p> * @param parameters advertising set parameters. * @param advertiseData Advertisement data to be broadcasted. @@ -180,7 +223,7 @@ public final class BluetoothLeAdvertiser { /** * Creates a new advertising set. If operation succeed, device will start advertising. This * method returns immediately, the operation status is delivered through - * {@code callback.onNewAdvertisingSet()}. + * {@code callback.onAdvertisingSetStarted()}. * <p> * @param parameters advertising set parameters. * @param advertiseData Advertisement data to be broadcasted. @@ -209,7 +252,10 @@ public final class BluetoothLeAdvertiser { } IAdvertisingSetCallback wrapped = wrap(callback, handler); - advertisingSetCallbackWrappers.put(callback, wrapped); + if (mCallbackWrappers.putIfAbsent(callback, wrapped) != null) { + throw new IllegalArgumentException( + "callback instance already associated with advertising"); + } try { gatt.startAdvertisingSet(parameters, advertiseData, scanResponse, periodicParameters, @@ -229,10 +275,9 @@ public final class BluetoothLeAdvertiser { throw new IllegalArgumentException("callback cannot be null"); } - IAdvertisingSetCallback wrapped = advertisingSetCallbackWrappers.remove(callback); + IAdvertisingSetCallback wrapped = mCallbackWrappers.remove(callback); if (wrapped == null) { - throw new IllegalArgumentException( - "callback does not represent valid registered callback."); + return; } IBluetoothGatt gatt; @@ -251,7 +296,9 @@ public final class BluetoothLeAdvertiser { * @hide */ public void cleanup() { - mLeAdvertisers.clear(); + mLegacyAdvertisers.clear(); + mCallbackWrappers.clear(); + mAdvertisingSets.clear(); } // Compute the size of advertisement data or scan resp @@ -317,13 +364,13 @@ public final class BluetoothLeAdvertiser { public void run() { if (status != AdvertisingSetCallback.ADVERTISE_SUCCESS) { callback.onAdvertisingSetStarted(null, status); - advertisingSetCallbackWrappers.remove(callback); + mCallbackWrappers.remove(callback); return; } AdvertisingSet advertisingSet = new AdvertisingSet(advertiserId, mBluetoothManager); - advertisingSets.put(advertiserId, advertisingSet); + mAdvertisingSets.put(advertiserId, advertisingSet); callback.onAdvertisingSetStarted(advertisingSet, status); } }); @@ -333,10 +380,10 @@ public final class BluetoothLeAdvertiser { handler.post(new Runnable() { @Override public void run() { - AdvertisingSet advertisingSet = advertisingSets.get(advertiserId); + AdvertisingSet advertisingSet = mAdvertisingSets.get(advertiserId); callback.onAdvertisingSetStopped(advertisingSet); - advertisingSets.remove(advertiserId); - advertisingSetCallbackWrappers.remove(callback); + mAdvertisingSets.remove(advertiserId); + mCallbackWrappers.remove(callback); } }); } @@ -345,7 +392,7 @@ public final class BluetoothLeAdvertiser { handler.post(new Runnable() { @Override public void run() { - AdvertisingSet advertisingSet = advertisingSets.get(advertiserId); + AdvertisingSet advertisingSet = mAdvertisingSets.get(advertiserId); callback.onAdvertisingEnabled(advertisingSet, enabled, status); } }); @@ -355,7 +402,7 @@ public final class BluetoothLeAdvertiser { handler.post(new Runnable() { @Override public void run() { - AdvertisingSet advertisingSet = advertisingSets.get(advertiserId); + AdvertisingSet advertisingSet = mAdvertisingSets.get(advertiserId); callback.onAdvertisingDataSet(advertisingSet, status); } }); @@ -365,7 +412,7 @@ public final class BluetoothLeAdvertiser { handler.post(new Runnable() { @Override public void run() { - AdvertisingSet advertisingSet = advertisingSets.get(advertiserId); + AdvertisingSet advertisingSet = mAdvertisingSets.get(advertiserId); callback.onScanResponseDataSet(advertisingSet, status); } }); @@ -375,7 +422,7 @@ public final class BluetoothLeAdvertiser { handler.post(new Runnable() { @Override public void run() { - AdvertisingSet advertisingSet = advertisingSets.get(advertiserId); + AdvertisingSet advertisingSet = mAdvertisingSets.get(advertiserId); callback.onAdvertisingParametersUpdated(advertisingSet, status); } }); @@ -385,7 +432,7 @@ public final class BluetoothLeAdvertiser { handler.post(new Runnable() { @Override public void run() { - AdvertisingSet advertisingSet = advertisingSets.get(advertiserId); + AdvertisingSet advertisingSet = mAdvertisingSets.get(advertiserId); callback.onPeriodicAdvertisingParametersUpdated(advertisingSet, status); } }); @@ -395,7 +442,7 @@ public final class BluetoothLeAdvertiser { handler.post(new Runnable() { @Override public void run() { - AdvertisingSet advertisingSet = advertisingSets.get(advertiserId); + AdvertisingSet advertisingSet = mAdvertisingSets.get(advertiserId); callback.onPeriodicAdvertisingDataSet(advertisingSet, status); } }); @@ -405,7 +452,7 @@ public final class BluetoothLeAdvertiser { handler.post(new Runnable() { @Override public void run() { - AdvertisingSet advertisingSet = advertisingSets.get(advertiserId); + AdvertisingSet advertisingSet = mAdvertisingSets.get(advertiserId); callback.onPeriodicAdvertisingEnable(advertisingSet, enable, status); } }); @@ -413,144 +460,6 @@ public final class BluetoothLeAdvertiser { }; } - /** - * Bluetooth GATT interface callbacks for advertising. - */ - private class AdvertiseCallbackWrapper extends IAdvertiserCallback.Stub { - private static final int LE_CALLBACK_TIMEOUT_MILLIS = 2000; - private final AdvertiseCallback mAdvertiseCallback; - private final AdvertiseData mAdvertisement; - private final AdvertiseData mScanResponse; - private final AdvertiseSettings mSettings; - private final IBluetoothGatt mBluetoothGatt; - - // mAdvertiserId -1: not registered - // -2: advertise stopped or registration timeout - // >=0: registered and advertising started - private int mAdvertiserId; - private boolean mIsAdvertising = false; - private int registrationError = AdvertiseCallback.ADVERTISE_FAILED_INTERNAL_ERROR; - - public AdvertiseCallbackWrapper(AdvertiseCallback advertiseCallback, - AdvertiseData advertiseData, AdvertiseData scanResponse, - AdvertiseSettings settings, - IBluetoothGatt bluetoothGatt) { - mAdvertiseCallback = advertiseCallback; - mAdvertisement = advertiseData; - mScanResponse = scanResponse; - mSettings = settings; - mBluetoothGatt = bluetoothGatt; - mAdvertiserId = -1; - } - - public void startRegisteration() { - synchronized (this) { - if (mAdvertiserId == -2) return; - - try { - mBluetoothGatt.registerAdvertiser(this); - wait(LE_CALLBACK_TIMEOUT_MILLIS); - } catch (InterruptedException | RemoteException e) { - Log.e(TAG, "Failed to start registeration", e); - } - if (mAdvertiserId >= 0 && mIsAdvertising) { - mLeAdvertisers.put(mAdvertiseCallback, this); - } else if (mAdvertiserId < 0) { - - // Registration timeout, reset mClientIf to -2 so no subsequent operations can - // proceed. - if (mAdvertiserId == -1) mAdvertiserId = -2; - // Post internal error if registration failed. - postStartFailure(mAdvertiseCallback, registrationError); - } else { - // Unregister application if it's already registered but advertise failed. - try { - mBluetoothGatt.unregisterAdvertiser(mAdvertiserId); - mAdvertiserId = -2; - } catch (RemoteException e) { - Log.e(TAG, "remote exception when unregistering", e); - } - } - } - } - - public void stopAdvertising() { - synchronized (this) { - try { - mBluetoothGatt.stopMultiAdvertising(mAdvertiserId); - wait(LE_CALLBACK_TIMEOUT_MILLIS); - } catch (InterruptedException | RemoteException e) { - Log.e(TAG, "Failed to stop advertising", e); - } - // Advertise callback should have been removed from LeAdvertisers when - // onMultiAdvertiseCallback was called. In case onMultiAdvertiseCallback is never - // invoked and wait timeout expires, remove callback here. - if (mLeAdvertisers.containsKey(mAdvertiseCallback)) { - mLeAdvertisers.remove(mAdvertiseCallback); - } - } - } - - /** - * Advertiser interface registered - app is ready to go - */ - @Override - public void onAdvertiserRegistered(int status, int advertiserId) { - Log.d(TAG, "onAdvertiserRegistered() - status=" + status + " advertiserId=" + advertiserId); - synchronized (this) { - if (status == BluetoothGatt.GATT_SUCCESS) { - try { - if (mAdvertiserId == -2) { - // Registration succeeds after timeout, unregister advertiser. - mBluetoothGatt.unregisterAdvertiser(advertiserId); - } else { - mAdvertiserId = advertiserId; - mBluetoothGatt.startMultiAdvertising(mAdvertiserId, mAdvertisement, - mScanResponse, mSettings); - } - return; - } catch (RemoteException e) { - Log.e(TAG, "failed to start advertising", e); - } - } else if (status == AdvertiseCallback.ADVERTISE_FAILED_TOO_MANY_ADVERTISERS) { - registrationError = status; - } - // Registration failed. - mAdvertiserId = -2; - notifyAll(); - } - } - - @Override - public void onMultiAdvertiseCallback(int status, boolean isStart, - AdvertiseSettings settings) { - synchronized (this) { - if (isStart) { - if (status == AdvertiseCallback.ADVERTISE_SUCCESS) { - // Start success - mIsAdvertising = true; - postStartSuccess(mAdvertiseCallback, settings); - } else { - // Start failure. - postStartFailure(mAdvertiseCallback, status); - } - } else { - // unregister advertiser for stop. - try { - mBluetoothGatt.unregisterAdvertiser(mAdvertiserId); - mAdvertiserId = -2; - mIsAdvertising = false; - mLeAdvertisers.remove(mAdvertiseCallback); - } catch (RemoteException e) { - Log.e(TAG, "remote exception when unregistering", e); - } - } - notifyAll(); - } - - } - } - private void postStartFailure(final AdvertiseCallback callback, final int error) { mHandler.post(new Runnable() { @Override diff --git a/core/java/android/content/ContentProviderNative.java b/core/java/android/content/ContentProviderNative.java index d428a3a857b7..2f87633a39d3 100644 --- a/core/java/android/content/ContentProviderNative.java +++ b/core/java/android/content/ContentProviderNative.java @@ -24,6 +24,7 @@ import android.database.Cursor; import android.database.CursorToBulkCursorAdaptor; import android.database.DatabaseUtils; import android.database.IContentObserver; +import android.database.PageViewCursor; import android.net.Uri; import android.os.Binder; import android.os.Bundle; @@ -103,6 +104,7 @@ abstract public class ContentProviderNative extends Binder implements IContentPr if (cursor != null) { CursorToBulkCursorAdaptor adaptor = null; + cursor = PageViewCursor.wrap(cursor, queryArgs); try { adaptor = new CursorToBulkCursorAdaptor(cursor, observer, getProviderName()); diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index 1f01e28ea0b0..4dc6fd227243 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -17,6 +17,7 @@ package android.content; import android.annotation.AnyRes; +import android.annotation.BroadcastBehavior; import android.annotation.IntDef; import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; @@ -1992,6 +1993,7 @@ public class Intent implements Parcelable, Cloneable { * This is a protected intent that can only be sent by the system. */ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + @BroadcastBehavior(includeBackground = true) public static final String ACTION_BOOT_COMPLETED = "android.intent.action.BOOT_COMPLETED"; /** diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl index 147b3e13b998..4de64c41e913 100644 --- a/core/java/android/content/pm/IPackageManager.aidl +++ b/core/java/android/content/pm/IPackageManager.aidl @@ -608,6 +608,12 @@ interface IPackageManager { boolean setRequiredForSystemUser(String packageName, boolean systemUserApp); + /** + * Sets whether or not an update is available. Ostensibly for instant apps + * to force exteranl resolution. + */ + void setUpdateAvailable(String packageName, boolean updateAvaialble); + String getServicesSystemSharedLibraryPackageName(); String getSharedSystemSharedLibraryPackageName(); diff --git a/core/java/android/content/pm/PackageInfo.java b/core/java/android/content/pm/PackageInfo.java index 5d5696b5a54e..8ff2f352a362 100644 --- a/core/java/android/content/pm/PackageInfo.java +++ b/core/java/android/content/pm/PackageInfo.java @@ -271,6 +271,9 @@ public class PackageInfo implements Parcelable { */ public String overlayTarget; + /** @hide */ + public boolean isStaticOverlay; + public PackageInfo() { } @@ -323,6 +326,7 @@ public class PackageInfo implements Parcelable { dest.writeString(restrictedAccountType); dest.writeString(requiredAccountType); dest.writeString(overlayTarget); + dest.writeInt(isStaticOverlay ? 1 : 0); } public static final Parcelable.Creator<PackageInfo> CREATOR @@ -372,6 +376,7 @@ public class PackageInfo implements Parcelable { restrictedAccountType = source.readString(); requiredAccountType = source.readString(); overlayTarget = source.readString(); + isStaticOverlay = source.readInt() != 0; // The component lists were flattened with the redundant ApplicationInfo // instances omitted. Distribute the canonical one here as appropriate. diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index 3a875bc79aa8..10ffab2d813d 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -2355,6 +2355,13 @@ public abstract class PackageManager { = "android.hardware.vr.high_performance"; /** + * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: + * The device implements headtracking suitable for a VR device. + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_VR_HEADTRACKING = "android.hardware.vr.headtracking"; + + /** * Action to external storage service to clean out removed apps. * @hide */ @@ -5286,6 +5293,11 @@ public abstract class PackageManager { public abstract void setInstallerPackageName(String targetPackage, String installerPackageName); + /** @hide */ + @SystemApi + @RequiresPermission(Manifest.permission.INSTALL_PACKAGES) + public abstract void setUpdateAvailable(String packageName, boolean updateAvaialble); + /** * Attempts to delete a package. Since this may take a little while, the * result will be posted back to the given observer. A deletion will fail if diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index a1c325ac26ea..5a28e87ac65f 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -211,6 +211,14 @@ public class PackageParser { CHILD_PACKAGE_TAGS.add(TAG_EAT_COMMENT); } + private static final boolean LOG_UNSAFE_BROADCASTS = false; + + // Set of broadcast actions that are safe for manifest receivers + private static final Set<String> SAFE_BROADCASTS = new ArraySet<>(); + static { + SAFE_BROADCASTS.add(Intent.ACTION_BOOT_COMPLETED); + } + /** @hide */ public static class NewPermissionInfo { public final String name; @@ -603,6 +611,7 @@ public class PackageParser { pi.restrictedAccountType = p.mRestrictedAccountType; pi.requiredAccountType = p.mRequiredAccountType; pi.overlayTarget = p.mOverlayTarget; + pi.isStaticOverlay = p.mIsStaticOverlay; pi.firstInstallTime = firstInstallTime; pi.lastUpdateTime = lastUpdateTime; if ((flags&PackageManager.GET_GIDS) != 0) { @@ -2097,6 +2106,9 @@ public class PackageParser { com.android.internal.R.styleable.AndroidManifestResourceOverlay); pkg.mOverlayTarget = sa.getString( com.android.internal.R.styleable.AndroidManifestResourceOverlay_targetPackage); + pkg.mIsStaticOverlay = sa.getBoolean( + com.android.internal.R.styleable.AndroidManifestResourceOverlay_isStatic, + false); sa.recycle(); if (pkg.mOverlayTarget == null) { @@ -2104,6 +2116,9 @@ public class PackageParser { mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED; return null; } + if (pkg.mIsStaticOverlay) { + // TODO(b/35742444): Need to support selection method based on a package name. + } XmlUtils.skipCurrentTag(parser); } else if (tagName.equals(TAG_KEY_SETS)) { @@ -4240,6 +4255,18 @@ public class PackageParser { if (intent.isVisibleToInstantApp()) { a.info.flags |= ActivityInfo.FLAG_VISIBLE_TO_EPHEMERAL; } + if (LOG_UNSAFE_BROADCASTS && receiver + && (owner.applicationInfo.targetSdkVersion >= Build.VERSION_CODES.O)) { + for (int i = 0; i < intent.countActions(); i++) { + final String action = intent.getAction(i); + if (action == null || !action.startsWith("android.")) continue; + if (!SAFE_BROADCASTS.contains(action)) { + Slog.w(TAG, "Broadcast " + action + " may never be delivered to " + + owner.packageName + " as requested at: " + + parser.getPositionDescription()); + } + } + } } else if (!receiver && parser.getName().equals("preferred")) { ActivityIntentInfo intent = new ActivityIntentInfo(a); if (!parseIntent(res, parser, false /*allowGlobs*/, false /*allowAutoVerify*/, @@ -5580,6 +5607,7 @@ public class PackageParser { public String mRequiredAccountType; public String mOverlayTarget; + public boolean mIsStaticOverlay; public boolean mTrustedOverlay; /** @@ -6056,6 +6084,7 @@ public class PackageParser { mRestrictedAccountType = dest.readString(); mRequiredAccountType = dest.readString(); mOverlayTarget = dest.readString(); + mIsStaticOverlay = (dest.readInt() == 1); mTrustedOverlay = (dest.readInt() == 1); mSigningKeys = (ArraySet<PublicKey>) dest.readArraySet(boot); mUpgradeKeySets = (ArraySet<String>) dest.readArraySet(boot); @@ -6171,6 +6200,7 @@ public class PackageParser { dest.writeString(mRestrictedAccountType); dest.writeString(mRequiredAccountType); dest.writeString(mOverlayTarget); + dest.writeInt(mIsStaticOverlay ? 1 : 0); dest.writeInt(mTrustedOverlay ? 1 : 0); dest.writeArraySet(mSigningKeys); dest.writeArraySet(mUpgradeKeySets); diff --git a/core/java/android/content/res/FontResourcesParser.java b/core/java/android/content/res/FontResourcesParser.java index 50fc3443f5a6..091cc263f5b4 100644 --- a/core/java/android/content/res/FontResourcesParser.java +++ b/core/java/android/content/res/FontResourcesParser.java @@ -16,7 +16,8 @@ package android.content.res; import com.android.internal.R; -import android.text.FontConfig; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.util.AttributeSet; import android.util.Xml; @@ -35,7 +36,81 @@ public class FontResourcesParser { private static final int NORMAL_WEIGHT = 400; private static final int ITALIC = 1; - public static FontConfig parse(XmlPullParser parser, Resources resources) + // A class represents single entry of font-family in xml file. + public interface FamilyResourceEntry {} + + // A class represents font provider based font-family element in xml file. + public static final class ProviderResourceEntry implements FamilyResourceEntry { + private final @NonNull String mProviderAuthority; + private final @NonNull String mProviderPackage; + private final @NonNull String mQuery; + + public ProviderResourceEntry(@NonNull String authority, @NonNull String pkg, + @NonNull String query) { + mProviderAuthority = authority; + mProviderPackage = pkg; + mQuery = query; + } + + public @NonNull String getAuthority() { + return mProviderAuthority; + } + + public @NonNull String getPackage() { + return mProviderPackage; + } + + public @NonNull String getQuery() { + return mQuery; + } + } + + // A class represents font element in xml file which points a file in resource. + public static final class FontFileResourceEntry { + private final @NonNull String mFileName; + private int mWeight; + private boolean mItalic; + private int mResourceId; + + public FontFileResourceEntry(@NonNull String fileName, int weight, boolean italic, + int resourceId) { + mFileName = fileName; + mWeight = weight; + mItalic = italic; + mResourceId = resourceId; + } + + public @NonNull String getFileName() { + return mFileName; + } + + public int getWeight() { + return mWeight; + } + + public boolean isItalic() { + return mItalic; + } + + public int getResourceId() { + return mResourceId; + } + } + + // A class represents file based font-family element in xml file. + public static final class FontFamilyFilesResourceEntry implements FamilyResourceEntry { + private final @NonNull FontFileResourceEntry[] mEntries; + + public FontFamilyFilesResourceEntry(@NonNull FontFileResourceEntry[] entries) { + mEntries = entries; + } + + public @NonNull FontFileResourceEntry[] getEntries() { + return mEntries; + } + } + + public static @Nullable FamilyResourceEntry parse(XmlPullParser parser, Resources resources) throws XmlPullParserException, IOException { int type; while ((type=parser.next()) != XmlPullParser.START_TAG @@ -49,21 +124,21 @@ public class FontResourcesParser { return readFamilies(parser, resources); } - private static FontConfig readFamilies(XmlPullParser parser, Resources resources) - throws XmlPullParserException, IOException { - FontConfig config = new FontConfig(); + private static @Nullable FamilyResourceEntry readFamilies(XmlPullParser parser, + Resources resources) throws XmlPullParserException, IOException { parser.require(XmlPullParser.START_TAG, null, "font-family"); String tag = parser.getName(); + FamilyResourceEntry result = null; if (tag.equals("font-family")) { - config.getFamilies().add(readFamily(parser, resources)); + return readFamily(parser, resources); } else { skip(parser); + return null; } - return config; } - private static FontConfig.Family readFamily(XmlPullParser parser, Resources resources) - throws XmlPullParserException, IOException { + private static @Nullable FamilyResourceEntry readFamily(XmlPullParser parser, + Resources resources) throws XmlPullParserException, IOException { AttributeSet attrs = Xml.asAttributeSet(parser); TypedArray array = resources.obtainAttributes(attrs, R.styleable.FontFamily); String authority = array.getString(R.styleable.FontFamily_fontProviderAuthority); @@ -74,9 +149,9 @@ public class FontResourcesParser { while (parser.next() != XmlPullParser.END_TAG) { skip(parser); } - return new FontConfig.Family(authority, providerPackage, query); + return new ProviderResourceEntry(authority, providerPackage, query); } - List<FontConfig.Font> fonts = new ArrayList<>(); + List<FontFileResourceEntry> fonts = new ArrayList<>(); while (parser.next() != XmlPullParser.END_TAG) { if (parser.getEventType() != XmlPullParser.START_TAG) continue; String tag = parser.getName(); @@ -86,10 +161,14 @@ public class FontResourcesParser { skip(parser); } } - return new FontConfig.Family(null, fonts, null, null); + if (fonts.isEmpty()) { + return null; + } + return new FontFamilyFilesResourceEntry(fonts.toArray( + new FontFileResourceEntry[fonts.size()])); } - private static FontConfig.Font readFont(XmlPullParser parser, Resources resources) + private static FontFileResourceEntry readFont(XmlPullParser parser, Resources resources) throws XmlPullParserException, IOException { AttributeSet attrs = Xml.asAttributeSet(parser); TypedArray array = resources.obtainAttributes(attrs, R.styleable.FontFamilyFont); @@ -101,7 +180,7 @@ public class FontResourcesParser { while (parser.next() != XmlPullParser.END_TAG) { skip(parser); } - return new FontConfig.Font(filename, 0, null, weight, isItalic, resourceId); + return new FontFileResourceEntry(filename, weight, isItalic, resourceId); } private static void skip(XmlPullParser parser) throws XmlPullParserException, IOException { diff --git a/core/java/android/content/res/ResourcesImpl.java b/core/java/android/content/res/ResourcesImpl.java index 38efa4901553..949d64458ae5 100644 --- a/core/java/android/content/res/ResourcesImpl.java +++ b/core/java/android/content/res/ResourcesImpl.java @@ -769,8 +769,13 @@ public class ResourcesImpl { if (file.endsWith("xml")) { final XmlResourceParser rp = loadXmlResourceParser( file, id, value.assetCookie, "font"); - final FontConfig config = FontResourcesParser.parse(rp, wrapper); - return Typeface.createFromResources(config, mAssets, file); + final FontResourcesParser.FamilyResourceEntry familyEntry = + FontResourcesParser.parse(rp, wrapper); + if (familyEntry == null) { + Log.e(TAG, "Failed to find font-family tag"); + return null; + } + return Typeface.createFromResources(familyEntry, mAssets, file); } return Typeface.createFromResources(mAssets, file, value.assetCookie); } catch (XmlPullParserException e) { @@ -796,20 +801,23 @@ public class ResourcesImpl { Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, file); try { + // TODO: Stop re-ussing font-family xml tag structure and use ResourceArray instead. final XmlResourceParser rp = loadXmlResourceParser( file, id, value.assetCookie, "font"); - final FontConfig config = FontResourcesParser.parse(rp, wrapper); - final List<FontConfig.Family> families = config.getFamilies(); - if (families == null || families.isEmpty()) { + final FontResourcesParser.FamilyResourceEntry familyEntry = + FontResourcesParser.parse(rp, wrapper); + if (familyEntry == null) { + Log.e(TAG, "failed to find font-family tag"); return; } - for (int j = 0; j < families.size(); j++) { - final FontConfig.Family family = families.get(j); - final List<FontConfig.Font> fonts = family.getFonts(); - for (int i = 0; i < fonts.size(); i++) { - int resourceId = fonts.get(i).getResourceId(); - wrapper.getFont(resourceId); - } + if (familyEntry instanceof FontResourcesParser.ProviderResourceEntry) { + throw new IllegalArgumentException("Provider based fonts can not be used."); + } + final FontResourcesParser.FontFamilyFilesResourceEntry filesEntry = + (FontResourcesParser.FontFamilyFilesResourceEntry) familyEntry; + for (FontResourcesParser.FontFileResourceEntry fileEntry : filesEntry.getEntries()) { + int resourceId = fileEntry.getResourceId(); + wrapper.getFont(resourceId); } } catch (XmlPullParserException e) { Log.e(TAG, "Failed to parse xml resource " + file, e); diff --git a/core/java/android/database/PageViewCursor.java b/core/java/android/database/PageViewCursor.java new file mode 100644 index 000000000000..fbd039d91742 --- /dev/null +++ b/core/java/android/database/PageViewCursor.java @@ -0,0 +1,245 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.database; + +import static com.android.internal.util.Preconditions.checkArgument; + +import android.annotation.Nullable; +import android.content.ContentResolver; +import android.os.Bundle; +import android.util.Log; +import android.util.MathUtils; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.ArrayUtils; + +/** + * Cursor wrapper that provides visibility into a subset of a wrapped cursor. + * + * The window is specified by offset and limit. + * + * @hide + */ +public final class PageViewCursor extends CrossProcessCursorWrapper { + + /** + * An extra added to results that are auto-paged using the wrapper. + */ + public static final String EXTRA_AUTO_PAGED = "android.content.extra.AUTO_PAGED"; + + private static final String TAG = "PageViewCursor"; + private static final boolean DEBUG = false; + private static final boolean VERBOSE = false; + + private final int mOffset; // aka first index + private final int mCount; + private final Bundle mExtras; + + private int mPos = -1; + + /** + * @see PageViewCursor#wrap(Cursor, Bundle) + */ + @VisibleForTesting + public PageViewCursor(Cursor cursor, int offset, int limit) { + super(cursor); + + checkArgument(offset > -1); + checkArgument(limit > -1); + + mOffset = offset; + + mExtras = new Bundle(); + Bundle extras = cursor.getExtras(); + if (extras != null) { + mExtras.putAll(extras); + } + mExtras.putBoolean(EXTRA_AUTO_PAGED, true); + + // We need a mutable bundle so we can add QUERY_RESULT_SIZE. + // Direct equality check is correct here. Bundle.EMPTY is a specific instance + // of Bundle that is immutable by way of implementation. + // mExtras = (extras == Bundle.EMPTY) ? new Bundle() : extras; + + // When we're wrapping another cursor, it should not already be "paged". + checkArgument(!mExtras.containsKey(ContentResolver.EXTRA_TOTAL_SIZE)); + + int count = mCursor.getCount(); + mExtras.putInt(ContentResolver.EXTRA_TOTAL_SIZE, count); + + mCount = MathUtils.constrain(count - offset, 0, limit); + + if (DEBUG) Log.d(TAG, "Wrapped cursor" + + " offset: " + mOffset + + ", limit: " + limit + + ", delegate_size: " + count + + ", paged_count: " + mCount); + } + + @Override + public Bundle getExtras() { + return mExtras; + } + + @Override + public int getPosition() { + return mPos; + } + + @Override + public boolean isBeforeFirst() { + if (mCount == 0) { + return true; + } + return mPos == -1; + } + + @Override + public boolean isAfterLast() { + if (mCount == 0) { + return true; + } + return mPos == mCount; + } + + @Override + public boolean isFirst() { + return mPos == 0; + } + + @Override + public boolean isLast() { + return mPos == mCount - 1; + } + + @Override + public boolean moveToFirst() { + return moveToPosition(0); + } + + @Override + public boolean moveToLast() { + return moveToPosition(mCount - 1); + } + + @Override + public boolean moveToNext() { + return move(1); + } + + @Override + public boolean moveToPrevious() { + return move(-1); + } + + @Override + public boolean move(int offset) { + return moveToPosition(mPos + offset); + } + + @Override + public boolean moveToPosition(int position) { + if (position >= mCount) { + if (VERBOSE) Log.v(TAG, "Invalid Positon: " + position + " >= count: " + mCount + + ". Moving to last record."); + mPos = mCount; + super.moveToPosition(mOffset + mPos); // move into "after last" state. + return false; + } + + // Make sure position isn't before the beginning of the cursor + if (position < 0) { + if (VERBOSE) Log.v(TAG, "Ignoring invalid move to position: " + position); + mPos = -1; + super.moveToPosition(mPos); + return false; + } + + if (position == mPos) { + if (VERBOSE) Log.v(TAG, "Ignoring no-op move to position: " + position); + return true; + } + + int delegatePosition = position + mOffset; + if (VERBOSE) Log.v(TAG, "Moving delegate cursor to position: " + delegatePosition); + if (super.moveToPosition(delegatePosition)) { + mPos = position; + return true; + } else { + mPos = -1; + super.moveToPosition(-1); + return false; + } + } + + @Override + public boolean onMove(int oldPosition, int newPosition) { + throw new UnsupportedOperationException("Not supported."); + } + + @Override + public int getCount() { + return mCount; + } + + /** + * Wraps the cursor such that it will honor paging args (if present), AND if the cursor + * does not report paging size. + * + * <p>No-op if cursor already contains paging or is less than specified page size. + */ + public static Cursor wrap(Cursor cursor, @Nullable Bundle queryArgs) { + + boolean hasPagingArgs = + queryArgs != null + && (queryArgs.containsKey(ContentResolver.QUERY_ARG_OFFSET) + || queryArgs.containsKey(ContentResolver.QUERY_ARG_LIMIT)); + + if (!hasPagingArgs) { + if (VERBOSE) Log.d(TAG, "No-wrap: No paging args in request."); + return cursor; + } + + if (hasPagedResponseDetails(cursor.getExtras())) { + if (VERBOSE) Log.d(TAG, "No-wrap. Cursor has paging details."); + return cursor; + } + + return new PageViewCursor( + cursor, + queryArgs.getInt(ContentResolver.QUERY_ARG_OFFSET, 0), + queryArgs.getInt(ContentResolver.QUERY_ARG_LIMIT, Integer.MAX_VALUE)); + } + + /** + * @return true if the extras contains information indicating the associated + * cursor is paged. + */ + private static boolean hasPagedResponseDetails(@Nullable Bundle extras) { + if (extras != null && extras.containsKey(ContentResolver.EXTRA_TOTAL_SIZE)) { + return true; + } + + String[] honoredArgs = extras.getStringArray(ContentResolver.EXTRA_HONORED_ARGS); + if (honoredArgs != null && ( + ArrayUtils.contains(honoredArgs, ContentResolver.QUERY_ARG_OFFSET) + || ArrayUtils.contains(honoredArgs, ContentResolver.QUERY_ARG_LIMIT))) { + return true; + } + + return false; + } +} diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java index 8bc65af65ef1..e3b97e82f6c8 100644 --- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java +++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java @@ -100,6 +100,8 @@ public class CameraDeviceImpl extends CameraDevice private final CameraCharacteristics mCharacteristics; private final int mTotalPartialCount; + private static final long NANO_PER_SECOND = 1000000000; //ns + /** * A list tracking request and its expected last regular frame number and last reprocess frame * number. Updated when calling ICameraDeviceUser methods. @@ -1239,6 +1241,14 @@ public class CameraDeviceImpl extends CameraDevice private final List<CaptureRequest> mRequestList; private final Handler mHandler; private final int mSessionId; + /** + * <p>Determine if the callback holder is for a constrained high speed request list that + * expects batched capture results. Capture results will be batched if the request list + * is interleaved with preview and video requests. Capture results won't be batched if the + * request list only contains preview requests, or if the request doesn't belong to a + * constrained high speed list. + */ + private final boolean mHasBatchedOutputs; CaptureCallbackHolder(CaptureCallback callback, List<CaptureRequest> requestList, Handler handler, boolean repeating, int sessionId) { @@ -1251,6 +1261,25 @@ public class CameraDeviceImpl extends CameraDevice mRequestList = new ArrayList<CaptureRequest>(requestList); mCallback = callback; mSessionId = sessionId; + + // Check whether this callback holder is for batched outputs. + // The logic here should match createHighSpeedRequestList. + boolean hasBatchedOutputs = true; + for (int i = 0; i < requestList.size(); i++) { + CaptureRequest request = requestList.get(i); + if (!request.isPartOfCRequestList()) { + hasBatchedOutputs = false; + break; + } + if (i == 0) { + Collection<Surface> targets = request.getTargets(); + if (targets.size() != 2) { + hasBatchedOutputs = false; + break; + } + } + } + mHasBatchedOutputs = hasBatchedOutputs; } public boolean isRepeating() { @@ -1288,6 +1317,14 @@ public class CameraDeviceImpl extends CameraDevice public int getSessionId() { return mSessionId; } + + public int getRequestCount() { + return mRequestList.size(); + } + + public boolean hasBatchedOutputs() { + return mHasBatchedOutputs; + } } /** @@ -1777,10 +1814,27 @@ public class CameraDeviceImpl extends CameraDevice @Override public void run() { if (!CameraDeviceImpl.this.isClosed()) { - holder.getCallback().onCaptureStarted( - CameraDeviceImpl.this, - holder.getRequest(resultExtras.getSubsequenceId()), - timestamp, frameNumber); + final int subsequenceId = resultExtras.getSubsequenceId(); + final CaptureRequest request = holder.getRequest(subsequenceId); + + if (holder.hasBatchedOutputs()) { + // Send derived onCaptureStarted for requests within the batch + final Range<Integer> fpsRange = + request.get(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE); + for (int i = 0; i < holder.getRequestCount(); i++) { + holder.getCallback().onCaptureStarted( + CameraDeviceImpl.this, + holder.getRequest(i), + timestamp - (subsequenceId - i) * + NANO_PER_SECOND/fpsRange.getUpper(), + frameNumber - (subsequenceId - i)); + } + } else { + holder.getCallback().onCaptureStarted( + CameraDeviceImpl.this, + holder.getRequest(resultExtras.getSubsequenceId()), + timestamp, frameNumber); + } } } }); @@ -1845,46 +1899,91 @@ public class CameraDeviceImpl extends CameraDevice Runnable resultDispatch = null; CaptureResult finalResult; + // Make a copy of the native metadata before it gets moved to a CaptureResult + // object. + final CameraMetadataNative resultCopy; + if (holder.hasBatchedOutputs()) { + resultCopy = new CameraMetadataNative(result); + } else { + resultCopy = null; + } // Either send a partial result or the final capture completed result if (isPartialResult) { final CaptureResult resultAsCapture = new CaptureResult(result, request, resultExtras); - // Partial result resultDispatch = new Runnable() { @Override public void run() { - if (!CameraDeviceImpl.this.isClosed()){ - holder.getCallback().onCaptureProgressed( - CameraDeviceImpl.this, - request, - resultAsCapture); + if (!CameraDeviceImpl.this.isClosed()) { + if (holder.hasBatchedOutputs()) { + // Send derived onCaptureProgressed for requests within + // the batch. + for (int i = 0; i < holder.getRequestCount(); i++) { + CameraMetadataNative resultLocal = + new CameraMetadataNative(resultCopy); + CaptureResult resultInBatch = new CaptureResult( + resultLocal, holder.getRequest(i), resultExtras); + + holder.getCallback().onCaptureProgressed( + CameraDeviceImpl.this, + holder.getRequest(i), + resultInBatch); + } + } else { + holder.getCallback().onCaptureProgressed( + CameraDeviceImpl.this, + request, + resultAsCapture); + } } } }; - finalResult = resultAsCapture; } else { List<CaptureResult> partialResults = mFrameNumberTracker.popPartialResults(frameNumber); + final long sensorTimestamp = + result.get(CaptureResult.SENSOR_TIMESTAMP); + final Range<Integer> fpsRange = + request.get(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE); + final int subsequenceId = resultExtras.getSubsequenceId(); final TotalCaptureResult resultAsCapture = new TotalCaptureResult(result, request, resultExtras, partialResults, holder.getSessionId()); - // Final capture result resultDispatch = new Runnable() { @Override public void run() { if (!CameraDeviceImpl.this.isClosed()){ - holder.getCallback().onCaptureCompleted( - CameraDeviceImpl.this, - request, - resultAsCapture); + if (holder.hasBatchedOutputs()) { + // Send derived onCaptureCompleted for requests within + // the batch. + for (int i = 0; i < holder.getRequestCount(); i++) { + resultCopy.set(CaptureResult.SENSOR_TIMESTAMP, + sensorTimestamp - (subsequenceId - i) * + NANO_PER_SECOND/fpsRange.getUpper()); + CameraMetadataNative resultLocal = + new CameraMetadataNative(resultCopy); + TotalCaptureResult resultInBatch = new TotalCaptureResult( + resultLocal, holder.getRequest(i), resultExtras, + partialResults, holder.getSessionId()); + + holder.getCallback().onCaptureCompleted( + CameraDeviceImpl.this, + holder.getRequest(i), + resultInBatch); + } + } else { + holder.getCallback().onCaptureCompleted( + CameraDeviceImpl.this, + request, + resultAsCapture); + } } } }; - finalResult = resultAsCapture; } diff --git a/core/java/android/net/ConnectivityMetricsEvent.java b/core/java/android/net/ConnectivityMetricsEvent.java index 6fdc739a5f54..4faff62c2655 100644 --- a/core/java/android/net/ConnectivityMetricsEvent.java +++ b/core/java/android/net/ConnectivityMetricsEvent.java @@ -19,39 +19,40 @@ package android.net; import android.os.Parcel; import android.os.Parcelable; -/** {@hide} */ +/** + * Represents a core networking event defined in package android.net.metrics. + * Logged by IpConnectivityLog and managed by ConnectivityMetrics service. + * {@hide} + * */ public final class ConnectivityMetricsEvent implements Parcelable { - /** The time when this event was collected, as returned by System.currentTimeMillis(). */ - final public long timestamp; - - /** The subsystem that generated the event. One of the COMPONENT_TAG_xxx constants. */ - final public int componentTag; - - /** The subsystem-specific event ID. */ - final public int eventTag; - + /** Time when this event was collected, as returned by System.currentTimeMillis(). */ + public long timestamp; + /** Transports of the network associated with the event, as defined in NetworkCapabilities. */ + public long transports; + /** Network id of the network associated with the event, or 0 if unspecified. */ + public int netId; + /** Name of the network interface associated with the event, or null if unspecified. */ + public String ifname; /** Opaque event-specific data. */ - final public Parcelable data; + public Parcelable data; - public ConnectivityMetricsEvent(long timestamp, int componentTag, - int eventTag, Parcelable data) { - this.timestamp = timestamp; - this.componentTag = componentTag; - this.eventTag = eventTag; - this.data = data; + public ConnectivityMetricsEvent() { + } + + private ConnectivityMetricsEvent(Parcel in) { + timestamp = in.readLong(); + transports = in.readLong(); + netId = in.readInt(); + ifname = in.readString(); + data = in.readParcelable(null); } /** Implement the Parcelable interface */ public static final Parcelable.Creator<ConnectivityMetricsEvent> CREATOR = new Parcelable.Creator<ConnectivityMetricsEvent> (){ public ConnectivityMetricsEvent createFromParcel(Parcel source) { - final long timestamp = source.readLong(); - final int componentTag = source.readInt(); - final int eventTag = source.readInt(); - final Parcelable data = source.readParcelable(null); - return new ConnectivityMetricsEvent(timestamp, componentTag, - eventTag, data); + return new ConnectivityMetricsEvent(source); } public ConnectivityMetricsEvent[] newArray(int size) { @@ -59,7 +60,6 @@ public final class ConnectivityMetricsEvent implements Parcelable { } }; - /** Implement the Parcelable interface */ @Override public int describeContents() { return 0; @@ -68,13 +68,15 @@ public final class ConnectivityMetricsEvent implements Parcelable { @Override public void writeToParcel(Parcel dest, int flags) { dest.writeLong(timestamp); - dest.writeInt(componentTag); - dest.writeInt(eventTag); + dest.writeLong(transports); + dest.writeInt(netId); + dest.writeString(ifname); dest.writeParcelable(data, 0); } + @Override public String toString() { - return String.format("ConnectivityMetricsEvent(%tT.%tL, %d, %d): %s", - timestamp, timestamp, componentTag, eventTag, data); + // TODO: add transports, netId, ifname + return String.format("ConnectivityMetricsEvent(%tT.%tL): %s", timestamp, timestamp, data); } } diff --git a/core/java/android/net/metrics/ApfProgramEvent.java b/core/java/android/net/metrics/ApfProgramEvent.java index c2795a2ab956..ad4588f139f9 100644 --- a/core/java/android/net/metrics/ApfProgramEvent.java +++ b/core/java/android/net/metrics/ApfProgramEvent.java @@ -47,23 +47,19 @@ public final class ApfProgramEvent implements Parcelable { @Retention(RetentionPolicy.SOURCE) public @interface Flags {} - public final long lifetime; // Lifetime of the program in seconds - public final int filteredRas; // Number of RAs filtered by the APF program - public final int currentRas; // Total number of current RAs at generation time - public final int programLength; // Length of the APF program in bytes - public final int flags; // Bitfield compound of FLAG_* constants - - public ApfProgramEvent( - long lifetime, int filteredRas, int currentRas, int programLength, @Flags int flags) { - this.lifetime = lifetime; - this.filteredRas = filteredRas; - this.currentRas = currentRas; - this.programLength = programLength; - this.flags = flags; + public long lifetime; // Maximum computed lifetime of the program in seconds + public long actualLifetime; // Effective program lifetime in seconds + public int filteredRas; // Number of RAs filtered by the APF program + public int currentRas; // Total number of current RAs at generation time + public int programLength; // Length of the APF program in bytes + public int flags; // Bitfield compound of FLAG_* constants + + public ApfProgramEvent() { } private ApfProgramEvent(Parcel in) { this.lifetime = in.readLong(); + this.actualLifetime = in.readLong(); this.filteredRas = in.readInt(); this.currentRas = in.readInt(); this.programLength = in.readInt(); @@ -73,6 +69,7 @@ public final class ApfProgramEvent implements Parcelable { @Override public void writeToParcel(Parcel out, int flags) { out.writeLong(lifetime); + out.writeLong(actualLifetime); out.writeInt(filteredRas); out.writeInt(currentRas); out.writeInt(programLength); @@ -87,8 +84,8 @@ public final class ApfProgramEvent implements Parcelable { @Override public String toString() { String lifetimeString = (lifetime < Long.MAX_VALUE) ? lifetime + "s" : "forever"; - return String.format("ApfProgramEvent(%d/%d RAs %dB %s %s)", - filteredRas, currentRas, programLength, lifetimeString, namesOf(flags)); + return String.format("ApfProgramEvent(%d/%d RAs %dB %ds/%s %s)", filteredRas, currentRas, + programLength, actualLifetime, lifetimeString, namesOf(flags)); } public static final Parcelable.Creator<ApfProgramEvent> CREATOR diff --git a/core/java/android/net/metrics/ApfStats.java b/core/java/android/net/metrics/ApfStats.java index f8d7fa9d2e8d..3b0dc7efc45e 100644 --- a/core/java/android/net/metrics/ApfStats.java +++ b/core/java/android/net/metrics/ApfStats.java @@ -25,25 +25,28 @@ import android.os.Parcelable; */ public final class ApfStats implements Parcelable { - public final long durationMs; // time interval in milliseconds these stastistics covers - public final int receivedRas; // number of received RAs - public final int matchingRas; // number of received RAs matching a known RA - public final int droppedRas; // number of received RAs ignored due to the MAX_RAS limit - public final int zeroLifetimeRas; // number of received RAs with a minimum lifetime of 0 - public final int parseErrors; // number of received RAs that could not be parsed - public final int programUpdates; // number of APF program updates - public final int maxProgramSize; // maximum APF program size advertised by hardware + /** time interval in milliseconds these stastistics covers. */ + public long durationMs; + /** number of received RAs. */ + public int receivedRas; + /** number of received RAs matching a known RA. */ + public int matchingRas; + /** number of received RAs ignored due to the MAX_RAS limit. */ + public int droppedRas; + /** number of received RAs with a minimum lifetime of 0. */ + public int zeroLifetimeRas; + /** number of received RAs that could not be parsed. */ + public int parseErrors; + /** number of APF program updates from receiving RAs.. */ + public int programUpdates; + /** total number of APF program updates. */ + public int programUpdatesAll; + /** number of APF program updates from allowing multicast traffic. */ + public int programUpdatesAllowingMulticast; + /** maximum APF program size advertised by hardware. */ + public int maxProgramSize; - public ApfStats(long durationMs, int receivedRas, int matchingRas, int droppedRas, - int zeroLifetimeRas, int parseErrors, int programUpdates, int maxProgramSize) { - this.durationMs = durationMs; - this.receivedRas = receivedRas; - this.matchingRas = matchingRas; - this.droppedRas = droppedRas; - this.zeroLifetimeRas = zeroLifetimeRas; - this.parseErrors = parseErrors; - this.programUpdates = programUpdates; - this.maxProgramSize = maxProgramSize; + public ApfStats() { } private ApfStats(Parcel in) { @@ -54,6 +57,8 @@ public final class ApfStats implements Parcelable { this.zeroLifetimeRas = in.readInt(); this.parseErrors = in.readInt(); this.programUpdates = in.readInt(); + this.programUpdatesAll = in.readInt(); + this.programUpdatesAllowingMulticast = in.readInt(); this.maxProgramSize = in.readInt(); } @@ -66,6 +71,8 @@ public final class ApfStats implements Parcelable { out.writeInt(zeroLifetimeRas); out.writeInt(parseErrors); out.writeInt(programUpdates); + out.writeInt(programUpdatesAll); + out.writeInt(programUpdatesAllowingMulticast); out.writeInt(maxProgramSize); } @@ -83,8 +90,9 @@ public final class ApfStats implements Parcelable { .append(String.format("%d matching, ", matchingRas)) .append(String.format("%d dropped, ", droppedRas)) .append(String.format("%d zero lifetime, ", zeroLifetimeRas)) - .append(String.format("%d parse errors, ", parseErrors)) - .append(String.format("%d program updates})", programUpdates)) + .append(String.format("%d parse errors}, ", parseErrors)) + .append(String.format("updates: {all: %d, RAs: %d, allow multicast: %d})", + programUpdatesAll, programUpdates, programUpdatesAllowingMulticast)) .toString(); } diff --git a/core/java/android/net/metrics/IpConnectivityLog.java b/core/java/android/net/metrics/IpConnectivityLog.java index 173e5fd0fbc1..79094c02b272 100644 --- a/core/java/android/net/metrics/IpConnectivityLog.java +++ b/core/java/android/net/metrics/IpConnectivityLog.java @@ -60,21 +60,23 @@ public class IpConnectivityLog { } /** - * Log an IpConnectivity event. - * @param timestamp is the epoch timestamp of the event in ms. - * @param data is a Parcelable instance representing the event. + * Log a ConnectivityMetricsEvent. + * @param ev the event to log. If the event timestamp is 0, + * the timestamp is set to the current time in milliseconds. * @return true if the event was successfully logged. */ - public boolean log(long timestamp, Parcelable data) { + public boolean log(ConnectivityMetricsEvent ev) { if (!checkLoggerService()) { if (DBG) { Log.d(TAG, SERVICE_NAME + " service was not ready"); } return false; } - + if (ev.timestamp == 0) { + ev.timestamp = System.currentTimeMillis(); + } try { - int left = mService.logEvent(new ConnectivityMetricsEvent(timestamp, 0, 0, data)); + int left = mService.logEvent(ev); return left >= 0; } catch (RemoteException e) { Log.e(TAG, "Error logging event", e); @@ -82,7 +84,31 @@ public class IpConnectivityLog { } } - public void log(Parcelable event) { - log(System.currentTimeMillis(), event); + /** + * Log an IpConnectivity event. + * @param timestamp is the epoch timestamp of the event in ms. + * If the timestamp is 0, the timestamp is set to the current time in milliseconds. + * @param data is a Parcelable instance representing the event. + * @return true if the event was successfully logged. + */ + public boolean log(long timestamp, Parcelable data) { + ConnectivityMetricsEvent ev = makeEv(data); + ev.timestamp = timestamp; + return log(ev); + } + + /** + * Log an IpConnectivity event. + * @param data is a Parcelable instance representing the event. + * @return true if the event was successfully logged. + */ + public boolean log(Parcelable data) { + return log(makeEv(data)); + } + + private static ConnectivityMetricsEvent makeEv(Parcelable data) { + ConnectivityMetricsEvent ev = new ConnectivityMetricsEvent(); + ev.data = data; + return ev; } } diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java index dc170ed259de..29884b132e5a 100644 --- a/core/java/android/os/BatteryStats.java +++ b/core/java/android/os/BatteryStats.java @@ -2042,8 +2042,8 @@ public abstract class BatteryStats implements Parcelable { public static final String[] HISTORY_EVENT_NAMES = new String[] { "null", "proc", "fg", "top", "sync", "wake_lock_in", "job", "user", "userfg", "conn", - "active", "pkginst", "pkgunin", "alarm", "stats", "inactive", "active", "tmpwhitelist", - "screenwake", "wakeupap", "longwake", "est_capacity" + "active", "pkginst", "pkgunin", "alarm", "stats", "pkginactive", "pkgactive", + "tmpwhitelist", "screenwake", "wakeupap", "longwake", "est_capacity" }; public static final String[] HISTORY_EVENT_CHECKIN_NAMES = new String[] { diff --git a/core/java/android/os/HwParcel.java b/core/java/android/os/HwParcel.java index a265dd08a92f..94fd5b074b82 100644 --- a/core/java/android/os/HwParcel.java +++ b/core/java/android/os/HwParcel.java @@ -219,6 +219,7 @@ public class HwParcel { public native final void writeStatus(int status); public native final void verifySuccess(); public native final void releaseTemporaryStorage(); + public native final void release(); public native final void send(); diff --git a/core/java/android/os/SystemProperties.java b/core/java/android/os/SystemProperties.java index b525193c1b9c..8632194f2b02 100644 --- a/core/java/android/os/SystemProperties.java +++ b/core/java/android/os/SystemProperties.java @@ -35,6 +35,12 @@ public class SystemProperties { private static final String TAG = "SystemProperties"; private static final boolean TRACK_KEY_ACCESS = false; + /** + * Android O removed the property name length limit, but com.amazon.kindle 7.8.1.5 + * uses reflection to read this whenever text is selected (http://b/36095274). + */ + public static final int PROP_NAME_MAX = Integer.MAX_VALUE; + public static final int PROP_VALUE_MAX = 91; private static final ArrayList<Runnable> sChangeCallbacks = new ArrayList<Runnable>(); diff --git a/core/java/android/os/storage/StorageVolume.java b/core/java/android/os/storage/StorageVolume.java index 46f2d387ac52..1fc0b820cf06 100644 --- a/core/java/android/os/storage/StorageVolume.java +++ b/core/java/android/os/storage/StorageVolume.java @@ -373,8 +373,7 @@ public final class StorageVolume implements Parcelable { } /** {@hide} */ - // TODO(b/26742218): find out where toString() is called internally and replace these calls by - // dump(). + // TODO: find out where toString() is called internally and replace these calls by dump(). public String dump() { final CharArrayWriter writer = new CharArrayWriter(); dump(new IndentingPrintWriter(writer, " ", 80)); diff --git a/core/java/android/provider/ContactsContract.java b/core/java/android/provider/ContactsContract.java index a0d16bc2d9b0..dac835463c02 100644 --- a/core/java/android/provider/ContactsContract.java +++ b/core/java/android/provider/ContactsContract.java @@ -44,7 +44,6 @@ import android.text.TextUtils; import android.util.DisplayMetrics; import android.util.Pair; import android.view.View; - import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; @@ -8913,11 +8912,15 @@ public final class ContactsContract { * ambiguous then the activity should prompt the user for the recipient to send the message * to. * <p> + * Voice Assistant may provide additional information to messaging app about which account + * to use for sending a message by populating {@link #EXTRA_SENDER_ACCOUNT_HASH}. + * <p> * Output: nothing * * @see #EXTRA_RECIPIENT_CONTACT_URI * @see #EXTRA_RECIPIENT_CONTACT_CHAT_ID * @see #EXTRA_RECIPIENT_CONTACT_NAME + * @see #EXTRA_SENDER_ACCOUNT_HASH * @see #METADATA_ACCOUNT_TYPE * @see #METADATA_MIMETYPE */ @@ -8975,6 +8978,16 @@ public final class ContactsContract { "android.provider.extra.RECIPIENT_CONTACT_NAME"; /** + * This optional extra specifies the hash of the account that should be used by messaging + * app for sending voice message with {@link #ACTION_VOICE_SEND_MESSAGE_TO_CONTACTS}. The + * value of this extra is a {@code String} and should be the value of {@link + * android.accounts.Account#hashCode()} for some account returned by {@link + * android.accounts.AccountManager#getAccounts()}. + */ + public static final String EXTRA_SENDER_ACCOUNT_HASH = + "android.provider.extra.SENDER_ACCOUNT_HASH"; + + /** * A string associated with an {@link #ACTION_VOICE_SEND_MESSAGE_TO_CONTACTS} activity * describing {@link RawContacts#ACCOUNT_TYPE} for the corresponding Contacts Provider * implementation. diff --git a/core/java/android/provider/DocumentsContract.java b/core/java/android/provider/DocumentsContract.java index f53b0d77880d..56d4ff79eb04 100644 --- a/core/java/android/provider/DocumentsContract.java +++ b/core/java/android/provider/DocumentsContract.java @@ -1563,7 +1563,7 @@ public final class DocumentsContract { if (resolver.getTargetSdkVersion() >= Build.VERSION_CODES.O) { if (e instanceof ParcelableException) { ((ParcelableException) e).maybeRethrow(FileNotFoundException.class); - } else if (e instanceof RemoteException ) { + } else if (e instanceof RemoteException) { ((RemoteException) e).rethrowAsRuntimeException(); } else if (e instanceof RuntimeException) { throw (RuntimeException) e; diff --git a/core/java/android/provider/FontsContract.java b/core/java/android/provider/FontsContract.java index 9f5d9d45c974..84443e9e3f55 100644 --- a/core/java/android/provider/FontsContract.java +++ b/core/java/android/provider/FontsContract.java @@ -138,12 +138,8 @@ public class FontsContract { private HandlerThread mThread; /** @hide */ - public FontsContract() { - // TODO: investigate if the system context is the best option here. ApplicationContext or - // the one passed by developer? - // TODO: Looks like ActivityThread.currentActivityThread() can return null. Check when it - // returns null and check if we need to handle null case. - mContext = ActivityThread.currentActivityThread().getSystemContext(); + public FontsContract(Context context) { + mContext = context.getApplicationContext(); mPackageManager = mContext.getPackageManager(); } diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 8a2a14c1f8a1..391ee835e92a 100755 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -5605,10 +5605,10 @@ public final class Settings { "accessibility_web_content_key_bindings"; /** - * Setting that specifies whether the display magnification is enabled. - * Display magnifications allows the user to zoom in the display content - * and is targeted to low vision users. The current magnification scale - * is controlled by {@link #ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE}. + * Setting that specifies whether the display magnification is enabled via a system-wide + * triple tap gesture. Display magnifications allows the user to zoom in the display content + * and is targeted to low vision users. The current magnification scale is controlled by + * {@link #ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE}. * * @hide */ @@ -5616,11 +5616,23 @@ public final class Settings { "accessibility_display_magnification_enabled"; /** + * Setting that specifies whether the display magnification is enabled via a shortcut + * affordance within the system's navigation area. Display magnifications allows the user to + * zoom in the display content and is targeted to low vision users. The current + * magnification scale is controlled by {@link #ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE}. + * + * @hide + */ + public static final String ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED = + "accessibility_display_magnification_navbar_enabled"; + + /** * Setting that specifies what the display magnification scale is. * Display magnifications allows the user to zoom in the display * content and is targeted to low vision users. Whether a display * magnification is performed is controlled by - * {@link #ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED} + * {@link #ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED} and + * {@link #ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED} * * @hide */ @@ -6950,6 +6962,7 @@ public final class Settings { ACCESSIBILITY_DISPLAY_DALTONIZER, ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED, ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED, + ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED, ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE, ACCESSIBILITY_SCRIPT_INJECTION, ACCESSIBILITY_WEB_CONTENT_KEY_BINDINGS, diff --git a/core/java/android/service/autofill/AutofillService.java b/core/java/android/service/autofill/AutofillService.java index 29e207358e70..709e5f9e7ef7 100644 --- a/core/java/android/service/autofill/AutofillService.java +++ b/core/java/android/service/autofill/AutofillService.java @@ -30,6 +30,7 @@ import android.os.IBinder; import android.os.ICancellationSignal; import android.os.Looper; import android.util.Log; +import android.view.autofill.AutofillManager; import com.android.internal.os.SomeArgs; @@ -90,6 +91,8 @@ public abstract class AutofillService extends Service { private static final int MSG_ON_FILL_REQUEST = 3; private static final int MSG_ON_SAVE_REQUEST = 4; + private static final int UNUSED_ARG = -1; + private final IAutoFillService mInterface = new IAutoFillService.Stub() { @Override public void onInit(IAutoFillServiceConnection connection) { @@ -102,14 +105,14 @@ public abstract class AutofillService extends Service { @Override public void onFillRequest(AssistStructure structure, Bundle extras, - IFillCallback callback) { + IFillCallback callback, int flags) { ICancellationSignal transport = CancellationSignal.createTransport(); try { callback.onCancellable(transport); } catch (RemoteException e) { e.rethrowFromSystemServer(); } - mHandlerCaller.obtainMessageOOOO(MSG_ON_FILL_REQUEST, structure, + mHandlerCaller.obtainMessageIIOOOO(MSG_ON_FILL_REQUEST, flags, UNUSED_ARG, structure, CancellationSignal.fromTransport(transport), extras, callback) .sendToTarget(); } @@ -135,8 +138,9 @@ public abstract class AutofillService extends Service { final Bundle extras = (Bundle) args.arg3; final IFillCallback callback = (IFillCallback) args.arg4; final FillCallback fillCallback = new FillCallback(callback); + final int flags = msg.arg1; args.recycle(); - onFillRequest(structure, extras, cancellation, fillCallback); + onFillRequest(structure, extras, flags, cancellation, fillCallback); break; } case MSG_ON_SAVE_REQUEST: { final SomeArgs args = (SomeArgs) msg.obj; @@ -188,7 +192,6 @@ public abstract class AutofillService extends Service { * <p>You should generally do initialization here rather than in {@link #onCreate}. */ public void onConnected() { - //TODO(b/33197203): is not called anymore, fix it! } /** @@ -206,11 +209,25 @@ public abstract class AutofillService extends Service { * as well as when filling different sections of the UI as the system will try to * aggressively unbind from the service to conserve resources. See {@link * FillResponse} Javadoc for examples of multiple-sections requests. + * @param flags either {@code 0} or {@link AutofillManager#FLAG_MANUAL_REQUEST}. * @param cancellationSignal signal for observing cancellation requests. The system will use * this to notify you that the fill result is no longer needed and you should stop * handling this fill request in order to save resources. * @param callback object used to notify the result of the request. */ + public void onFillRequest(@NonNull AssistStructure structure, @Nullable Bundle data, int flags, + @NonNull CancellationSignal cancellationSignal, @NonNull FillCallback callback) { + //TODO(b/33197203): make non-abstract once older method is removed + onFillRequest(structure, data, cancellationSignal, callback); + } + + /** + * @hide + * @deprecated - use {@link #onFillRequest(AssistStructure, Bundle, int, + * CancellationSignal, FillCallback)} instead + */ + //TODO(b/33197203): remove once clients are not using anymore + @Deprecated public abstract void onFillRequest(@NonNull AssistStructure structure, @Nullable Bundle data, @NonNull CancellationSignal cancellationSignal, @NonNull FillCallback callback); @@ -238,7 +255,6 @@ public abstract class AutofillService extends Service { * <p> At this point this service may no longer be an active {@link AutofillService}. */ public void onDisconnected() { - //TODO(b/33197203): is not called anymore, fix it! } /** diff --git a/core/java/android/service/autofill/AutofillServiceInfo.java b/core/java/android/service/autofill/AutofillServiceInfo.java index d2200528aedb..f6d40dbf3414 100644 --- a/core/java/android/service/autofill/AutofillServiceInfo.java +++ b/core/java/android/service/autofill/AutofillServiceInfo.java @@ -78,14 +78,12 @@ public final class AutofillServiceInfo { // TODO(b/35956626): inline newSettingsActivity once clients migrate final String newSettingsActivity = metaDataArray.getString(R.styleable.AutofillService_settingsActivity); - System.out.println(">>> NEW CRAP MAN: " + newSettingsActivity); // TODO(felipeal): tmp if (newSettingsActivity != null) { mSettingsActivity = newSettingsActivity; } else { mSettingsActivity = metaDataArray.getString(R.styleable.AutoFillService_settingsActivity); } - System.out.println(">>> FINAL CRAP MAN: " + mSettingsActivity); // TODO(felipeal): tmp metaDataArray.recycle(); } else { mSettingsActivity = null; diff --git a/core/java/android/service/autofill/Dataset.java b/core/java/android/service/autofill/Dataset.java index 246194706989..ebe02c2aa9b6 100644 --- a/core/java/android/service/autofill/Dataset.java +++ b/core/java/android/service/autofill/Dataset.java @@ -167,6 +167,7 @@ public final class Dataset implements Parcelable { public @NonNull Builder setValue(@NonNull AutoFillId id, @NonNull AutoFillValue value) { return setValue(id.getDaRealId(), value.getDaRealValue()); } + /** * Sets the value of a field. * diff --git a/core/java/android/service/autofill/FillCallback.java b/core/java/android/service/autofill/FillCallback.java index 00b206c23ba9..e8ad14f55261 100644 --- a/core/java/android/service/autofill/FillCallback.java +++ b/core/java/android/service/autofill/FillCallback.java @@ -37,7 +37,7 @@ public final class FillCallback { /** * Notifies the Android System that an * {@link AutofillService#onFillRequest(android.app.assist.AssistStructure, Bundle, - * android.os.CancellationSignal, FillCallback)} was successfully fulfilled by the service. + * int, android.os.CancellationSignal, FillCallback)} was successfully fulfilled by the service. * * @param response autofill information for that activity, or {@code null} when the activity * cannot be autofilled (for example, if it only contains read-only fields). See @@ -56,7 +56,7 @@ public final class FillCallback { /** * Notifies the Android System that an * {@link AutofillService#onFillRequest(android.app.assist.AssistStructure, - * Bundle, android.os.CancellationSignal, FillCallback)} + * Bundle, int, android.os.CancellationSignal, FillCallback)} * could not be fulfilled by the service. * * @param message error message to be displayed to the user. diff --git a/core/java/android/service/autofill/FillResponse.java b/core/java/android/service/autofill/FillResponse.java index 069e83c6c15c..c43019dc297d 100644 --- a/core/java/android/service/autofill/FillResponse.java +++ b/core/java/android/service/autofill/FillResponse.java @@ -31,7 +31,7 @@ import java.util.ArrayList; /** * Response for a {@link * AutofillService#onFillRequest(android.app.assist.AssistStructure, - * Bundle, android.os.CancellationSignal, FillCallback)}. + * Bundle, int, android.os.CancellationSignal, FillCallback)}. * * <p>The response typically contains one or more {@link Dataset}s, each representing a set of * fields that can be autofilled together, and the Android system displays a dataset picker UI @@ -44,8 +44,8 @@ import java.util.ArrayList; * <pre class="prettyprint"> * new FillResponse.Builder() * .add(new Dataset.Builder(createPresentation()) - * .setTextFieldValue(id1, "homer") - * .setTextFieldValue(id2, "D'OH!") + * .setValue(id1, AutofillValue.forText("homer")) + * .setValue(id2, AutofillValue.forText("D'OH!")) * .build()) * .build(); * </pre> @@ -55,48 +55,19 @@ import java.util.ArrayList; * <pre class="prettyprint"> * new FillResponse.Builder() * .add(new Dataset.Builder(createFirstPresentation()) - * .setTextFieldValue(id1, "homer") - * .setTextFieldValue(id2, "D'OH!") + * .setValue(id1, AutofillValue.forText("homer")) + * .setValue(id2, AutofillValue.forText("D'OH!")) * .build()) * .add(new Dataset.Builder(createSecondPresentation()) - * .setTextFieldValue(id1, "elbarto") - * .setTextFieldValue(id2, "cowabonga") + * .setValue(id1, AutofillValue.forText("elbarto") + * .setValue(id2, AutofillValue.forText("cowabonga") * .build()) * .build(); * </pre> * - * <p>If the user does not have any data associated with this {@link android.app.Activity} but - * the service wants to offer the user the option to save the data that was entered, then the - * service could populate the response with a {@link SaveInfo} instead of {@link Dataset}s: - * - * <pre class="prettyprint"> - * new FillResponse.Builder() - * .setSaveInfo(new SaveInfo.Builder(SaveInfo.SAVE_INFO_TYPE_CREDENTIALS) - * .addSavableFields(id1, id2)) - * .build(); - * </pre> - * - * <p>Similarly, there might be cases where the user data on the service is enough to populate some - * fields but not all, and the service would still be interested on saving the other fields. In this - * scenario, the service could populate the response with both {@link Dataset}s and - * {@link SaveInfo}: - * - * <pre class="prettyprint"> - * new FillResponse.Builder() - * .add(new Dataset.Builder(createPresentation()) - * .setTextFieldValue(id1, "Homer") // first name - * .setTextFieldValue(id2, "Simpson") // last name - * .setTextFieldValue(id3, "742 Evergreen Terrace") // street - * .setTextFieldValue(id4, "Springfield") // city - * .build()) - * .setSaveInfo(new SaveInfo.Builder(SaveInfo.SAVE_INFO_TYPE_ADDRESS) - * .addSavableFields(id5, id6)) // state and zipcode - * .build(); - * - * </pre> - * - * <p>Notice that the ids that are part of a dataset (ids 1 to 4, in this example) are automatically - * added to the {@code savableIds} list. + * If the service is interested on saving the user-edited data back, it must set a {@link SaveInfo} + * in the {@link FillResponse}. Typically, the {@link SaveInfo} contains the same ids as the + * {@link Dataset}, but other combinations are possible - see {@link SaveInfo} for more details * * <p>If the service has multiple {@link Dataset}s for different sections of the activity, * for example, a user section for which there are two datasets followed by an address @@ -113,12 +84,12 @@ import java.util.ArrayList; * <pre class="prettyprint"> * new FillResponse.Builder() * .add(new Dataset.Builder(createFirstPresentation()) - * .setTextFieldValue(id1, "Homer") - * .setTextFieldValue(id2, "Simpson") + * .setValue(id1, AutofillValue.forText("Homer")) + * .setValue(id2, AutofillValue.forText("Simpson")) * .build()) * .add(new Dataset.Builder(createSecondPresentation()) - * .setTextFieldValue(id1, "Bart") - * .setTextFieldValue(id2, "Simpson") + * .setValue(id1, AutofillValue.forText("Bart")) + * .setValue(id2, AutofillValue.forText("Simpson")) * .build()) * .build(); * </pre> @@ -129,12 +100,12 @@ import java.util.ArrayList; * <pre class="prettyprint"> * new FillResponse.Builder() * .add(new Dataset.Builder(createThirdPresentation()) - * .setTextFieldValue(id3, "742 Evergreen Terrace") - * .setTextFieldValue(id4, "Springfield") + * .setValue(id3, AutofillValue.forText("742 Evergreen Terrace")) + * .setValue(id4, AutofillValue.forText("Springfield")) * .build()) * .add(new Dataset.Builder(createFourthPresentation()) - * .setTextFieldValue(id3, "Springfield Power Plant") - * .setTextFieldValue(id4, "Springfield") + * .setValue(id3, AutofillValue.forText("Springfield Power Plant")) + * .setValue(id4, AutofillValue.forText("Springfield")) * .build()) * .build(); * </pre> @@ -167,16 +138,7 @@ public final class FillResponse implements Parcelable { private FillResponse(@NonNull Builder builder) { mDatasets = builder.mDatasets; - mSaveInfo = builder.mSaveInfo; - if (mSaveInfo != null) { - mSaveInfo.addSavableIds(mDatasets); - if (mSaveInfo.getSavableIds() == null) { - throw new IllegalArgumentException( - "need to provide at least one savable id on SaveInfo"); - } - } - mExtras = builder.mExtras; mPresentation = builder.mPresentation; mAuthentication = builder.mAuthentication; @@ -307,8 +269,8 @@ public final class FillResponse implements Parcelable { * Sets a {@link Bundle} that will be passed to subsequent APIs that * manipulate this response. For example, they are passed to subsequent * calls to {@link AutofillService#onFillRequest( - * android.app.assist.AssistStructure, Bundle, android.os.CancellationSignal, - * FillCallback)} and {@link AutofillService#onSaveRequest( + * android.app.assist.AssistStructure, Bundle, int, + * android.os.CancellationSignal, FillCallback)} and {@link AutofillService#onSaveRequest( * android.app.assist.AssistStructure, Bundle, SaveCallback)}. * * @param extras The response extras. diff --git a/core/java/android/service/autofill/IAutoFillService.aidl b/core/java/android/service/autofill/IAutoFillService.aidl index 80685d87a788..9f296c60c6d4 100644 --- a/core/java/android/service/autofill/IAutoFillService.aidl +++ b/core/java/android/service/autofill/IAutoFillService.aidl @@ -31,7 +31,7 @@ import com.android.internal.os.IResultReceiver; oneway interface IAutoFillService { void onInit(in IAutoFillServiceConnection connection); void onFillRequest(in AssistStructure structure, in Bundle extras, - in IFillCallback callback); + in IFillCallback callback, int flags); void onSaveRequest(in AssistStructure structure, in Bundle extras, in ISaveCallback callback); } diff --git a/core/java/android/service/autofill/SaveInfo.java b/core/java/android/service/autofill/SaveInfo.java index 1bd88c7ce414..6213d27bfc70 100644 --- a/core/java/android/service/autofill/SaveInfo.java +++ b/core/java/android/service/autofill/SaveInfo.java @@ -25,28 +25,86 @@ import android.content.IntentSender; import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; -import android.util.ArraySet; import android.view.autofill.AutoFillId; import android.view.autofill.AutofillId; +import android.view.autofill.AutofillManager; +import android.view.autofill.AutofillValue; + +import com.android.internal.util.Preconditions; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; -import java.util.ArrayList; +import java.util.Arrays; /** - * Information used to indicate that a service is interested on saving the user-inputed data for - * future use. + * Information used to indicate that an {@link AutofillService} is interested on saving the + * user-inputed data for future use, through a + * {@link AutofillService#onSaveRequest(android.app.assist.AssistStructure, Bundle, SaveCallback)} + * call. + * + * <p>A {@link SaveInfo} is always associated with a {@link FillResponse}, and it contains at least + * two pieces of information: + * + * <ol> + * <li>The type of user data that would be saved (like passoword or credit card info). + * <li>The minimum set of views (represented by their {@link AutofillId}) that need to be changed + * to trigger a save request. + * </ol> + * + * Typically, the {@link SaveInfo} contains the same {@code id}s as the {@link Dataset}: + * + * <pre class="prettyprint"> + * new FillResponse.Builder() + * .add(new Dataset.Builder(createPresentation()) + * .setValue(id1, AutofillValue.forText("homer")) + * .setValue(id2, AutofillValue.forText("D'OH!")) + * .build()) + * .setSaveInfo(new SaveInfo.Builder(SaveInfo.SAVE_INFO_TYPE_PASSWORD, new int[] {id1, id2}) + * .build()) + * .build(); + * </pre> + * + * There might be cases where the {@link AutofillService} knows how to fill the + * {@link android.app.Activity}, but the user has no data for it. In that case, the + * {@link FillResponse} should contain just the {@link SaveInfo}, but no {@link Dataset}s: * - * <p>A {@link SaveInfo} is always associated with a {@link FillResponse}. + * <pre class="prettyprint"> + * new FillResponse.Builder() + * .setSaveInfo(new SaveInfo.Builder(SaveInfo.SAVE_INFO_TYPE_PASSWORD, new int[] {id1, id2}) + * .build()) + * .build(); + * </pre> * - * <p>A {@link SaveInfo} must define the type it represents, and contain at least one - * {@code savableId}. A {@code savableId} is the {@link AutofillId} of a view the service is - * interested to save in a {@code onSaveRequest()}; the ids of all {@link Dataset} present in the - * {@link FillResponse} associated with this {@link SaveInfo} are already marked as savable, - * but additional ids can be added through {@link Builder#addSavableIds(AutofillId...)}. + * <p>There might be cases where the user data in the {@link AutofillService} is enough + * to populate some fields but not all, and the service would still be interested on saving the + * other fields. In this scenario, the service could set the + * {@link SaveInfo.Builder#setOptionalIds(AutofillId[])} as well: * - * <p>See {@link AutofillService#onSaveRequest(android.app.assist.AssistStructure, Bundle, - * SaveCallback)} and {@link FillResponse} for more info. + * <pre class="prettyprint"> + * new FillResponse.Builder() + * .add(new Dataset.Builder(createPresentation()) + * .setValue(id1, AutofillValue.forText("742 Evergreen Terrace")) // street + * .setValue(id2, AutofillValue.forText("Springfield")) // city + * .build()) + * .setSaveInfo(new SaveInfo.Builder(SaveInfo.SAVE_INFO_TYPE_ADDRESS, new int[] {id1, id2}) + * .setOptionalIds(new int[] {id3, id4}) // state and zipcode + * .build()) + * .build(); + * </pre> + * + * The + * {@link AutofillService#onSaveRequest(android.app.assist.AssistStructure, Bundle, SaveCallback)} + * is triggered after a call to {@link AutofillManager#commit()}, but only when all conditions + * below are met: + * + * <ol> + * <li>The {@link SaveInfo} associated with the {@link FillResponse} is not {@code null}. + * <li>The {@link AutofillValue} of all required views (as set by the {@code requiredIds} passed + * to {@link SaveInfo.Builder} constructor are not empty. + * <li>The {@link AutofillValue} of at least one view (be it required or optional) has changed + * (i.e., it's not the same value passed in a {@link Dataset}). + * <li>The user explicitly tapped the affordance asking to save data for autofill. + * </ol> */ public final class SaveInfo implements Parcelable { @@ -61,7 +119,6 @@ public final class SaveInfo implements Parcelable { */ public static final int SAVE_DATA_TYPE_PASSWORD = 1; - /** * Type used on when the {@link FillResponse} represents a physical address (such as street, * city, state, etc). @@ -74,9 +131,10 @@ public final class SaveInfo implements Parcelable { public static final int SAVE_DATA_TYPE_CREDIT_CARD = 3; private final @SaveDataType int mType; - private CharSequence mNegativeActionTitle; - private IntentSender mNegativeActionListener; - private ArraySet<AutofillId> mSavableIds; + private final CharSequence mNegativeActionTitle; + private final IntentSender mNegativeActionListener; + private final AutofillId[] mRequiredIds; + private final AutofillId[] mOptionalIds; private final CharSequence mDescription; /** @hide */ @@ -94,7 +152,8 @@ public final class SaveInfo implements Parcelable { mType = builder.mType; mNegativeActionTitle = builder.mNegativeActionTitle; mNegativeActionListener = builder.mNegativeActionListener; - mSavableIds = builder.mSavableIds; + mRequiredIds = builder.mRequiredIds; + mOptionalIds = builder.mOptionalIds; mDescription = builder.mDescription; } @@ -109,8 +168,13 @@ public final class SaveInfo implements Parcelable { } /** @hide */ - public @Nullable ArraySet<AutofillId> getSavableIds() { - return mSavableIds; + public AutofillId[] getRequiredIds() { + return mRequiredIds; + } + + /** @hide */ + public @Nullable AutofillId[] getOptionalIds() { + return mOptionalIds; } /** @hide */ @@ -123,25 +187,6 @@ public final class SaveInfo implements Parcelable { return mDescription; } - /** @hide */ - public void addSavableIds(@Nullable ArrayList<Dataset> datasets) { - if (datasets != null) { - for (Dataset dataset : datasets) { - final ArrayList<AutofillId> ids = dataset.getFieldIds(); - if (ids != null) { - final int fieldCount = ids.size(); - for (int i = 0; i < fieldCount; i++) { - final AutofillId id = ids.get(i); - if (mSavableIds == null) { - mSavableIds = new ArraySet<>(); - } - mSavableIds.add(id); - } - } - } - } - } - /** * A builder for {@link SaveInfo} objects. */ @@ -150,7 +195,9 @@ public final class SaveInfo implements Parcelable { private final @SaveDataType int mType; private CharSequence mNegativeActionTitle; private IntentSender mNegativeActionListener; - private ArraySet<AutofillId> mSavableIds; + // TODO(b/33197203): make mRequiredIds final once addSavableIds() is gone + private AutofillId[] mRequiredIds; + private AutofillId[] mOptionalIds; private CharSequence mDescription; private boolean mDestroyed; @@ -161,8 +208,17 @@ public final class SaveInfo implements Parcelable { * be {@link SaveInfo#SAVE_DATA_TYPE_GENERIC}, {@link SaveInfo#SAVE_DATA_TYPE_PASSWORD}, * {@link SaveInfo#SAVE_DATA_TYPE_ADDRESS}, or {@link SaveInfo#SAVE_DATA_TYPE_CREDIT_CARD}; * otherwise it will assume {@link SaveInfo#SAVE_DATA_TYPE_GENERIC}. + * @param requiredIds ids of all required views that will trigger a save request. + * + * <p>See {@link SaveInfo} for more info. + * + * @throws IllegalArgumentException if {@code requiredIds} is {@code null} or empty. */ - public Builder(@SaveDataType int type) { + public Builder(@SaveDataType int type, @NonNull AutofillId[] requiredIds) { + if (false) {// TODO(b/33197203): re-move when clients use it + Preconditions.checkArgument(requiredIds != null && requiredIds.length > 0, + "must have at least one required id: " + Arrays.toString(requiredIds)); + } switch (type) { case SAVE_DATA_TYPE_PASSWORD: case SAVE_DATA_TYPE_ADDRESS: @@ -172,28 +228,43 @@ public final class SaveInfo implements Parcelable { default: mType = SAVE_DATA_TYPE_GENERIC; } + mRequiredIds = requiredIds; } /** - * Adds ids of additional views the service would be interested to save, but were not - * indirectly set through {@link FillResponse.Builder#addDataset(Dataset)}. - * - * @param ids The savable ids. - * @return This builder. - * - * @see FillResponse + * @hide + * @deprecated + * // TODO(b/33197203): make sure is removed when clients migrated */ + @Deprecated + public Builder(@SaveDataType int type) { + this(type, null); + } + + /** + * @hide + * @deprecated + * // TODO(b/33197203): make sure is removed when clients migrated + */ + @Deprecated public @NonNull Builder addSavableIds(@Nullable AutofillId... ids) { throwIfDestroyed(); + mRequiredIds = ids; + return this; + } - if (ids == null) { - return this; - } - for (AutofillId id : ids) { - if (mSavableIds == null) { - mSavableIds = new ArraySet<>(); - } - mSavableIds.add(id); + /** + * Sets the ids of additional, optional views the service would be interested to save. + * + * <p>See {@link SaveInfo} for more info. + * + * @param ids The ids of the optional views. + * @return This builder. + */ + public @NonNull Builder setOptionalIds(@Nullable AutofillId[] ids) { + throwIfDestroyed(); + if (ids != null && ids.length != 0) { + mOptionalIds = ids; } return this; } @@ -206,14 +277,14 @@ public final class SaveInfo implements Parcelable { public @NonNull Builder addSavableIds(@Nullable AutoFillId... ids) { throwIfDestroyed(); - if (ids == null) { + if (ids == null || ids.length == 0) { return this; } - for (AutoFillId id : ids) { - if (mSavableIds == null) { - mSavableIds = new ArraySet<>(); - } - mSavableIds.add(id.getDaRealId()); + if (mRequiredIds == null) { + mRequiredIds = new AutofillId[ids.length]; + } + for (int i = 0; i < ids.length; i++) { + mRequiredIds[i] = ids[i].getDaRealId(); } return this; } @@ -228,6 +299,7 @@ public final class SaveInfo implements Parcelable { * @return This Builder. */ public @NonNull Builder setDescription(@Nullable CharSequence description) { + throwIfDestroyed(); mDescription = description; return this; } @@ -293,7 +365,9 @@ public final class SaveInfo implements Parcelable { if (!DEBUG) return super.toString(); return new StringBuilder("SaveInfo: [type=").append(mType) - .append(", savableIds=").append(mSavableIds) + .append(", requiredIds=").append(Arrays.toString(mRequiredIds)) + .append(", optionalIds=").append(Arrays.toString(mOptionalIds)) + .append(", description=").append(mDescription) .append("]").toString(); } @@ -309,9 +383,10 @@ public final class SaveInfo implements Parcelable { @Override public void writeToParcel(Parcel parcel, int flags) { parcel.writeInt(mType); + parcel.writeParcelableArray(mRequiredIds, flags); parcel.writeCharSequence(mNegativeActionTitle); parcel.writeParcelable(mNegativeActionListener, flags); - parcel.writeTypedArraySet(mSavableIds, flags); + parcel.writeParcelableArray(mOptionalIds, flags); parcel.writeCharSequence(mDescription); } @@ -321,13 +396,10 @@ public final class SaveInfo implements Parcelable { // Always go through the builder to ensure the data ingested by // the system obeys the contract of the builder to avoid attacks // using specially crafted parcels. - final Builder builder = new Builder(parcel.readInt()); + final Builder builder = new Builder(parcel.readInt(), + parcel.readParcelableArray(null, AutofillId.class)); builder.setNegativeAction(parcel.readCharSequence(), parcel.readParcelable(null)); - final ArraySet<AutofillId> savableIds = parcel.readTypedArraySet(null); - final int savableIdsCount = (savableIds != null) ? savableIds.size() : 0; - for (int i = 0; i < savableIdsCount; i++) { - builder.addSavableIds(savableIds.valueAt(i)); - } + builder.setOptionalIds(parcel.readParcelableArray(null, AutofillId.class)); builder.setDescription(parcel.readCharSequence()); return builder.build(); } diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java index 70e0461ce73f..f55c7cfb4ba5 100644 --- a/core/java/android/service/notification/NotificationListenerService.java +++ b/core/java/android/service/notification/NotificationListenerService.java @@ -149,13 +149,13 @@ public abstract class NotificationListenerService extends Service { // Notification cancellation reasons /** Notification was canceled by the status bar reporting a notification click. */ - public static final int REASON_DELEGATE_CLICK = 1; + public static final int REASON_CLICK = 1; /** Notification was canceled by the status bar reporting a user dismissal. */ - public static final int REASON_DELEGATE_CANCEL = 2; + public static final int REASON_CANCEL = 2; /** Notification was canceled by the status bar reporting a user dismiss all. */ - public static final int REASON_DELEGATE_CANCEL_ALL = 3; + public static final int REASON_CANCEL_ALL = 3; /** Notification was canceled by the status bar reporting an inflation error. */ - public static final int REASON_DELEGATE_ERROR = 4; + public static final int REASON_ERROR = 4; /** Notification was canceled by the package manager modifying the package. */ public static final int REASON_PACKAGE_CHANGED = 5; /** Notification was canceled by the owning user context being stopped. */ diff --git a/core/java/android/service/vr/IVrManager.aidl b/core/java/android/service/vr/IVrManager.aidl index 10e417784700..6034c1820972 100644 --- a/core/java/android/service/vr/IVrManager.aidl +++ b/core/java/android/service/vr/IVrManager.aidl @@ -50,5 +50,13 @@ interface IVrManager { * @param enabled true if the device should be placed in persistent VR mode. */ void setPersistentVrModeEnabled(in boolean enabled); + + /** + * Return current virtual display id. + * + * @return {@link android.view.Display.INVALID_DISPLAY} if there is no virtual display + * currently, else return the display id of the virtual display + */ + int getCompatibilityDisplayId(); } diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java index 483a49ba1697..6bbb0ff9861b 100644 --- a/core/java/android/service/wallpaper/WallpaperService.java +++ b/core/java/android/service/wallpaper/WallpaperService.java @@ -18,11 +18,11 @@ package android.service.wallpaper; import android.content.res.TypedArray; import android.graphics.Canvas; +import android.util.MergedConfiguration; import android.view.WindowInsets; import com.android.internal.R; import com.android.internal.os.HandlerCaller; -import com.android.internal.util.ScreenShapeHelper; import com.android.internal.view.BaseIWindow; import com.android.internal.view.BaseSurfaceHolder; @@ -32,7 +32,6 @@ import android.app.Service; import android.app.WallpaperManager; import android.content.Context; import android.content.Intent; -import android.content.res.Configuration; import android.graphics.PixelFormat; import android.graphics.Rect; import android.hardware.display.DisplayManager; @@ -55,7 +54,6 @@ import android.view.SurfaceHolder; import android.view.View; import android.view.ViewGroup; import android.view.WindowManager; -import android.view.WindowManager.LayoutParams; import android.view.WindowManagerGlobal; import java.io.FileDescriptor; @@ -169,7 +167,7 @@ public abstract class WallpaperService extends Service { final Rect mFinalSystemInsets = new Rect(); final Rect mFinalStableInsets = new Rect(); final Rect mBackdropFrame = new Rect(); - final Configuration mConfiguration = new Configuration(); + final MergedConfiguration mMergedConfiguration = new MergedConfiguration(); final WindowManager.LayoutParams mLayout = new WindowManager.LayoutParams(); @@ -288,7 +286,7 @@ public abstract class WallpaperService extends Service { @Override public void resized(Rect frame, Rect overscanInsets, Rect contentInsets, Rect visibleInsets, Rect stableInsets, Rect outsets, boolean reportDraw, - Configuration newConfig, Rect backDropRect, boolean forceLayout, + MergedConfiguration mergedConfiguration, Rect backDropRect, boolean forceLayout, boolean alwaysConsumeNavBar, int displayId) { Message msg = mCaller.obtainMessageIO(MSG_WINDOW_RESIZED, reportDraw ? 1 : 0, outsets); @@ -568,7 +566,8 @@ public abstract class WallpaperService extends Service { out.print(mVisibleInsets.toShortString()); out.print(" mWinFrame="); out.print(mWinFrame.toShortString()); out.print(" mContentInsets="); out.println(mContentInsets.toShortString()); - out.print(prefix); out.print("mConfiguration="); out.println(mConfiguration); + out.print(prefix); out.print("mConfiguration="); + out.println(mMergedConfiguration.getMergedConfiguration()); out.print(prefix); out.print("mLayout="); out.println(mLayout); synchronized (mLock) { out.print(prefix); out.print("mPendingXOffset="); out.print(mPendingXOffset); @@ -695,7 +694,7 @@ public abstract class WallpaperService extends Service { mWindow, mWindow.mSeq, mLayout, mWidth, mHeight, View.VISIBLE, 0, mWinFrame, mOverscanInsets, mContentInsets, mVisibleInsets, mStableInsets, mOutsets, mBackdropFrame, - mConfiguration, mSurfaceHolder.mSurface); + mMergedConfiguration, mSurfaceHolder.mSurface); if (DEBUG) Log.v(TAG, "New surface: " + mSurfaceHolder.mSurface + ", frame=" + mWinFrame); diff --git a/core/java/android/text/FontConfig.java b/core/java/android/text/FontConfig.java index 1087851c853f..04596fa5aa3d 100644 --- a/core/java/android/text/FontConfig.java +++ b/core/java/android/text/FontConfig.java @@ -16,42 +16,57 @@ package android.text; +import static java.lang.annotation.RetentionPolicy.SOURCE; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.os.Parcel; import android.os.ParcelFileDescriptor; import android.os.Parcelable; import java.io.IOException; -import java.util.ArrayList; -import java.util.List; +import java.lang.annotation.Retention; +import java.util.Arrays; + /** * Font configuration descriptions for System fonts. */ public final class FontConfig implements Parcelable { - private final List<Family> mFamilies = new ArrayList<>(); - private final List<Alias> mAliases = new ArrayList<>(); + private final @NonNull Family[] mFamilies; + private final @NonNull Alias[] mAliases; - public FontConfig() { + public FontConfig(@NonNull Family[] families, @NonNull Alias[] aliases) { + mFamilies = families; + mAliases = aliases; } - public FontConfig(FontConfig config) { - for (int i = 0; i < config.mFamilies.size(); i++) { - mFamilies.add(new Family(config.mFamilies.get(i))); + /** + * For duplicating file descriptors. + * + * Note that this copy constructor can not be usable for deep copy. + * @hide + */ + public FontConfig(@NonNull FontConfig config) { + mFamilies = new Family[config.mFamilies.length]; + for (int i = 0; i < config.mFamilies.length; ++i) { + mFamilies[i] = new Family(config.mFamilies[i]); } - mAliases.addAll(config.mAliases); + mAliases = Arrays.copyOf(config.mAliases, config.mAliases.length); } /** * Returns the ordered list of families included in the system fonts. */ - public List<Family> getFamilies() { + public @NonNull Family[] getFamilies() { return mFamilies; } /** * Returns the list of aliases defined for the font families in the system fonts. */ - public List<Alias> getAliases() { + public @NonNull Alias[] getAliases() { return mAliases; } @@ -59,33 +74,14 @@ public final class FontConfig implements Parcelable { * @hide */ public FontConfig(Parcel in) { - readFromParcel(in); + mFamilies = in.readTypedArray(Family.CREATOR); + mAliases = in.readTypedArray(Alias.CREATOR); } @Override public void writeToParcel(Parcel out, int flag) { - out.writeInt(mFamilies.size()); - for (int i = 0; i < mFamilies.size(); i++) { - mFamilies.get(i).writeToParcel(out, flag); - } - out.writeInt(mAliases.size()); - for (int i = 0; i < mAliases.size(); i++) { - mAliases.get(i).writeToParcel(out, flag); - } - } - - /** - * @hide - */ - public void readFromParcel(Parcel in) { - int size = in.readInt(); - for (int i = 0; i < size; i++) { - mFamilies.add(new Family(in)); - } - size = in.readInt(); - for (int i = 0; i < size; i++) { - mAliases.add(new Alias(in)); - } + out.writeTypedArray(mFamilies, flag); + out.writeTypedArray(mAliases, flag); } @Override @@ -164,36 +160,37 @@ public final class FontConfig implements Parcelable { * Class that holds information about a Font. */ public static final class Font implements Parcelable { - private String mFontName; + private final @NonNull String mFontName; private final int mTtcIndex; - private final List<Axis> mAxes; + private final @NonNull Axis[] mAxes; private final int mWeight; private final boolean mIsItalic; - private ParcelFileDescriptor mFd; - private final int mResourceId; + private @Nullable ParcelFileDescriptor mFd; /** * @hide */ - public Font(String fontName, int ttcIndex, List<Axis> axes, int weight, boolean isItalic, - int resourceId) { + public Font(@NonNull String fontName, int ttcIndex, @NonNull Axis[] axes, int weight, + boolean isItalic) { mFontName = fontName; mTtcIndex = ttcIndex; mAxes = axes; mWeight = weight; mIsItalic = isItalic; mFd = null; - mResourceId = resourceId; - } - - public Font(String fontName, int ttcIndex, List<Axis> axes, int weight, boolean isItalic) { - this(fontName, ttcIndex, axes, weight, isItalic, 0); } + /** + * This is for duplicating FileDescriptors. + * + * Note that this copy ctor doesn't deep copy the members. + * + * @hide + */ public Font(Font origin) { mFontName = origin.mFontName; mTtcIndex = origin.mTtcIndex; - mAxes = new ArrayList<>(origin.mAxes); + mAxes = origin.mAxes; mWeight = origin.mWeight; mIsItalic = origin.mIsItalic; if (origin.mFd != null) { @@ -203,24 +200,16 @@ public final class FontConfig implements Parcelable { e.printStackTrace(); } } - mResourceId = origin.mResourceId; } /** * Returns the name associated by the system to this font. */ - public String getFontName() { + public @NonNull String getFontName() { return mFontName; } /** - * @hide - */ - public void setFontName(String fontName) { - mFontName = fontName; - } - - /** * Returns the index to be used to access this font when accessing a TTC file. */ public int getTtcIndex() { @@ -230,7 +219,7 @@ public final class FontConfig implements Parcelable { /** * Returns the list of axes associated to this font. */ - public List<Axis> getAxes() { + public @NonNull Axis[] getAxes() { return mAxes; } @@ -251,35 +240,24 @@ public final class FontConfig implements Parcelable { /** * Returns a file descriptor to access the specified font. This should be closed after use. */ - public ParcelFileDescriptor getFd() { + public @Nullable ParcelFileDescriptor getFd() { return mFd; } /** * @hide */ - public void setFd(ParcelFileDescriptor fd) { + public void setFd(@NonNull ParcelFileDescriptor fd) { mFd = fd; } /** * @hide */ - public int getResourceId() { - return mResourceId; - } - - /** - * @hide - */ public Font(Parcel in) { mFontName = in.readString(); mTtcIndex = in.readInt(); - final int numAxes = in.readInt(); - mAxes = new ArrayList<>(); - for (int i = 0; i < numAxes; i++) { - mAxes.add(new Axis(in)); - } + mAxes = in.createTypedArray(Axis.CREATOR); mWeight = in.readInt(); mIsItalic = in.readInt() == 1; if (in.readInt() == 1) { /* has FD */ @@ -287,24 +265,19 @@ public final class FontConfig implements Parcelable { } else { mFd = null; } - mResourceId = in.readInt(); } @Override public void writeToParcel(Parcel out, int flag) { out.writeString(mFontName); out.writeInt(mTtcIndex); - out.writeInt(mAxes.size()); - for (int i = 0; i < mAxes.size(); i++) { - mAxes.get(i).writeToParcel(out, flag); - } + out.writeTypedArray(mAxes, flag); out.writeInt(mWeight); out.writeInt(mIsItalic ? 1 : 0); out.writeInt(mFd == null ? 0 : 1); if (mFd != null) { mFd.writeToParcel(out, flag); } - out.writeInt(mResourceId); } @Override @@ -329,27 +302,27 @@ public final class FontConfig implements Parcelable { * Class that holds information about a Font alias. */ public static final class Alias implements Parcelable { - private final String mName; - private final String mToName; + private final @NonNull String mName; + private final @NonNull String mToName; private final int mWeight; - public Alias(String name, String toName, int weight) { - this.mName = name; - this.mToName = toName; - this.mWeight = weight; + public Alias(@NonNull String name, @NonNull String toName, int weight) { + mName = name; + mToName = toName; + mWeight = weight; } /** * Returns the new name for the alias. */ - public String getName() { + public @NonNull String getName() { return mName; } /** * Returns the existing name to which this alias points to. */ - public String getToName() { + public @NonNull String getToName() { return mToName; } @@ -398,149 +371,110 @@ public final class FontConfig implements Parcelable { * Class that holds information about a Font family. */ public static final class Family implements Parcelable { - private final String mName; - private final List<Font> mFonts; - private final String mLanguage; - private final String mVariant; - private final String mProviderAuthority; - private final String mProviderPackage; - private final String mQuery; - - public Family(String name, List<Font> fonts, String language, String variant) { + private final @NonNull String mName; + private final @NonNull Font[] mFonts; + private final @NonNull String mLanguage; + + /** @hide */ + @Retention(SOURCE) + @IntDef({VARIANT_DEFAULT, VARIANT_COMPACT, VARIANT_ELEGANT}) + public @interface Variant {} + + /** + * Value for font variant. + * + * Indicates the font has no variant attribute. + */ + public static final int VARIANT_DEFAULT = 0; + + /** + * Value for font variant. + * + * Indicates the font is for compact variant. + * @see android.graphics.Paint#setElegantTextHeight + */ + public static final int VARIANT_COMPACT = 1; + + /** + * Value for font variant. + * + * Indiates the font is for elegant variant. + * @see android.graphics.Paint#setElegantTextHeight + */ + public static final int VARIANT_ELEGANT = 2; + + // Must be same with Minikin's variant values. + // See frameworks/minikin/include/minikin/FontFamily.h + private final @Variant int mVariant; + + public Family(@NonNull String name, @NonNull Font[] fonts, @NonNull String language, + @Variant int variant) { mName = name; mFonts = fonts; mLanguage = language; mVariant = variant; - mProviderAuthority = null; - mProviderPackage = null; - mQuery = null; } /** + * For duplicating file descriptor underlying Font object. + * + * This copy constructor is not for deep copying. * @hide */ - public Family(String providerAuthority, String providerPackage, String query) { - mName = null; - mFonts = null; - mLanguage = null; - mVariant = null; - mProviderAuthority = providerAuthority; - mProviderPackage = providerPackage; - mQuery = query; - } - public Family(Family origin) { mName = origin.mName; mLanguage = origin.mLanguage; mVariant = origin.mVariant; - mFonts = new ArrayList<>(); - for (int i = 0; i < origin.mFonts.size(); i++) { - mFonts.add(new Font(origin.mFonts.get(i))); + mFonts = new Font[origin.mFonts.length]; + for (int i = 0; i < origin.mFonts.length; ++i) { + mFonts[i] = new Font(origin.mFonts[i]); } - mProviderAuthority = origin.mProviderAuthority; - mProviderPackage = origin.mProviderPackage; - mQuery = origin.mQuery; } /** * Returns the name given by the system to this font family. */ - public String getName() { + public @Nullable String getName() { return mName; } /** * Returns the list of fonts included in this family. */ - public List<Font> getFonts() { + public @Nullable Font[] getFonts() { return mFonts; } /** * Returns the language for this family. May be null. */ - public String getLanguage() { + public @Nullable String getLanguage() { return mLanguage; } /** * Returns the font variant for this family, e.g. "elegant" or "compact". May be null. */ - public String getVariant() { + public @Variant int getVariant() { return mVariant; } /** * @hide */ - public String getProviderAuthority() { - return mProviderAuthority; - } - - /** - * @hide - */ - public String getProviderPackage() { - return mProviderPackage; - } - - /** - * @hide - */ - public String getQuery() { - return mQuery; - } - - /** - * @hide - */ public Family(Parcel in) { mName = in.readString(); - final int size = in.readInt(); - mFonts = new ArrayList<>(); - for (int i = 0; i < size; i++) { - mFonts.add(new Font(in)); - } + mFonts = in.readTypedArray(Font.CREATOR); mLanguage = in.readString(); - mVariant = in.readString(); - if (in.readInt() == 1) { - mProviderAuthority = in.readString(); - } else { - mProviderAuthority = null; - } - if (in.readInt() == 1) { - mProviderPackage = in.readString(); - } else { - mProviderPackage = null; - } - if (in.readInt() == 1) { - mQuery = in.readString(); - } else { - mQuery = null; - } + mVariant = in.readInt(); } @Override public void writeToParcel(Parcel out, int flag) { out.writeString(mName); - out.writeInt(mFonts.size()); - for (int i = 0; i < mFonts.size(); i++) { - mFonts.get(i).writeToParcel(out, flag); - } + out.writeTypedArray(mFonts, flag); out.writeString(mLanguage); - out.writeString(mVariant); - out.writeInt(mProviderAuthority == null ? 0 : 1); - if (mProviderAuthority != null) { - out.writeString(mProviderAuthority); - } - out.writeInt(mProviderPackage == null ? 0 : 1); - if (mProviderPackage != null) { - out.writeString(mProviderPackage); - } - out.writeInt(mQuery == null ? 0 : 1); - if (mQuery != null) { - out.writeString(mQuery); - } + out.writeInt(mVariant); } @Override diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java index 94c463c8e651..353dfed343e0 100644 --- a/core/java/android/text/StaticLayout.java +++ b/core/java/android/text/StaticLayout.java @@ -1031,6 +1031,7 @@ public class StaticLayout extends Layout { float avail, TextUtils.TruncateAt where, int line, float textWidth, TextPaint paint, boolean forceEllipsis) { + avail -= getTotalInsets(line); if (textWidth <= avail && !forceEllipsis) { // Everything fits! mLines[mColumns * line + ELLIPSIS_START] = 0; @@ -1134,6 +1135,17 @@ public class StaticLayout extends Layout { mLines[mColumns * line + ELLIPSIS_COUNT] = ellipsisCount; } + private float getTotalInsets(int line) { + int totalIndent = 0; + if (mLeftIndents != null) { + totalIndent = mLeftIndents[Math.min(line, mLeftIndents.length - 1)]; + } + if (mRightIndents != null) { + totalIndent += mRightIndents[Math.min(line, mRightIndents.length - 1)]; + } + return totalIndent; + } + // Override the base class so we can directly access our members, // rather than relying on member functions. // The logic mirrors that of Layout.getLineForVertical diff --git a/core/java/android/transition/Transition.java b/core/java/android/transition/Transition.java index af2547e44b9f..255a02938c32 100644 --- a/core/java/android/transition/Transition.java +++ b/core/java/android/transition/Transition.java @@ -799,8 +799,10 @@ public abstract class Transition implements Cloneable { * targetId list. If the target parameter is null, then the target list * is not checked (this is in the case of ListView items, where the * views are ignored and only the ids are used). + * + * @hide */ - boolean isValidTarget(View target) { + public boolean isValidTarget(View target) { if (target == null) { return false; } diff --git a/core/java/android/util/LauncherIcons.java b/core/java/android/util/LauncherIcons.java new file mode 100644 index 000000000000..e5aa2b5b4ce7 --- /dev/null +++ b/core/java/android/util/LauncherIcons.java @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.util; + +import android.content.Context; +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Matrix; +import android.graphics.Paint; +import android.graphics.PaintFlagsDrawFilter; +import android.graphics.Path; +import android.graphics.RectF; +import android.graphics.drawable.AdaptiveIconDrawable; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; + +/** + * Utility class to handle icon treatments (e.g., shadow generation) for the Launcher icons. + * @hide + */ +public final class LauncherIcons { + + private final Paint mPaint = new Paint(); + private final Canvas mCanvas = new Canvas(); + + private static final int KEY_SHADOW_ALPHA = 61; + private static final int AMBIENT_SHADOW_ALPHA = 30; + private static final float BLUR_FACTOR = 0.5f / 48; + private int mShadowInset; + private Bitmap mShadowBitmap; + private int mIconSize; + private Resources mRes; + + public LauncherIcons(Context context) { + mRes = context.getResources(); + DisplayMetrics metrics = mRes.getDisplayMetrics(); + mShadowInset = (int) metrics.density / DisplayMetrics.DENSITY_DEFAULT; + mCanvas.setDrawFilter(new PaintFlagsDrawFilter(Paint.DITHER_FLAG, + Paint.FILTER_BITMAP_FLAG)); + mIconSize = (int) mRes.getDimensionPixelSize(android.R.dimen.app_icon_size); + } + + /** + * Draw the drawable into a bitmap. + */ + public Bitmap createIconBitmap(Drawable icon) { + final Bitmap bitmap = Bitmap.createBitmap(mIconSize, mIconSize, Bitmap.Config.ARGB_8888); + mPaint.setAlpha(255); + mCanvas.setBitmap(bitmap); + int iconInset = 0; + if (mShadowBitmap != null) { + mCanvas.drawBitmap(mShadowBitmap, 0, 0, mPaint); + iconInset = mShadowInset; + } + + icon.setBounds(iconInset, iconInset, mIconSize - iconInset, + mIconSize - iconInset); + icon.draw(mCanvas); + mCanvas.setBitmap(null); + return bitmap; + } + + public Drawable wrapIconDrawableWithShadow(Drawable drawable) { + if (!(drawable instanceof AdaptiveIconDrawable)) { + return drawable; + } + AdaptiveIconDrawable d = + (AdaptiveIconDrawable) drawable.getConstantState().newDrawable().mutate(); + getShadowBitmap(d); + Bitmap iconbitmap = createIconBitmap(d); + return new BitmapDrawable(mRes, iconbitmap); + } + + private Bitmap getShadowBitmap(AdaptiveIconDrawable d) { + if (mShadowBitmap != null) { + return mShadowBitmap; + } + + int shadowSize = mIconSize - mShadowInset; + mShadowBitmap = Bitmap.createBitmap(mIconSize, mIconSize, Bitmap.Config.ALPHA_8); + mCanvas.setBitmap(mShadowBitmap); + + // Draw key shadow + mPaint.setColor(Color.TRANSPARENT); + float blur = BLUR_FACTOR * mIconSize; + mPaint.setShadowLayer(blur, 0, mShadowInset, KEY_SHADOW_ALPHA << 24); + d.setBounds(mShadowInset, mShadowInset, mIconSize - mShadowInset, mIconSize - mShadowInset); + mCanvas.drawPath(d.getIconMask(), mPaint); + + // Draw ambient shadow + mPaint.setShadowLayer(blur, 0, 0, AMBIENT_SHADOW_ALPHA << 24); + d.setBounds(mShadowInset, 2 * mShadowInset, mIconSize - mShadowInset, mIconSize); + mCanvas.drawPath(d.getIconMask(), mPaint); + mPaint.clearShadowLayer(); + + return mShadowBitmap; + } +} diff --git a/core/java/android/util/MergedConfiguration.aidl b/core/java/android/util/MergedConfiguration.aidl new file mode 100644 index 000000000000..c24dbbec68a6 --- /dev/null +++ b/core/java/android/util/MergedConfiguration.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package android.util; + +parcelable MergedConfiguration;
\ No newline at end of file diff --git a/core/java/android/util/MergedConfiguration.java b/core/java/android/util/MergedConfiguration.java new file mode 100644 index 000000000000..d94af8a68fc1 --- /dev/null +++ b/core/java/android/util/MergedConfiguration.java @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package android.util; + +import android.annotation.NonNull; +import android.content.res.Configuration; +import android.os.Parcel; +import android.os.Parcelable; + +/** + * Container that holds global and override config and their merge product. + * Merged configuration updates automatically whenever global or override configs are updated via + * setters. + * + * {@hide} + */ +public class MergedConfiguration implements Parcelable { + + private Configuration mGlobalConfig = new Configuration(); + private Configuration mOverrideConfig = new Configuration(); + private Configuration mMergedConfig = new Configuration(); + + public MergedConfiguration() { + } + + public MergedConfiguration(Configuration globalConfig, Configuration overrideConfig) { + setConfiguration(globalConfig, overrideConfig); + } + + public MergedConfiguration(MergedConfiguration mergedConfiguration) { + setConfiguration(mergedConfiguration.getGlobalConfiguration(), + mergedConfiguration.getOverrideConfiguration()); + } + + private MergedConfiguration(Parcel in) { + readFromParcel(in); + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeParcelable(mGlobalConfig, flags); + dest.writeParcelable(mOverrideConfig, flags); + dest.writeParcelable(mMergedConfig, flags); + } + + public void readFromParcel(Parcel source) { + mGlobalConfig = source.readParcelable(Configuration.class.getClassLoader()); + mOverrideConfig = source.readParcelable(Configuration.class.getClassLoader()); + mMergedConfig = source.readParcelable(Configuration.class.getClassLoader()); + } + + @Override + public int describeContents() { + return 0; + } + + public static final Creator<MergedConfiguration> CREATOR = new Creator<MergedConfiguration>() { + @Override + public MergedConfiguration createFromParcel(Parcel in) { + return new MergedConfiguration(in); + } + + @Override + public MergedConfiguration[] newArray(int size) { + return new MergedConfiguration[size]; + } + }; + + /** + * Update global and override configurations. + * Merged configuration will automatically be updated. + * @param globalConfig New global configuration. + * @param overrideConfig New override configuration. + */ + public void setConfiguration(Configuration globalConfig, Configuration overrideConfig) { + mGlobalConfig.setTo(globalConfig); + mOverrideConfig.setTo(overrideConfig); + updateMergedConfig(); + } + + /** + * @return Stored global configuration value. + */ + @NonNull + public Configuration getGlobalConfiguration() { + return mGlobalConfig; + } + + /** + * @return Stored override configuration value. + */ + public Configuration getOverrideConfiguration() { + return mOverrideConfig; + } + + /** + * @return Stored merged configuration value. + */ + public Configuration getMergedConfiguration() { + return mMergedConfig; + } + + /** Update merged config when global or override config changes. */ + private void updateMergedConfig() { + mMergedConfig.setTo(mGlobalConfig); + mMergedConfig.updateFrom(mOverrideConfig); + } +} diff --git a/core/java/android/view/AccessibilityIterators.java b/core/java/android/view/AccessibilityIterators.java index e59937dae055..ca54bef1e780 100644 --- a/core/java/android/view/AccessibilityIterators.java +++ b/core/java/android/view/AccessibilityIterators.java @@ -16,7 +16,6 @@ package android.view; -import android.content.ComponentCallbacks; import android.content.res.Configuration; import java.text.BreakIterator; @@ -65,7 +64,7 @@ public final class AccessibilityIterators { } static class CharacterTextSegmentIterator extends AbstractTextSegmentIterator - implements ComponentCallbacks { + implements ViewRootImpl.ConfigChangedCallback { private static CharacterTextSegmentIterator sInstance; private Locale mLocale; @@ -144,19 +143,14 @@ public final class AccessibilityIterators { } @Override - public void onConfigurationChanged(Configuration newConfig) { - Locale locale = newConfig.locale; + public void onConfigurationChanged(Configuration globalConfig) { + final Locale locale = globalConfig.getLocales().get(0); if (!mLocale.equals(locale)) { mLocale = locale; onLocaleChanged(locale); } } - @Override - public void onLowMemory() { - /* ignore */ - } - protected void onLocaleChanged(Locale locale) { mImpl = BreakIterator.getCharacterInstance(locale); } diff --git a/core/java/android/view/IWindow.aidl b/core/java/android/view/IWindow.aidl index 14b2abec2adb..611cc6337fb4 100644 --- a/core/java/android/view/IWindow.aidl +++ b/core/java/android/view/IWindow.aidl @@ -17,7 +17,6 @@ package android.view; -import android.content.res.Configuration; import android.graphics.Rect; import android.os.Bundle; import android.os.ParcelFileDescriptor; @@ -26,6 +25,7 @@ import android.view.KeyEvent; import android.view.MotionEvent; import com.android.internal.os.IResultReceiver; +import android.util.MergedConfiguration; /** * API back to a client window that the Window Manager uses to inform it of @@ -49,8 +49,8 @@ oneway interface IWindow { void resized(in Rect frame, in Rect overscanInsets, in Rect contentInsets, in Rect visibleInsets, in Rect stableInsets, in Rect outsets, boolean reportDraw, - in Configuration newConfig, in Rect backDropFrame, boolean forceLayout, - boolean alwaysConsumeNavBar, int displayId); + in MergedConfiguration newMergedConfiguration, in Rect backDropFrame, + boolean forceLayout, boolean alwaysConsumeNavBar, int displayId); void moved(int newX, int newY); void dispatchAppVisibility(boolean visible); void dispatchGetNewSurface(); diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl index 1facc106d645..51d65144f260 100644 --- a/core/java/android/view/IWindowSession.aidl +++ b/core/java/android/view/IWindowSession.aidl @@ -11,17 +11,17 @@ ** 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 +** See the License for the specific language governing permissions and ** limitations under the License. */ package android.view; import android.content.ClipData; -import android.content.res.Configuration; import android.graphics.Rect; import android.graphics.Region; import android.os.Bundle; +import android.util.MergedConfiguration; import android.view.InputChannel; import android.view.IWindow; import android.view.IWindowId; @@ -83,9 +83,9 @@ interface IWindowSession { * treat as real display. Example of such area is a chin in some models of wearable devices. * @param outBackdropFrame Rect which is used draw the resizing background during a resize * operation. - * @param outConfiguration New configuration of window, if it is now - * becoming visible and the global configuration has changed since it - * was last displayed. + * @param outMergedConfiguration New config container that holds global, override and merged + * config for window, if it is now becoming visible and the merged configuration has changed + * since it was last displayed. * @param outSurface Object in which is placed the new display surface. * * @return int Result flags: {@link WindowManagerGlobal#RELAYOUT_SHOW_FOCUS}, @@ -95,33 +95,8 @@ interface IWindowSession { int requestedWidth, int requestedHeight, int viewVisibility, int flags, out Rect outFrame, out Rect outOverscanInsets, out Rect outContentInsets, out Rect outVisibleInsets, out Rect outStableInsets, - out Rect outOutsets, out Rect outBackdropFrame, out Configuration outConfig, - out Surface outSurface); - - /** - * Position a window relative to it's parent (attached) window without triggering - * a full relayout. This action may be deferred until a given frame number - * for the parent window appears. This allows for synchronizing movement of a child - * to repainting the contents of the parent. - * - * "width" and "height" correspond to the width and height members of - * WindowManager.LayoutParams in the {@link #relayout relayout()} case. - * This may differ from the surface buffer size in the - * case of {@link LayoutParams#FLAG_SCALED} and {@link #relayout relayout()} - * must be used with requestedWidth/height if this must be changed. - * - * @param window The window being modified. Must be attached to a parent window - * or this call will fail. - * @param left The new left position - * @param top The new top position - * @param right The new right position - * @param bottom The new bottom position - * @param deferTransactionUntilFrame Frame number from our parent (attached) to - * defer this action until. - * @param outFrame Rect in which is placed the new position/size on screen. - */ - void repositionChild(IWindow childWindow, int left, int top, int right, int bottom, - long deferTransactionUntilFrame, out Rect outFrame); + out Rect outOutsets, out Rect outBackdropFrame, + out MergedConfiguration outMergedConfiguration, out Surface outSurface); /* * Notify the window manager that an application is relaunching and @@ -134,12 +109,6 @@ interface IWindowSession { void prepareToReplaceWindows(IBinder appToken, boolean childrenOnly); /** - * If a call to relayout() asked to have the surface destroy deferred, - * it must call this once it is okay to destroy that surface. - */ - void performDeferredDestroy(IWindow window); - - /** * Called by a client to report that it ran out of graphics memory. */ boolean outOfMemory(IWindow window); diff --git a/core/java/android/view/LayoutInflater.java b/core/java/android/view/LayoutInflater.java index e3ac40c5da4e..0e06cd32161b 100644 --- a/core/java/android/view/LayoutInflater.java +++ b/core/java/android/view/LayoutInflater.java @@ -833,6 +833,7 @@ public abstract class LayoutInflater { final int depth = parser.getDepth(); int type; + boolean pendingRequestFocus = false; while (((type = parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) { @@ -844,7 +845,8 @@ public abstract class LayoutInflater { final String name = parser.getName(); if (TAG_REQUEST_FOCUS.equals(name)) { - parseRequestFocus(parser, parent); + pendingRequestFocus = true; + consumeChildElements(parser); } else if (TAG_TAG.equals(name)) { parseViewTag(parser, parent, attrs); } else if (TAG_INCLUDE.equals(name)) { @@ -863,23 +865,16 @@ public abstract class LayoutInflater { } } + if (pendingRequestFocus) { + parent.restoreDefaultFocus(); + } + if (finishInflate) { parent.onFinishInflate(); } } /** - * Parses a <code><request-focus></code> element and requests focus on - * the containing View. - */ - private void parseRequestFocus(XmlPullParser parser, View view) - throws XmlPullParserException, IOException { - view.requestFocus(); - - consumeChildElements(parser); - } - - /** * Parses a <code><tag></code> element and sets a keyed tag on the * containing View. */ diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java index 6d320ef32b0d..824e035697fc 100644 --- a/core/java/android/view/SurfaceView.java +++ b/core/java/android/view/SurfaceView.java @@ -16,8 +16,9 @@ package android.view; -import static android.view.WindowManagerPolicy.APPLICATION_MEDIA_SUBLAYER; +import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_PANEL; import static android.view.WindowManagerPolicy.APPLICATION_MEDIA_OVERLAY_SUBLAYER; +import static android.view.WindowManagerPolicy.APPLICATION_MEDIA_SUBLAYER; import static android.view.WindowManagerPolicy.APPLICATION_PANEL_SUBLAYER; import android.content.Context; @@ -28,6 +29,7 @@ import android.graphics.PixelFormat; import android.graphics.PorterDuff; import android.graphics.Rect; import android.graphics.Region; +import android.os.Build; import android.os.Handler; import android.os.Message; import android.os.SystemClock; @@ -777,6 +779,31 @@ public class SurfaceView extends View { } /** + * This method still exists only for compatibility reasons because some applications have relied + * on this method via reflection. See Issue 36345857 for details. + * + * @deprecated No platform code is using this method anymore. + * @hide + */ + @Deprecated + public void setWindowType(int type) { + if (getContext().getApplicationInfo().targetSdkVersion > Build.VERSION_CODES.N_MR1) { + throw new UnsupportedOperationException( + "SurfaceView#setWindowType() has never been a public API."); + } + + if (type == TYPE_APPLICATION_PANEL) { + Log.e(TAG, "If you are calling SurfaceView#setWindowType(TYPE_APPLICATION_PANEL) " + + "just to make the SurfaceView to be placed on top of its window, you must " + + "call setZOrderOnTop(true) instead.", new Throwable()); + setZOrderOnTop(true); + return; + } + Log.e(TAG, "SurfaceView#setWindowType(int) is deprecated and now does nothing. " + + "type=" + type, new Throwable()); + } + + /** * Check to see if the surface has fixed size dimensions or if the surface's * dimensions are dimensions are dependent on its current layout. * diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 029caf918665..6cddbac736af 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -978,142 +978,123 @@ public class View implements Drawable.Callback, KeyEvent.Callback, */ public static final int AUTOFILL_MODE_MANUAL = 2; - /** @hide */ - @IntDef({ - AUTOFILL_HINT_NONE, - AUTOFILL_HINT_EMAIL_ADDRESS, - AUTOFILL_HINT_NAME, - AUTOFILL_HINT_POSTAL_ADDRESS, - AUTOFILL_HINT_PASSWORD, - AUTOFILL_HINT_PHONE, - AUTOFILL_HINT_USERNAME, - AUTOFILL_HINT_POSTAL_CODE, - AUTOFILL_HINT_CREDIT_CARD_NUMBER, - AUTOFILL_HINT_CREDIT_CARD_SECURITY_CODE, - AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_DATE, - AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_MONTH, - AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_YEAR, - AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_DAY, - }) - @Retention(RetentionPolicy.SOURCE) - public @interface AutofillHint {} - - /** - * No autofill hint is set. - * - * Use with {@link #setAutofillHint(int)} and <a href="#attr_android:autofillHint"> - * {@code android:autofillHint}. - */ - public static final int AUTOFILL_HINT_NONE = 0; - /** * This view contains an email address. * - * Use with {@link #setAutofillHint(int)} and <a href="#attr_android:autofillHint"> - * {@code android:autofillHint}. + * Use with {@link #setAutofillHint(String[])}, or set "{@value #AUTOFILL_HINT_EMAIL_ADDRESS}" + * to <a href="#attr_android:autofillHint"> {@code android:autofillHint}. */ - public static final int AUTOFILL_HINT_EMAIL_ADDRESS = 0x1; + public static final String AUTOFILL_HINT_EMAIL_ADDRESS = "emailAddress"; /** * The view contains a real name. * - * Use with {@link #setAutofillHint(int)} and <a href="#attr_android:autofillHint"> - * {@code android:autofillHint}. + * Use with {@link #setAutofillHint(String[])}, or set "{@value #AUTOFILL_HINT_NAME}" to + * <a href="#attr_android:autofillHint"> {@code android:autofillHint}. */ - public static final int AUTOFILL_HINT_NAME = 0x2; + public static final String AUTOFILL_HINT_NAME = "name"; /** * The view contains a user name. * - * Use with {@link #setAutofillHint(int)} and <a href="#attr_android:autofillHint"> - * {@code android:autofillHint}. + * Use with {@link #setAutofillHint(String[])}, or set "{@value #AUTOFILL_HINT_USERNAME}" to + * <a href="#attr_android:autofillHint"> {@code android:autofillHint}. */ - public static final int AUTOFILL_HINT_USERNAME = 0x4; + public static final String AUTOFILL_HINT_USERNAME = "username"; /** * The view contains a password. * - * Use with {@link #setAutofillHint(int)} and <a href="#attr_android:autofillHint"> - * {@code android:autofillHint}. + * Use with {@link #setAutofillHint(String[])}, or set "{@value #AUTOFILL_HINT_PASSWORD}" to + * <a href="#attr_android:autofillHint"> {@code android:autofillHint}. */ - public static final int AUTOFILL_HINT_PASSWORD = 0x8; + public static final String AUTOFILL_HINT_PASSWORD = "password"; /** * The view contains a phone number. * - * Use with {@link #setAutofillHint(int)} and <a href="#attr_android:autofillHint"> - * {@code android:autofillHint}. + * Use with {@link #setAutofillHint(String[])}, or set "{@value #AUTOFILL_HINT_PHONE}" to + * <a href="#attr_android:autofillHint"> {@code android:autofillHint}. */ - public static final int AUTOFILL_HINT_PHONE = 0x10; + public static final String AUTOFILL_HINT_PHONE = "phone"; /** * The view contains a postal address. * - * Use with {@link #setAutofillHint(int)} and <a href="#attr_android:autofillHint"> - * {@code android:autofillHint}. + * Use with {@link #setAutofillHint(String[])}, or set "{@value #AUTOFILL_HINT_POSTAL_ADDRESS}" + * to <a href="#attr_android:autofillHint"> {@code android:autofillHint}. */ - public static final int AUTOFILL_HINT_POSTAL_ADDRESS = 0x20; + public static final String AUTOFILL_HINT_POSTAL_ADDRESS = "postalAddress"; /** * The view contains a postal code. * - * Use with {@link #setAutofillHint(int)} and <a href="#attr_android:autofillHint"> - * {@code android:autofillHint}. + * Use with {@link #setAutofillHint(String[])}, or set "{@value #AUTOFILL_HINT_POSTAL_CODE}" to + * <a href="#attr_android:autofillHint"> {@code android:autofillHint}. */ - public static final int AUTOFILL_HINT_POSTAL_CODE = 0x40; + public static final String AUTOFILL_HINT_POSTAL_CODE = "postalCode"; /** * The view contains a credit card number. * - * Use with {@link #setAutofillHint(int)} and <a href="#attr_android:autofillHint"> - * {@code android:autofillHint}. + * Use with {@link #setAutofillHint(String[])}, or set "{@value + * #AUTOFILL_HINT_CREDIT_CARD_NUMBER}" to <a href="#attr_android:autofillHint"> {@code + * android:autofillHint}. */ - public static final int AUTOFILL_HINT_CREDIT_CARD_NUMBER = 0x80; + public static final String AUTOFILL_HINT_CREDIT_CARD_NUMBER = "creditCardNumber"; /** * The view contains a credit card security code. * - * Use with {@link #setAutofillHint(int)} and <a href="#attr_android:autofillHint"> - * {@code android:autofillHint}. + * Use with {@link #setAutofillHint(String[])}, or set "{@value + * #AUTOFILL_HINT_CREDIT_CARD_SECURITY_CODE}" to <a href="#attr_android:autofillHint"> {@code + * android:autofillHint}. */ - public static final int AUTOFILL_HINT_CREDIT_CARD_SECURITY_CODE = 0x100; + public static final String AUTOFILL_HINT_CREDIT_CARD_SECURITY_CODE = "creditCardSecurityCode"; /** * The view contains a credit card expiration date. * - * Use with {@link #setAutofillHint(int)} and <a href="#attr_android:autofillHint"> - * {@code android:autofillHint}. + * Use with {@link #setAutofillHint(String[])}, or set "{@value + * #AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_DATE}" to <a href="#attr_android:autofillHint"> {@code + * android:autofillHint}. */ - public static final int AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_DATE = 0x200; + public static final String AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_DATE = + "creditCardExpirationDate"; /** * The view contains the month a credit card expires. * - * Use with {@link #setAutofillHint(int)} and <a href="#attr_android:autofillHint"> - * {@code android:autofillHint}. + * Use with {@link #setAutofillHint(String[])}, or set "{@value + * #AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_MONTH}" to <a href="#attr_android:autofillHint"> {@code + * android:autofillHint}. */ - public static final int AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_MONTH = 0x400; + public static final String AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_MONTH = + "creditCardExpirationMonth"; /** * The view contains the year a credit card expires. * - * Use with {@link #setAutofillHint(int)} and <a href="#attr_android:autofillHint"> - * {@code android:autofillHint}. + * Use with {@link #setAutofillHint(String[])}, or set "{@value + * #AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_YEAR}" to <a href="#attr_android:autofillHint"> {@code + * android:autofillHint}. */ - public static final int AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_YEAR = 0x800; + public static final String AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_YEAR = + "creditCardExpirationYear"; /** * The view contains the day a credit card expires. * - * Use with {@link #setAutofillHint(int)} and <a href="#attr_android:autofillHint"> - * {@code android:autofillHint}. + * Use with {@link #setAutofillHint(String[])}, or set "{@value + * #AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_DAY}" to <a href="#attr_android:autofillHint"> {@code + * android:autofillHint}. */ - public static final int AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_DAY = 0x1000; + public static final String AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_DAY = "creditCardExpirationDay"; /** - * Hint for the autofill services that describes the content of the view. + * Hintd for the autofill services that describes the content of the view. */ - @AutofillHint private int mAutofillHint; + private @Nullable String[] mAutofillHint; /** @hide */ @IntDef({ @@ -5049,7 +5030,37 @@ public class View implements Drawable.Callback, KeyEvent.Callback, break; case R.styleable.View_autofillHint: if (a.peekValue(attr) != null) { - setAutofillHint(a.getInt(attr, AUTOFILL_HINT_NONE)); + CharSequence[] rawHints = null; + String rawString = null; + + if (a.getType(attr) == TypedValue.TYPE_REFERENCE) { + int resId = a.getResourceId(attr, 0); + + try { + rawHints = a.getTextArray(attr); + } catch (NullPointerException e) { + rawString = getResources().getString(resId); + } + } else { + rawString = a.getString(attr); + } + + if (rawHints == null) { + if (rawString == null) { + throw new IllegalArgumentException( + "Could not resolve autofillHint"); + } else { + rawHints = rawString.split(","); + } + } + + String[] hints = new String[rawHints.length]; + + int numHints = rawHints.length; + for (int rawHintNum = 0; rawHintNum < numHints; rawHintNum++) { + hints[rawHintNum] = rawHints[rawHintNum].toString().trim(); + } + setAutofillHint(hints); } break; case R.styleable.View_importantForAutofill: @@ -7257,9 +7268,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * Called when assist structure is being retrieved from a view as part of an autofill request. * * <p>This method already provides most of what's needed for autofill, but should be overridden + * when: * <ol> * <li>The view contents does not include PII (Personally Identifiable Information), so it - * can call {@link ViewStructure#setSanitized(boolean)} passing {@code true}. + * can call {@link ViewStructure#setDataIsSensitive(boolean)} passing {@code false}. * <li>It must set fields such {@link ViewStructure#setText(CharSequence)}, * {@link ViewStructure#setAutofillOptions(String[])}, or {@link ViewStructure#setUrl(String)}. * </ol> @@ -7433,8 +7445,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * </pre> * * @param value value to be autofilled. + * + * @return {@code true} if the view was successfully autofilled, {@code false} otherwise */ - public void autofill(@SuppressWarnings("unused") AutofillValue value) { + public boolean autofill(@SuppressWarnings("unused") AutofillValue value) { + return false; } /** @@ -7445,9 +7460,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * * @param value value to be autofilled. * @param virtualId id identifying the virtual child inside the custom view. + * + * @return {@code true} if the view was successfully autofilled, {@code false} otherwise */ - public void autofillVirtual(@SuppressWarnings("unused") int virtualId, + public boolean autofillVirtual(@SuppressWarnings("unused") int virtualId, @SuppressWarnings("unused") AutofillValue value) { + return false; } /** @@ -7464,12 +7482,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback, /** * Describes the content of a view so that a autofill service can fill in the appropriate data. * - * @return The hint set via the attribute + * @return The hint set via the attribute or {@code null} if no hint it set. * * @attr ref android.R.styleable#View_autofillHint */ @ViewDebug.ExportedProperty() - @AutofillHint public int getAutofillHint() { + @Nullable public String[] getAutofillHint() { return mAutofillHint; } @@ -9099,11 +9117,15 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * Sets the a hint that helps the autofill service to select the appropriate data to fill the * view. * - * @param autofillHint The autofill hint to set + * @param autofillHint The autofill hint to set. If the array is emtpy, {@code null} is set. * @attr ref android.R.styleable#View_autofillHint */ - public void setAutofillHint(@AutofillHint int autofillHint) { - mAutofillHint = autofillHint; + public void setAutofillHint(@Nullable String... autofillHint) { + if (autofillHint == null || autofillHint.length == 0) { + mAutofillHint = null; + } else { + mAutofillHint = autofillHint; + } } /** diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 580888c4c3bc..048b7c22237c 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -16,6 +16,7 @@ package android.view; +import static android.view.Display.INVALID_DISPLAY; import static android.view.View.PFLAG_DRAW_ANIMATION; import static android.view.WindowCallbacks.RESIZE_MODE_DOCKED_DIVIDER; import static android.view.WindowCallbacks.RESIZE_MODE_FREEFORM; @@ -28,10 +29,10 @@ import android.Manifest; import android.animation.LayoutTransition; import android.annotation.NonNull; import android.app.ActivityManager; +import android.app.ActivityThread; import android.app.ResourcesManager; import android.content.ClipData; import android.content.ClipDescription; -import android.content.ComponentCallbacks; import android.content.Context; import android.content.pm.PackageManager; import android.content.res.CompatibilityInfo; @@ -67,6 +68,7 @@ import android.os.Trace; import android.util.AndroidRuntimeException; import android.util.DisplayMetrics; import android.util.Log; +import android.util.MergedConfiguration; import android.util.Slog; import android.util.TimeUtils; import android.util.TypedValue; @@ -161,7 +163,55 @@ public final class ViewRootImpl implements ViewParent, static final ArrayList<Runnable> sFirstDrawHandlers = new ArrayList(); static boolean sFirstDrawComplete = false; - static final ArrayList<ComponentCallbacks> sConfigCallbacks = new ArrayList(); + /** + * Callback for notifying about global configuration changes. + */ + public interface ConfigChangedCallback { + + /** Notifies about global config change. */ + void onConfigurationChanged(Configuration globalConfig); + } + + private static final ArrayList<ConfigChangedCallback> sConfigCallbacks = new ArrayList<>(); + + /** + * Callback for notifying activities about override configuration changes. + */ + public interface ActivityConfigCallback { + + /** + * Notifies about override config change and/or move to different display. + * @param overrideConfig New override config to apply to activity. + * @param newDisplayId New display id, {@link Display#INVALID_DISPLAY} if not changed. + */ + void onConfigurationChanged(Configuration overrideConfig, int newDisplayId); + } + + /** + * Callback used to notify corresponding activity about override configuration change and make + * sure that all resources are set correctly before updating the ViewRootImpl's internal state. + */ + private ActivityConfigCallback mActivityConfigCallback; + + /** + * Used when configuration change first updates the config of corresponding activity. + * In that case we receive a call back from {@link ActivityThread} and this flag is used to + * preserve the initial value. + * + * @see #performConfigurationChange(Configuration, Configuration, boolean, int) + */ + private boolean mForceNextConfigUpdate; + + /** + * Signals that compatibility booleans have been initialized according to + * target SDK versions. + */ + private static boolean sCompatibilityDone = false; + + /** + * Always assign focus if a focusable View is available. + */ + private static boolean sAlwaysAssignFocus; /** * This list must only be modified by the main thread, so a lock is only needed when changing @@ -333,8 +383,12 @@ public final class ViewRootImpl implements ViewParent, private WindowInsets mLastWindowInsets; - final Configuration mLastConfiguration = new Configuration(); - final Configuration mPendingConfiguration = new Configuration(); + /** Last applied configuration obtained from resources. */ + private final Configuration mLastConfigurationFromResources = new Configuration(); + /** Last configuration reported from WM or via {@link #MSG_UPDATE_CONFIGURATION}. */ + private final MergedConfiguration mLastReportedMergedConfiguration = new MergedConfiguration(); + /** Configurations waiting to be applied. */ + private final MergedConfiguration mPendingMergedConfiguration = new MergedConfiguration(); boolean mScrollMayChange; @SoftInputModeFlags @@ -451,6 +505,13 @@ public final class ViewRootImpl implements ViewParent, mFallbackEventHandler = new PhoneFallbackEventHandler(context); mChoreographer = Choreographer.getInstance(); mDisplayManager = (DisplayManager)context.getSystemService(Context.DISPLAY_SERVICE); + + if (!sCompatibilityDone) { + sAlwaysAssignFocus = mTargetSdkVersion < Build.VERSION_CODES.O; + + sCompatibilityDone = true; + } + loadSystemProperties(); } @@ -462,12 +523,18 @@ public final class ViewRootImpl implements ViewParent, } } - public static void addConfigCallback(ComponentCallbacks callback) { + /** Add static config callback to be notified about global config changes. */ + public static void addConfigCallback(ConfigChangedCallback callback) { synchronized (sConfigCallbacks) { sConfigCallbacks.add(callback); } } + /** Add activity config callback to be notified about override config changes. */ + public void setActivityConfigCallback(ActivityConfigCallback callback) { + mActivityConfigCallback = callback; + } + public void addWindowCallbacks(WindowCallbacks callback) { if (USE_MT_RENDERER) { synchronized (mWindowCallbacks) { @@ -1540,6 +1607,7 @@ public final class ViewRootImpl implements ViewParent, mFullRedrawNeeded = true; mLayoutRequested = true; + final Configuration config = mContext.getResources().getConfiguration(); if (shouldUseDisplaySize(lp)) { // NOTE -- system code, won't try to do compat mode. Point size = new Point(); @@ -1547,7 +1615,6 @@ public final class ViewRootImpl implements ViewParent, desiredWindowWidth = size.x; desiredWindowHeight = size.y; } else { - Configuration config = mContext.getResources().getConfiguration(); desiredWindowWidth = dipToPx(config.screenWidthDp); desiredWindowHeight = dipToPx(config.screenHeightDp); } @@ -1559,11 +1626,11 @@ public final class ViewRootImpl implements ViewParent, mAttachInfo.mHasWindowFocus = false; mAttachInfo.mWindowVisibility = viewVisibility; mAttachInfo.mRecomputeGlobalAttributes = false; - mLastConfiguration.setTo(host.getResources().getConfiguration()); + mLastConfigurationFromResources.setTo(config); mLastSystemUiVisibility = mAttachInfo.mSystemUiVisibility; // Set the layout direction if it has not been set before (inherit is the default) if (mViewLayoutDirectionInitial == View.LAYOUT_DIRECTION_INHERIT) { - host.setLayoutDirection(mLastConfiguration.getLayoutDirection()); + host.setLayoutDirection(config.getLayoutDirection()); } host.dispatchAttachedToWindow(mAttachInfo, 0); mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(true); @@ -1808,11 +1875,14 @@ public final class ViewRootImpl implements ViewParent, + " outsets=" + mPendingOutsets.toShortString() + " surface=" + mSurface); - if (mPendingConfiguration.seq != 0) { + final Configuration pendingMergedConfig = + mPendingMergedConfiguration.getMergedConfiguration(); + if (pendingMergedConfig.seq != 0) { if (DEBUG_CONFIGURATION) Log.v(mTag, "Visible with new config: " - + mPendingConfiguration); - updateConfiguration(new Configuration(mPendingConfiguration), !mFirst); - mPendingConfiguration.seq = 0; + + pendingMergedConfig); + performConfigurationChange(mPendingMergedConfiguration, !mFirst, + INVALID_DISPLAY /* same display */); + pendingMergedConfig.seq = 0; updatedConfiguration = true; } @@ -2180,7 +2250,7 @@ public final class ViewRootImpl implements ViewParent, } } - if (mFirst) { + if (mFirst && sAlwaysAssignFocus) { // handle first focus request if (DEBUG_INPUT_RESIZE) Log.v(mTag, "First: mView.hasFocus()=" + mView.hasFocus()); @@ -3290,7 +3360,9 @@ public final class ViewRootImpl implements ViewParent, checkThread(); if (mView != null) { if (!mView.hasFocus()) { - v.requestFocus(); + if (sAlwaysAssignFocus) { + v.requestFocus(); + } } else { // the one case where will transfer focus away from the current one // is if the current view is a view group that prefers to give focus @@ -3368,43 +3440,82 @@ public final class ViewRootImpl implements ViewParent, unscheduleTraversals(); } - void updateConfiguration(Configuration config, boolean force) { + /** + * Notifies all callbacks that configuration and/or display has changed and updates internal + * state. + * @param mergedConfiguration New global and override config in {@link MergedConfiguration} + * container. + * @param force Flag indicating if we should force apply the config. + * @param newDisplayId Id of new display if moved, {@link Display#INVALID_DISPLAY} if not + * changed. + */ + private void performConfigurationChange(MergedConfiguration mergedConfiguration, boolean force, + int newDisplayId) { + if (mergedConfiguration == null) { + throw new IllegalArgumentException("No merged config provided."); + } + + Configuration globalConfig = mergedConfiguration.getGlobalConfiguration(); + final Configuration overrideConfig = mergedConfiguration.getOverrideConfiguration(); if (DEBUG_CONFIGURATION) Log.v(mTag, - "Applying new config to window " - + mWindowAttributes.getTitle() - + ": " + config); + "Applying new config to window " + mWindowAttributes.getTitle() + + ", globalConfig: " + globalConfig + + ", overrideConfig: " + overrideConfig); - CompatibilityInfo ci = mDisplay.getDisplayAdjustments().getCompatibilityInfo(); + final CompatibilityInfo ci = mDisplay.getDisplayAdjustments().getCompatibilityInfo(); if (!ci.equals(CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO)) { - config = new Configuration(config); - ci.applyToConfiguration(mNoncompatDensity, config); + globalConfig = new Configuration(globalConfig); + ci.applyToConfiguration(mNoncompatDensity, globalConfig); } synchronized (sConfigCallbacks) { for (int i=sConfigCallbacks.size()-1; i>=0; i--) { - sConfigCallbacks.get(i).onConfigurationChanged(config); + sConfigCallbacks.get(i).onConfigurationChanged(globalConfig); } } - if (mView != null) { - // At this point the resources have been updated to - // have the most recent config, whatever that is. Use - // the one in them which may be newer. - final Resources localResources = mView.getResources(); - config = localResources.getConfiguration(); - if (force || mLastConfiguration.diff(config) != 0) { - // Update the display with new DisplayAdjustments. - mDisplay = ResourcesManager.getInstance().getAdjustedDisplay( - mDisplay.getDisplayId(), localResources); - final int lastLayoutDirection = mLastConfiguration.getLayoutDirection(); - final int currentLayoutDirection = config.getLayoutDirection(); - mLastConfiguration.setTo(config); - if (lastLayoutDirection != currentLayoutDirection && - mViewLayoutDirectionInitial == View.LAYOUT_DIRECTION_INHERIT) { - mView.setLayoutDirection(currentLayoutDirection); - } - mView.dispatchConfigurationChanged(config); + mLastReportedMergedConfiguration.setConfiguration(globalConfig, overrideConfig); + + mForceNextConfigUpdate = force; + if (mActivityConfigCallback != null) { + // An activity callback is set - notify it about override configuration update. + // This basically initiates a round trip to ActivityThread and back, which will ensure + // that corresponding activity and resources are updated before updating inner state of + // ViewRootImpl. Eventually it will call #updateConfiguration(). + mActivityConfigCallback.onConfigurationChanged(overrideConfig, newDisplayId); + } else { + // There is no activity callback - update the configuration right away. + updateConfiguration(); + } + mForceNextConfigUpdate = false; + } + + /** + * Update display and views if last applied merged configuration changed. + */ + public void updateConfiguration() { + if (mView == null) { + return; + } + + // At this point the resources have been updated to + // have the most recent config, whatever that is. Use + // the one in them which may be newer. + final Resources localResources = mView.getResources(); + final Configuration config = localResources.getConfiguration(); + if (mForceNextConfigUpdate || mLastConfigurationFromResources.diff(config) != 0) { + // Update the display with new DisplayAdjustments. + mDisplay = ResourcesManager.getInstance().getAdjustedDisplay( + mDisplay.getDisplayId(), localResources); + + final int lastLayoutDirection = mLastConfigurationFromResources.getLayoutDirection(); + final int currentLayoutDirection = config.getLayoutDirection(); + mLastConfigurationFromResources.setTo(config); + if (lastLayoutDirection != currentLayoutDirection + && mViewLayoutDirectionInitial == View.LAYOUT_DIRECTION_INHERIT) { + mView.setLayoutDirection(currentLayoutDirection); } + mView.dispatchConfigurationChanged(config); } } @@ -3562,13 +3673,16 @@ public final class ViewRootImpl implements ViewParent, if (mAdded) { SomeArgs args = (SomeArgs) msg.obj; - if (mDisplay.getDisplayId() != args.argi3) { - onMovedToDisplay(args.argi3); + final int displayId = args.argi3; + final boolean displayChanged = mDisplay.getDisplayId() != displayId; + if (displayChanged) { + onMovedToDisplay(displayId); } - Configuration config = (Configuration) args.arg4; - if (config != null) { - updateConfiguration(config, false); + final MergedConfiguration mergedConfiguration = (MergedConfiguration) args.arg4; + if (mergedConfiguration != null) { + performConfigurationChange(mergedConfiguration, false /* force */, + displayChanged ? displayId : INVALID_DISPLAY /* same display */); } final boolean framesChanged = !mWinFrame.equals(args.arg1) @@ -3739,11 +3853,19 @@ public final class ViewRootImpl implements ViewParent, handleDispatchSystemUiVisibilityChanged((SystemUiVisibilityInfo) msg.obj); } break; case MSG_UPDATE_CONFIGURATION: { - Configuration config = (Configuration)msg.obj; - if (config.isOtherSeqNewer(mLastConfiguration)) { - config = mLastConfiguration; + Configuration config = (Configuration) msg.obj; + if (config.isOtherSeqNewer( + mLastReportedMergedConfiguration.getMergedConfiguration())) { + // If we already have a newer merged config applied - use its global part. + config = mLastReportedMergedConfiguration.getGlobalConfiguration(); } - updateConfiguration(config, false); + + // Use the newer global config and last reported override config. + mPendingMergedConfiguration.setConfiguration(config, + mLastReportedMergedConfiguration.getOverrideConfiguration()); + + performConfigurationChange(mPendingMergedConfiguration, false /* force */, + INVALID_DISPLAY /* same display */); } break; case MSG_CLEAR_ACCESSIBILITY_FOCUS_HOST: { setAccessibilityFocus(null, null); @@ -4463,9 +4585,7 @@ public final class ViewRootImpl implements ViewParent, return true; } } else { - // find the best view to give focus to in this non-touch-mode with no-focus - View v = focusSearch(null, direction); - if (v != null && v.requestFocus(direction)) { + if (mView.restoreDefaultFocus()) { return true; } } @@ -4475,9 +4595,10 @@ public final class ViewRootImpl implements ViewParent, private boolean performKeyboardGroupNavigation(int direction) { final View focused = mView.findFocus(); - View cluster = focused != null - ? focused.keyboardNavigationClusterSearch(null, direction) - : keyboardNavigationClusterSearch(null, direction); + if (focused == null && mView.restoreDefaultFocus()) { + return true; + } + View cluster = focused.keyboardNavigationClusterSearch(null, direction); // Since requestFocus only takes "real" focus directions (and therefore also // restoreFocusInCluster), convert forward/backward focus into FOCUS_DOWN. @@ -5883,7 +6004,7 @@ public final class ViewRootImpl implements ViewParent, if (params != null) { if (DBG) Log.d(mTag, "WindowLayout in layoutWindow:" + params); } - mPendingConfiguration.seq = 0; + mPendingMergedConfiguration.getMergedConfiguration().seq = 0; //Log.d(mTag, ">>>>>> CALLING relayout"); if (params != null && mOrigWindowType != params.type) { // For compatibility with old apps, don't crash here. @@ -5899,8 +6020,8 @@ public final class ViewRootImpl implements ViewParent, (int) (mView.getMeasuredHeight() * appScale + 0.5f), viewVisibility, insetsPending ? WindowManagerGlobal.RELAYOUT_INSETS_PENDING : 0, mWinFrame, mPendingOverscanInsets, mPendingContentInsets, mPendingVisibleInsets, - mPendingStableInsets, mPendingOutsets, mPendingBackDropFrame, mPendingConfiguration, - mSurface); + mPendingStableInsets, mPendingOutsets, mPendingBackDropFrame, + mPendingMergedConfiguration, mSurface); mPendingAlwaysConsumeNavBar = (relayoutResult & WindowManagerGlobal.RELAYOUT_RES_CONSUME_ALWAYS_NAV_BAR) != 0; @@ -6180,9 +6301,9 @@ public final class ViewRootImpl implements ViewParent, } } - public void dispatchResized(Rect frame, Rect overscanInsets, Rect contentInsets, + private void dispatchResized(Rect frame, Rect overscanInsets, Rect contentInsets, Rect visibleInsets, Rect stableInsets, Rect outsets, boolean reportDraw, - Configuration newConfig, Rect backDropFrame, boolean forceLayout, + MergedConfiguration mergedConfiguration, Rect backDropFrame, boolean forceLayout, boolean alwaysConsumeNavBar, int displayId) { if (DEBUG_LAYOUT) Log.v(mTag, "Resizing " + this + ": frame=" + frame.toShortString() + " contentInsets=" + contentInsets.toShortString() @@ -6214,7 +6335,8 @@ public final class ViewRootImpl implements ViewParent, args.arg1 = sameProcessCall ? new Rect(frame) : frame; args.arg2 = sameProcessCall ? new Rect(contentInsets) : contentInsets; args.arg3 = sameProcessCall ? new Rect(visibleInsets) : visibleInsets; - args.arg4 = sameProcessCall && newConfig != null ? new Configuration(newConfig) : newConfig; + args.arg4 = sameProcessCall && mergedConfiguration != null + ? new MergedConfiguration(mergedConfiguration) : null; args.arg5 = sameProcessCall ? new Rect(overscanInsets) : overscanInsets; args.arg6 = sameProcessCall ? new Rect(stableInsets) : stableInsets; args.arg7 = sameProcessCall ? new Rect(outsets) : outsets; @@ -7224,13 +7346,13 @@ public final class ViewRootImpl implements ViewParent, @Override public void resized(Rect frame, Rect overscanInsets, Rect contentInsets, Rect visibleInsets, Rect stableInsets, Rect outsets, boolean reportDraw, - Configuration newConfig, Rect backDropFrame, boolean forceLayout, + MergedConfiguration mergedConfiguration, Rect backDropFrame, boolean forceLayout, boolean alwaysConsumeNavBar, int displayId) { final ViewRootImpl viewAncestor = mViewAncestor.get(); if (viewAncestor != null) { viewAncestor.dispatchResized(frame, overscanInsets, contentInsets, - visibleInsets, stableInsets, outsets, reportDraw, newConfig, backDropFrame, - forceLayout, alwaysConsumeNavBar, displayId); + visibleInsets, stableInsets, outsets, reportDraw, mergedConfiguration, + backDropFrame, forceLayout, alwaysConsumeNavBar, displayId); } } diff --git a/core/java/android/view/ViewStructure.java b/core/java/android/view/ViewStructure.java index bccaca2c8baa..38c7738b4bac 100644 --- a/core/java/android/view/ViewStructure.java +++ b/core/java/android/view/ViewStructure.java @@ -16,6 +16,7 @@ package android.view; +import android.annotation.Nullable; import android.graphics.Matrix; import android.graphics.Rect; import android.os.Bundle; @@ -323,7 +324,7 @@ public abstract class ViewStructure { * Sets the a hint that helps the autofill service to select the appropriate data to fill the * view. */ - public abstract void setAutofillHint(@View.AutofillHint int hint); + public abstract void setAutofillHint(@Nullable String[] hint); /** * Sets the {@link AutofillValue} representing the current value of this node. @@ -346,19 +347,27 @@ public abstract class ViewStructure { public abstract void setInputType(int inputType); /** - * Marks this node as sanitized so its content are sent on {@link + * Sets whether the data on this node is sensitive; if it is, then its content (text, autofill + * value, etc..) is striped before calls to {@link * android.service.autofill.AutofillService#onFillRequest(android.app.assist.AssistStructure, - * Bundle, android.os.CancellationSignal, android.service.autofill.FillCallback)}. + * Bundle, int, android.os.CancellationSignal, android.service.autofill.FillCallback)}. * - * <p>Only nodes that does not have PII (Personally Identifiable Information - sensitive data - * such as email addresses, credit card numbers, passwords, etc...) should be marked - * as sanitized; a good rule of thumb is to mark as sanitized nodes whose value were statically - * set from resources. + * <p>By default, all nodes are assumed to be sensitive, and only nodes that does not have PII + * (Personally Identifiable Information - sensitive data such as email addresses, credit card + * numbers, passwords, etc...) should be marked as non-sensitive; a good rule of thumb is to + * mark as non-sensitive nodes whose value were statically set from resources. + * + * <p>Notice that the content of even sensitive nodes are sent to the service (through the + * {@link + * android.service.autofill.AutofillService#onSaveRequest(android.app.assist.AssistStructure, + * Bundle, android.service.autofill.SaveCallback)} call) when the user consented to save + * thedata, so it is important to set the content of sensitive nodes as well, but mark them as + * sensitive. * * <p>Should only be set when the node is used for autofill purposes - it will be ignored * when used for Assist. */ - public abstract void setSanitized(boolean sanitized); + public abstract void setDataIsSensitive(boolean sensitive); /** * Call when done populating a {@link ViewStructure} returned by diff --git a/core/java/android/view/WindowManagerInternal.java b/core/java/android/view/WindowManagerInternal.java index a541a4cd3b1e..6dbc09ceef97 100644 --- a/core/java/android/view/WindowManagerInternal.java +++ b/core/java/android/view/WindowManagerInternal.java @@ -168,6 +168,14 @@ public abstract class WindowManagerInternal { public abstract void setMagnificationSpec(MagnificationSpec spec); /** + * Set by the accessibility framework to indicate whether the magnifiable regions of the display + * should be shown. + * + * @param show {@code true} to show magnifiable region bounds, {@code false} to hide + */ + public abstract void setForceShowMagnifiableBounds(boolean show); + + /** * Obtains the magnification regions. * * @param magnificationRegion the current magnification region diff --git a/core/java/android/view/autofill/AutoFillValue.java b/core/java/android/view/autofill/AutoFillValue.java index 5dd17f1c2bee..4774d8f0a71a 100644 --- a/core/java/android/view/autofill/AutoFillValue.java +++ b/core/java/android/view/autofill/AutoFillValue.java @@ -89,11 +89,6 @@ public final class AutoFillValue implements Parcelable { return mRealValue.equals(other.mRealValue); } - /** @hide */ - public String coerceToString() { - return mRealValue.coerceToString(); - } - @Override public String toString() { if (!DEBUG) return super.toString(); diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java index c4f90dcbaf3c..b852aabe3b3d 100644 --- a/core/java/android/view/autofill/AutofillManager.java +++ b/core/java/android/view/autofill/AutofillManager.java @@ -71,12 +71,19 @@ public final class AutofillManager { public static final String EXTRA_AUTHENTICATION_RESULT = "android.view.autofill.extra.AUTHENTICATION_RESULT"; - /** @hide */ public static final int FLAG_START_SESSION = 0x1; - /** @hide */ public static final int FLAG_VIEW_ENTERED = 0x2; - /** @hide */ public static final int FLAG_VIEW_EXITED = 0x4; - /** @hide */ public static final int FLAG_VALUE_CHANGED = 0x8; + // Public flags start from the lowest bit + /** + * Indicates autofill was explicitly requested by the user. + */ + public static final int FLAG_MANUAL_REQUEST = 0x1; - private final Rect mTempRect = new Rect(); + // Private flags start from the highest bit + /** @hide */ public static final int FLAG_START_SESSION = 0x80000000; + /** @hide */ public static final int FLAG_VIEW_ENTERED = 0x40000000; + /** @hide */ public static final int FLAG_VIEW_EXITED = 0x20000000; + /** @hide */ public static final int FLAG_VALUE_CHANGED = 0x10000000; + + @NonNull private final Rect mTempRect = new Rect(); private final IAutoFillManager mService; private IAutoFillManagerClient mServiceClient; @@ -121,6 +128,66 @@ public final class AutofillManager { } /** + * Checkes whether autofill is enabled for the current user. + * + * <p>Typically used to determine whether the option to explicitly request autofill should + * be offered - see {@link #requestAutofill(View)}. + * + * @return whether autofill is enabled for the current user. + */ + public boolean isEnabled() { + ensureServiceClientAddedIfNeeded(); + return mEnabled; + } + + /** + * Explicitly requests a new autofill context. + * + * <p>Normally, the autofill context is automatically started when autofillable views are + * focused, but this method should be used in the cases where it must be explicitly requested, + * like a view that provides a contextual menu allowing users to autofill the activity. + * + * @param view view requesting the new autofill context. + */ + public void requestAutofill(@NonNull View view) { + ensureServiceClientAddedIfNeeded(); + + if (!mEnabled) { + return; + } + + final Rect bounds = mTempRect; + view.getBoundsOnScreen(bounds); + final AutofillId id = getAutofillId(view); + final AutofillValue value = view.getAutofillValue(); + + startSession(id, view.getWindowToken(), bounds, value, FLAG_MANUAL_REQUEST); + } + + /** + * Explicitly requests a new autofill context for virtual views. + * + * <p>Normally, the autofill context is automatically started when autofillable views are + * focused, but this method should be used in the cases where it must be explicitly requested, + * like a virtual view that provides a contextual menu allowing users to autofill the activity. + * + * @param view the {@link View} whose descendant is the virtual view. + * @param childId id identifying the virtual child inside the view. + * @param bounds child boundaries, relative to the top window. + */ + public void requestAutofill(@NonNull View view, int childId, @NonNull Rect bounds) { + ensureServiceClientAddedIfNeeded(); + + if (!mEnabled) { + return; + } + + final AutofillId id = getAutofillId(view, childId); + startSession(id, view.getWindowToken(), bounds, null, FLAG_MANUAL_REQUEST); + } + + + /** * Called when a {@link View} that supports autofill is entered. * * @param view {@link View} that was entered. @@ -129,6 +196,9 @@ public final class AutofillManager { ensureServiceClientAddedIfNeeded(); if (!mEnabled) { + if (mCallback != null) { + mCallback.onAutofillEvent(view, AutofillCallback.EVENT_INPUT_UNAVAILABLE); + } return; } @@ -139,7 +209,7 @@ public final class AutofillManager { if (!mHasSession) { // Starts new session. - startSession(id, view.getWindowToken(), bounds, value); + startSession(id, view.getWindowToken(), bounds, value, 0); } else { // Update focus on existing session. updateSession(id, bounds, value, FLAG_VIEW_ENTERED); @@ -174,6 +244,10 @@ public final class AutofillManager { ensureServiceClientAddedIfNeeded(); if (!mEnabled) { + if (mCallback != null) { + mCallback.onAutofillEventVirtual(view, childId, + AutofillCallback.EVENT_INPUT_UNAVAILABLE); + } return; } @@ -181,7 +255,7 @@ public final class AutofillManager { if (!mHasSession) { // Starts new session. - startSession(id, view.getWindowToken(), bounds, null); + startSession(id, view.getWindowToken(), bounds, null, 0); } else { // Update focus on existing session. updateSession(id, bounds, null, FLAG_VIEW_ENTERED); @@ -224,16 +298,16 @@ public final class AutofillManager { /** * Called to indicate the value of an autofillable virtual {@link View} changed. * - * @param parent parent view whose value changed. + * @param view the {@link View} whose descendant is the virtual view. * @param childId id identifying the virtual child inside the parent view. * @param value new value of the child. */ - public void notifyVirtualValueChanged(View parent, int childId, AutofillValue value) { + public void notifyVirtualValueChanged(View view, int childId, AutofillValue value) { if (!mEnabled || !mHasSession) { return; } - final AutofillId id = getAutofillId(parent, childId); + final AutofillId id = getAutofillId(view, childId); updateSession(id, null, value, FLAG_VALUE_CHANGED); } @@ -304,17 +378,18 @@ public final class AutofillManager { return new AutofillId(parent.getAccessibilityViewId(), childId); } - private void startSession(AutofillId id, IBinder windowToken, Rect bounds, - AutofillValue value) { + private void startSession(@NonNull AutofillId id, @NonNull IBinder windowToken, + @NonNull Rect bounds, @NonNull AutofillValue value, int flags) { if (DEBUG) { - Log.d(TAG, "startSession(): id=" + id + ", bounds=" + bounds + ", value=" + value); + Log.d(TAG, "startSession(): id=" + id + ", bounds=" + bounds + ", value=" + value + + ", flags=" + flags); } try { mService.startSession(mContext.getActivityToken(), windowToken, mServiceClient.asBinder(), id, bounds, value, mContext.getUserId(), - mCallback != null); - final AutofillClient client = getClient(); + mCallback != null, flags, mContext.getOpPackageName()); + AutofillClient client = getClient(); if (client != null) { client.resetableStateAvailable(); } @@ -471,6 +546,15 @@ public final class AutofillManager { public static final int EVENT_INPUT_HIDDEN = 2; /** + * The auto-fill input UI affordance associated with the view won't be shown because + * autofill is not available. + * + * <p>If the view provides its own auto-complete UI affordance but was not displaying it + * to avoid flickering, it could shown it upon receiving this event. + */ + public static final int EVENT_INPUT_UNAVAILABLE = 3; + + /** * Called after a change in the autofill state associated with a view. * * @param view view associated with the change. diff --git a/core/java/android/view/autofill/AutofillValue.java b/core/java/android/view/autofill/AutofillValue.java index 0c7620e69c62..e2dd7fe9eeaa 100644 --- a/core/java/android/view/autofill/AutofillValue.java +++ b/core/java/android/view/autofill/AutofillValue.java @@ -16,13 +16,23 @@ package android.view.autofill; +import static android.view.View.AUTOFILL_TYPE_DATE; +import static android.view.View.AUTOFILL_TYPE_LIST; +import static android.view.View.AUTOFILL_TYPE_TEXT; +import static android.view.View.AUTOFILL_TYPE_TOGGLE; import static android.view.autofill.Helper.DEBUG; +import static android.view.autofill.Helper.VERBOSE; +import android.annotation.NonNull; import android.annotation.Nullable; import android.os.Parcel; import android.os.Parcelable; import android.view.View; +import com.android.internal.util.Preconditions; + +import java.util.Objects; + /** * Abstracts how a {@link View} can be autofilled by an * {@link android.service.autofill.AutofillService}. @@ -31,52 +41,107 @@ import android.view.View; * {@link View#getAutofillType()}. */ public final class AutofillValue implements Parcelable { - private final String mText; - private final int mListIndex; - private final boolean mToggle; - private final long mDate; + private final @View.AutofillType int mType; + private final @NonNull Object mValue; - private AutofillValue(CharSequence text, int listIndex, boolean toggle, long date) { - mText = (text == null) ? null : text.toString(); - mListIndex = listIndex; - mToggle = toggle; - mDate = date; + private AutofillValue(@View.AutofillType int type, @NonNull Object value) { + mType = type; + mValue = value; } /** * Gets the value to autofill a text field. * - * <p>See {@link View#AUTOFILL_TYPE_TEXT} for more info. + * <p>See {@link View#AUTOFILL_TYPE_TEXT} for more info.</p> + * + * @throws IllegalStateException if the value is not a text value + */ + @NonNull public CharSequence getTextValue() { + Preconditions.checkState(isText(), "value must be a text value, not type=" + mType); + return (CharSequence) mValue; + } + + /** + * Checks is this is a text value. + * + * <p>See {@link View#AUTOFILL_TYPE_TEXT} for more info.</p> */ - public CharSequence getTextValue() { - return mText; + public boolean isText() { + return mType == AUTOFILL_TYPE_TEXT; } /** * Gets the value to autofill a toggable field. * - * <p>See {@link View#AUTOFILL_TYPE_TOGGLE} for more info. + * <p>See {@link View#AUTOFILL_TYPE_TOGGLE} for more info.</p> + * + * @throws IllegalStateException if the value is not a toggle value */ public boolean getToggleValue() { - return mToggle; + Preconditions.checkState(isToggle(), "value must be a toggle value, not type=" + mType); + return (Boolean) mValue; + } + + /** + * Checks is this is a toggle value. + * + * <p>See {@link View#AUTOFILL_TYPE_TOGGLE} for more info.</p> + */ + public boolean isToggle() { + return mType == AUTOFILL_TYPE_TOGGLE; } /** * Gets the value to autofill a selection list field. * - * <p>See {@link View#AUTOFILL_TYPE_LIST} for more info. + * <p>See {@link View#AUTOFILL_TYPE_LIST} for more info.</p> + * + * @throws IllegalStateException if the value is not a list value */ public int getListValue() { - return mListIndex; + Preconditions.checkState(isList(), "value must be a list value, not type=" + mType); + return (Integer) mValue; + } + + /** + * Checks is this is a list value. + * + * <p>See {@link View#AUTOFILL_TYPE_LIST} for more info.</p> + */ + public boolean isList() { + return mType == AUTOFILL_TYPE_LIST; } /** * Gets the value to autofill a date field. * - * <p>See {@link View#AUTOFILL_TYPE_DATE} for more info. + * <p>See {@link View#AUTOFILL_TYPE_DATE} for more info.</p> + * + * @throws IllegalStateException if the value is not a date value */ public long getDateValue() { - return mDate; + Preconditions.checkState(isDate(), "value must be a date value, not type=" + mType); + return (Long) mValue; + } + + /** + * Checks is this is a date value. + * + * <p>See {@link View#AUTOFILL_TYPE_DATE} for more info.</p> + */ + public boolean isDate() { + return mType == AUTOFILL_TYPE_DATE; + } + + /** + * Used to define whether a field is empty so it's not sent to service on save. + * + * <p>Only applies to some types, like text. + * + * @hide + */ + public boolean isEmpty() { + return isText() && ((CharSequence) mValue).length() == 0; } ///////////////////////////////////// @@ -85,13 +150,7 @@ public final class AutofillValue implements Parcelable { @Override public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + ((mText == null) ? 0 : mText.hashCode()); - result = prime * result + mListIndex; - result = prime * result + (mToggle ? 1231 : 1237); - result = prime * result + (int) (mDate ^ (mDate >>> 32)); - return result; + return mType + mValue.hashCode(); } @Override @@ -100,32 +159,24 @@ public final class AutofillValue implements Parcelable { if (obj == null) return false; if (getClass() != obj.getClass()) return false; final AutofillValue other = (AutofillValue) obj; - if (mText == null) { - if (other.mText != null) return false; + + if (mType != other.mType) return false; + + if (isText()) { + return mValue.toString().equals(other.mValue.toString()); } else { - if (!mText.equals(other.mText)) return false; + return Objects.equals(mValue, other.mValue); } - if (mListIndex != other.mListIndex) return false; - if (mToggle != other.mToggle) return false; - if (mDate != other.mDate) return false; - return true; - } - - /** @hide */ - public String coerceToString() { - // TODO(b/33197203): How can we filter on toggles or list values? - return mText; } @Override public String toString() { if (!DEBUG) return super.toString(); - if (mText != null) { - return mText.length() + "_chars"; - } + final String sanitizedValue = isText() && !VERBOSE + ? ((CharSequence) mValue).length() + "_chars" : mValue.toString(); - return "[l=" + mListIndex + ", t=" + mToggle + ", d=" + mDate + "]"; + return "[type=" + mType + ", value=" + sanitizedValue + "]"; } ///////////////////////////////////// @@ -139,17 +190,44 @@ public final class AutofillValue implements Parcelable { @Override public void writeToParcel(Parcel parcel, int flags) { - parcel.writeString(mText); - parcel.writeInt(mListIndex); - parcel.writeInt(mToggle ? 1 : 0); - parcel.writeLong(mDate); + parcel.writeInt(mType); + + switch (mType) { + case AUTOFILL_TYPE_TEXT: + parcel.writeCharSequence((CharSequence) mValue); + break; + case AUTOFILL_TYPE_TOGGLE: + parcel.writeInt((Boolean) mValue ? 1 : 0); + break; + case AUTOFILL_TYPE_LIST: + parcel.writeInt((Integer) mValue); + break; + case AUTOFILL_TYPE_DATE: + parcel.writeLong((Long) mValue); + break; + } } - private AutofillValue(Parcel parcel) { - mText = parcel.readString(); - mListIndex = parcel.readInt(); - mToggle = parcel.readInt() == 1; - mDate = parcel.readLong(); + private AutofillValue(@NonNull Parcel parcel) { + mType = parcel.readInt(); + + switch (mType) { + case AUTOFILL_TYPE_TEXT: + mValue = parcel.readCharSequence(); + break; + case AUTOFILL_TYPE_TOGGLE: + int rawValue = parcel.readInt(); + mValue = rawValue != 0; + break; + case AUTOFILL_TYPE_LIST: + mValue = parcel.readInt(); + break; + case AUTOFILL_TYPE_DATE: + mValue = parcel.readLong(); + break; + default: + throw new IllegalArgumentException("type=" + mType + " not valid"); + } } public static final Parcelable.Creator<AutofillValue> CREATOR = @@ -175,9 +253,8 @@ public final class AutofillValue implements Parcelable { * <p>See {@link View#AUTOFILL_TYPE_TEXT} for more info. */ // TODO(b/33197203): use cache - @Nullable public static AutofillValue forText(@Nullable CharSequence value) { - return value == null ? null : new AutofillValue(value, 0, false, 0); + return value == null ? null : new AutofillValue(AUTOFILL_TYPE_TEXT, value); } /** @@ -187,7 +264,7 @@ public final class AutofillValue implements Parcelable { * <p>See {@link View#AUTOFILL_TYPE_TOGGLE} for more info. */ public static AutofillValue forToggle(boolean value) { - return new AutofillValue(null, 0, value, 0); + return new AutofillValue(AUTOFILL_TYPE_TOGGLE, value); } /** @@ -197,7 +274,7 @@ public final class AutofillValue implements Parcelable { * <p>See {@link View#AUTOFILL_TYPE_LIST} for more info. */ public static AutofillValue forList(int value) { - return new AutofillValue(null, value, false, 0); + return new AutofillValue(AUTOFILL_TYPE_LIST, value); } /** @@ -206,6 +283,6 @@ public final class AutofillValue implements Parcelable { * <p>See {@link View#AUTOFILL_TYPE_DATE} for more info. */ public static AutofillValue forDate(long value) { - return new AutofillValue(null, 0, false, value); + return new AutofillValue(AUTOFILL_TYPE_DATE, value); } } diff --git a/core/java/android/view/autofill/IAutoFillManager.aidl b/core/java/android/view/autofill/IAutoFillManager.aidl index 86a49653d367..97210cc919a5 100644 --- a/core/java/android/view/autofill/IAutoFillManager.aidl +++ b/core/java/android/view/autofill/IAutoFillManager.aidl @@ -32,7 +32,7 @@ interface IAutoFillManager { boolean addClient(in IAutoFillManagerClient client, int userId); oneway void startSession(in IBinder activityToken, IBinder windowToken, in IBinder appCallback, in AutofillId autoFillId, in Rect bounds, in AutofillValue value, int userId, - boolean hasCallback); + boolean hasCallback, int flags, String packageName); oneway void updateSession(in IBinder activityToken, in AutofillId id, in Rect bounds, in AutofillValue value, int flags, int userId); oneway void finishSession(in IBinder activityToken, int userId); diff --git a/core/java/android/view/textclassifier/TextClassificationManager.java b/core/java/android/view/textclassifier/TextClassificationManager.java index f032414b70ba..35c9a294d273 100644 --- a/core/java/android/view/textclassifier/TextClassificationManager.java +++ b/core/java/android/view/textclassifier/TextClassificationManager.java @@ -63,7 +63,7 @@ public final class TextClassificationManager { if (mDefault == null) { try { mSmartSelectionFd = ParcelFileDescriptor.open( - new File("/etc/assistant/smart-selection.model"), + new File("/etc/textclassifier/textclassifier.smartselection.en.model"), ParcelFileDescriptor.MODE_READ_ONLY); mDefault = new TextClassifierImpl(mContext, mSmartSelectionFd); } catch (FileNotFoundException e) { @@ -109,7 +109,7 @@ public final class TextClassificationManager { synchronized (mLangIdLock) { if (mLangId == null) { mLangIdFd = ParcelFileDescriptor.open( - new File("/etc/assistant/lang-id.model"), + new File("/etc/textclassifier/textclassifier.langid.model"), ParcelFileDescriptor.MODE_READ_ONLY); mLangId = new LangId(mLangIdFd.getFd()); } diff --git a/core/java/android/view/textclassifier/TextClassifierImpl.java b/core/java/android/view/textclassifier/TextClassifierImpl.java index c95a1fb966f5..06ac8699f864 100644 --- a/core/java/android/view/textclassifier/TextClassifierImpl.java +++ b/core/java/android/view/textclassifier/TextClassifierImpl.java @@ -121,8 +121,8 @@ final class TextClassifierImpl implements TextClassifier { .classifyText(text.toString(), startIndex, endIndex); if (results.length > 0) { // TODO: Added this log for debug only. Remove before release. - Log.d(LOG_TAG, - String.format("Classification type: %s", results[0].mCollection)); + Log.d(LOG_TAG, String.format( + "Classification type: %s", getHighestScoringType(results))); return createClassificationResult(results, classified); } } @@ -188,7 +188,7 @@ final class TextClassifierImpl implements TextClassifier { builder.setEntityType(classifications[i].mCollection, classifications[i].mScore); } - final String type = classifications[0].mCollection; + final String type = getHighestScoringType(classifications); final Intent intent = IntentFactory.create(mContext, type, text.toString()); final PackageManager pm; final ResolveInfo resolveInfo; @@ -226,6 +226,23 @@ final class TextClassifierImpl implements TextClassifier { return builder.build(); } + private static String getHighestScoringType(SmartSelection.ClassificationResult[] types) { + if (types.length < 1) { + return ""; + } + + String type = types[0].mCollection; + float highestScore = types[0].mScore; + final int size = types.length; + for (int i = 1; i < size; i++) { + if (types[i].mScore > highestScore) { + type = types[i].mCollection; + highestScore = types[i].mScore; + } + } + return type; + } + /** * @throws IllegalArgumentException if text is null; startIndex is negative; * endIndex is greater than text.length() or is not greater than startIndex @@ -265,7 +282,7 @@ final class TextClassifierImpl implements TextClassifier { final SmartSelection.ClassificationResult[] results = smartSelection.classifyText(text, selectionStart, selectionEnd); if (results.length > 0) { - final String type = results[0].mCollection; + final String type = getHighestScoringType(results); if (matches(type, linkMask)) { final Intent intent = IntentFactory.create( context, type, text.substring(selectionStart, selectionEnd)); diff --git a/core/java/android/widget/AbsSpinner.java b/core/java/android/widget/AbsSpinner.java index 053574f8a12a..fae574289b88 100644 --- a/core/java/android/widget/AbsSpinner.java +++ b/core/java/android/widget/AbsSpinner.java @@ -23,6 +23,7 @@ import android.graphics.Rect; import android.os.Parcel; import android.os.Parcelable; import android.util.AttributeSet; +import android.util.Log; import android.util.SparseArray; import android.view.View; import android.view.ViewGroup; @@ -38,6 +39,8 @@ import com.android.internal.R; * @attr ref android.R.styleable#AbsSpinner_entries */ public abstract class AbsSpinner extends AdapterView<SpinnerAdapter> { + private static final String LOG_TAG = AbsSpinner.class.getSimpleName(); + SpinnerAdapter mAdapter; int mHeightMeasureSpec; @@ -511,11 +514,15 @@ public abstract class AbsSpinner extends AdapterView<SpinnerAdapter> { } @Override - public void autofill(AutofillValue value) { - if (!isEnabled()) return; + public boolean autofill(AutofillValue value) { + if (!isEnabled()) return false; - final int position = value.getListValue(); - setSelection(position); + if (value.isList()) { + setSelection(value.getListValue()); + } else { + Log.w(LOG_TAG, value + " could not be autofilled into " + this); + } + return true; } @Override diff --git a/core/java/android/widget/CompoundButton.java b/core/java/android/widget/CompoundButton.java index d2464052e68f..9dc61ab56b34 100644 --- a/core/java/android/widget/CompoundButton.java +++ b/core/java/android/widget/CompoundButton.java @@ -28,6 +28,7 @@ import android.graphics.drawable.Drawable; import android.os.Parcel; import android.os.Parcelable; import android.util.AttributeSet; +import android.util.Log; import android.view.Gravity; import android.view.SoundEffectConstants; import android.view.ViewDebug; @@ -55,6 +56,7 @@ import com.android.internal.R; * </p> */ public abstract class CompoundButton extends Button implements Checkable { + private static final String LOG_TAG = CompoundButton.class.getSimpleName(); private boolean mChecked; private boolean mBroadcasting; @@ -578,14 +580,20 @@ public abstract class CompoundButton extends Button implements Checkable { public void onProvideAutofillStructure(ViewStructure structure, int flags) { super.onProvideAutofillStructure(structure, flags); - structure.setSanitized(mCheckedFromResource); + structure.setDataIsSensitive(!mCheckedFromResource); } @Override - public void autofill(AutofillValue value) { - if (!isEnabled()) return; + public boolean autofill(AutofillValue value) { + if (!isEnabled()) return false; - setChecked(value.getToggleValue()); + if (value.isToggle()) { + setChecked(value.getToggleValue()); + } else { + Log.w(LOG_TAG, value + " could not be autofilled into " + this); + } + + return true; } @Override diff --git a/core/java/android/widget/DatePicker.java b/core/java/android/widget/DatePicker.java index 31a88d4faaec..7d04f355f251 100644 --- a/core/java/android/widget/DatePicker.java +++ b/core/java/android/widget/DatePicker.java @@ -29,6 +29,7 @@ import android.os.Parcel; import android.os.Parcelable; import android.text.format.DateUtils; import android.util.AttributeSet; +import android.util.Log; import android.util.SparseArray; import android.view.View; import android.view.ViewStructure; @@ -83,6 +84,8 @@ import java.util.Locale; */ @Widget public class DatePicker extends FrameLayout { + private static final String LOG_TAG = DatePicker.class.getSimpleName(); + /** * Presentation mode for the Holo-style date picker that uses a set of * {@link android.widget.NumberPicker}s. @@ -772,10 +775,16 @@ public class DatePicker extends FrameLayout { } @Override - public void autofill(AutofillValue value) { - if (!isEnabled()) return; + public boolean autofill(AutofillValue value) { + if (!isEnabled()) return false; + + if (value.isDate()) { + mDelegate.updateDate(value.getDateValue()); + } else { + Log.w(LOG_TAG, value + " could not be autofilled into " + this); + } - mDelegate.updateDate(value.getDateValue()); + return true; } @Override diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java index ade03e1b7fb4..faa23106e920 100644 --- a/core/java/android/widget/Editor.java +++ b/core/java/android/widget/Editor.java @@ -157,6 +157,7 @@ public class Editor { private static final int MENU_ITEM_ORDER_SELECT_ALL = 9; private static final int MENU_ITEM_ORDER_REPLACE = 10; private static final int MENU_ITEM_ORDER_PROCESS_TEXT_INTENT_ACTIONS_START = 11; + private static final int MENU_ITEM_ORDER_AUTOFILL = 12; // Each Editor manages its own undo stack. private final UndoManager mUndoManager = new UndoManager(); @@ -2644,6 +2645,10 @@ public class Editor { .setAlphabeticShortcut('a') .setEnabled(mTextView.canSelectAllText()) .setOnMenuItemClickListener(mOnContextMenuItemClickListener); + menu.add(Menu.NONE, TextView.ID_AUTOFILL, MENU_ITEM_ORDER_AUTOFILL, + com.android.internal.R.string.autofill) + .setEnabled(mTextView.canRequestAutofill()) + .setOnMenuItemClickListener(mOnContextMenuItemClickListener); mPreserveSelection = true; } @@ -3828,6 +3833,12 @@ public class Editor { .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM); } + if (mTextView.canRequestAutofill()) { + menu.add(Menu.NONE, TextView.ID_AUTOFILL, MENU_ITEM_ORDER_AUTOFILL, + com.android.internal.R.string.autofill) + .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM); + } + updateSelectAllItem(menu); updateReplaceItem(menu); } diff --git a/core/java/android/widget/RadioGroup.java b/core/java/android/widget/RadioGroup.java index dc9976d436ae..a7574c7a45ae 100644 --- a/core/java/android/widget/RadioGroup.java +++ b/core/java/android/widget/RadioGroup.java @@ -55,6 +55,7 @@ import com.android.internal.R; * */ public class RadioGroup extends LinearLayout { + private static final String LOG_TAG = RadioGroup.class.getSimpleName(); // holds the checked id; the selection is empty by default private int mCheckedId = -1; @@ -421,20 +422,28 @@ public class RadioGroup extends LinearLayout { @Override public void onProvideAutofillStructure(ViewStructure structure, int flags) { super.onProvideAutofillStructure(structure, flags); - structure.setSanitized(mCheckedId == mInitialCheckedId); + structure.setDataIsSensitive(mCheckedId != mInitialCheckedId); } @Override - public void autofill(AutofillValue value) { - if (!isEnabled()) return; + public boolean autofill(AutofillValue value) { + if (!isEnabled()) return false; + + int index; + if (value.isList()) { + index = value.getListValue(); + } else { + Log.w(LOG_TAG, value + " could not be autofilled into " + this); + return false; + } - final int index = value.getListValue(); final View child = getChildAt(index); if (child == null) { Log.w(VIEW_LOG_TAG, "RadioGroup.autoFill(): no child with index " + index); - return; + return false; } check(child.getId()); + return true; } @Override diff --git a/core/java/android/widget/TextClock.java b/core/java/android/widget/TextClock.java index a6a9db44c420..59881b5e6b6b 100644 --- a/core/java/android/widget/TextClock.java +++ b/core/java/android/widget/TextClock.java @@ -132,7 +132,7 @@ public class TextClock extends TextView { private CharSequence mDescFormat; - private boolean mRegistered; + private boolean mAttached; private Calendar mTime; private String mTimeZone; @@ -252,7 +252,7 @@ public class TextClock extends TextView { } createTime(mTimeZone); - // Wait until registering for events to handle the ticker + // Wait until onAttachedToWindow() to handle the ticker chooseFormat(false); } @@ -503,9 +503,12 @@ public class TextClock extends TextView { boolean hadSeconds = mHasSeconds; mHasSeconds = DateFormat.hasSeconds(mFormat); - if (handleTicker && mRegistered && hadSeconds != mHasSeconds) { - if (hadSeconds) getHandler().removeCallbacks(mTicker); - else mTicker.run(); + if (handleTicker && mAttached && hadSeconds != mHasSeconds) { + if (hadSeconds) { + getHandler().removeCallbacks(mTicker); + } else if (getVisibility() == VISIBLE) { + mTicker.run(); + } } } @@ -517,27 +520,50 @@ public class TextClock extends TextView { } @Override - public void onVisibilityAggregated(boolean isVisible) { - if (!mRegistered && isVisible) { - mRegistered = true; + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + + if (!mAttached) { + mAttached = true; registerReceiver(); registerObserver(); createTime(mTimeZone); - if (mHasSeconds) { - mTicker.run(); - } else { - onTimeChanged(); + if (getVisibility() == VISIBLE) { + if (mHasSeconds) { + mTicker.run(); + } else { + onTimeChanged(); + } } - } else if (mRegistered && !isVisible) { + } + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + + if (mAttached) { unregisterReceiver(); unregisterObserver(); getHandler().removeCallbacks(mTicker); - mRegistered = false; + mAttached = false; + } + } + + @Override + public void onVisibilityAggregated(boolean isVisible) { + if (mAttached) { + if (isVisible && mHasSeconds) { + mTicker.run(); + } else { + getHandler().removeCallbacks(mTicker); + } + onTimeChanged(); } } @@ -560,7 +586,7 @@ public class TextClock extends TextView { } private void registerObserver() { - if (mRegistered) { + if (mAttached) { if (mFormatChangeObserver == null) { mFormatChangeObserver = new FormatChangeObserver(getHandler()); } @@ -587,9 +613,11 @@ public class TextClock extends TextView { } private void onTimeChanged() { - mTime.setTimeInMillis(System.currentTimeMillis()); - setText(DateFormat.format(mFormat, mTime)); - setContentDescription(DateFormat.format(mDescFormat, mTime)); + if (getVisibility() == VISIBLE) { + mTime.setTimeInMillis(System.currentTimeMillis()); + setText(DateFormat.format(mFormat, mTime)); + setContentDescription(DateFormat.format(mDescFormat, mTime)); + } } /** @hide */ diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index d591316f5f2d..26edc436b2f8 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -9900,7 +9900,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener final boolean isPassword = hasPasswordTransformationMethod() || isPasswordInputType(getInputType()); if (forAutofill) { - structure.setSanitized(mTextFromResource); + structure.setDataIsSensitive(!mTextFromResource); } if (!isPassword || forAutofill) { @@ -10012,13 +10012,33 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener // TODO(b/33197203): add unit/CTS tests for autofill methods - @Override - public void autofill(AutofillValue value) { - final CharSequence text = value.getTextValue(); + boolean canRequestAutofill() { + final AutofillManager afm = mContext.getSystemService(AutofillManager.class); + if (afm != null) { + return afm.isEnabled(); + } + return false; + } + + private void requestAutofill() { + final AutofillManager afm = mContext.getSystemService(AutofillManager.class); + if (afm != null) { + afm.requestAutofill(this); + } + } - if (text != null && isTextEditable()) { - setText(text, mBufferType, true, 0); + @Override + public boolean autofill(AutofillValue value) { + if (value.isText()) { + if (isTextEditable()) { + setText(value.getTextValue(), mBufferType, true, 0); + return true; + } + } else { + Log.w(LOG_TAG, value + " could not be autofilled into " + this); } + + return false; } @Override @@ -10479,6 +10499,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener static final int ID_PASTE_AS_PLAIN_TEXT = android.R.id.pasteAsPlainText; static final int ID_REPLACE = android.R.id.replaceText; static final int ID_ASSIST = android.R.id.textAssist; + static final int ID_AUTOFILL = android.R.id.autofill; /** * Called when a context menu option for the text view is selected. Currently @@ -10543,6 +10564,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener case ID_SHARE: shareSelectedText(); return true; + + case ID_AUTOFILL: + requestAutofill(); + stopTextActionMode(); + return true; } return false; } diff --git a/core/java/android/widget/TimePicker.java b/core/java/android/widget/TimePicker.java index 9825f1e99840..1e97e3ba5134 100644 --- a/core/java/android/widget/TimePicker.java +++ b/core/java/android/widget/TimePicker.java @@ -27,6 +27,7 @@ import android.icu.util.Calendar; import android.os.Parcel; import android.os.Parcelable; import android.util.AttributeSet; +import android.util.Log; import android.util.MathUtils; import android.view.View; import android.view.ViewStructure; @@ -53,6 +54,8 @@ import java.util.Locale; */ @Widget public class TimePicker extends FrameLayout { + private static final String LOG_TAG = TimePicker.class.getSimpleName(); + /** * Presentation mode for the Holo-style time picker that uses a set of * {@link android.widget.NumberPicker}s. @@ -527,10 +530,16 @@ public class TimePicker extends FrameLayout { } @Override - public void autofill(AutofillValue value) { - if (!isEnabled()) return; + public boolean autofill(AutofillValue value) { + if (!isEnabled()) return false; + + if (value.isDate()) { + mDelegate.setDate(value.getDateValue()); + } else { + Log.w(LOG_TAG, value + " could not be autofilled into " + this); + } - mDelegate.setDate(value.getDateValue()); + return true; } @Override diff --git a/core/java/com/android/internal/content/FileSystemProvider.java b/core/java/com/android/internal/content/FileSystemProvider.java index 3ac5a72e087b..83cc9f09e8ed 100644 --- a/core/java/com/android/internal/content/FileSystemProvider.java +++ b/core/java/com/android/internal/content/FileSystemProvider.java @@ -18,6 +18,7 @@ package com.android.internal.content; import android.annotation.CallSuper; import android.content.ContentResolver; +import android.content.ContentValues; import android.content.Intent; import android.content.res.AssetFileDescriptor; import android.database.Cursor; @@ -157,10 +158,9 @@ public abstract class FileSystemProvider extends DocumentsProvider { if (!before.renameTo(after)) { throw new IllegalStateException("Failed to rename to " + after); } - removeFromMediaStore(visibleFileBefore); final String afterDocId = getDocIdForFile(after); - scanFile(getFileForDocId(afterDocId, true)); + moveInMediaStore(visibleFileBefore, getFileForDocId(afterDocId, true)); if (!TextUtils.equals(docId, afterDocId)) { return afterDocId; @@ -170,22 +170,6 @@ public abstract class FileSystemProvider extends DocumentsProvider { } @Override - public void deleteDocument(String docId) throws FileNotFoundException { - final File file = getFileForDocId(docId); - final File visibleFile = getFileForDocId(docId, true); - - final boolean isDirectory = file.isDirectory(); - if (isDirectory) { - FileUtils.deleteContents(file); - } - if (!file.delete()) { - throw new IllegalStateException("Failed to delete " + file); - } - - removeFromMediaStore(visibleFile); - } - - @Override public String moveDocument(String sourceDocumentId, String sourceParentDocumentId, String targetParentDocumentId) throws FileNotFoundException { @@ -200,22 +184,56 @@ public abstract class FileSystemProvider extends DocumentsProvider { throw new IllegalStateException("Failed to move to " + after); } - // Notify media store to update its content - removeFromMediaStore(visibleFileBefore); final String docId = getDocIdForFile(after); - scanFile(getFileForDocId(docId, true)); + moveInMediaStore(visibleFileBefore, getFileForDocId(docId, true)); return docId; } - private void removeFromMediaStore(File visibleFile) throws FileNotFoundException { + private void moveInMediaStore(File oldVisibleFile, File newVisibleFile) { + if (newVisibleFile != null) { + final ContentResolver resolver = getContext().getContentResolver(); + final Uri externalUri = MediaStore.Files.getContentUri("external"); + + ContentValues values = new ContentValues(); + values.put(MediaStore.Files.FileColumns.DATA, newVisibleFile.getAbsolutePath()); + + // Logic borrowed from MtpDatabase. + // note - we are relying on a special case in MediaProvider.update() to update + // the paths for all children in the case where this is a directory. + final String path = oldVisibleFile.getAbsolutePath(); + resolver.update(externalUri, + values, + "_data LIKE ? AND lower(_data)=lower(?)", + new String[] { path, path }); + } + } + + @Override + public void deleteDocument(String docId) throws FileNotFoundException { + final File file = getFileForDocId(docId); + final File visibleFile = getFileForDocId(docId, true); + + final boolean isDirectory = file.isDirectory(); + if (isDirectory) { + FileUtils.deleteContents(file); + } + if (!file.delete()) { + throw new IllegalStateException("Failed to delete " + file); + } + + removeFromMediaStore(visibleFile, isDirectory); + } + + private void removeFromMediaStore(File visibleFile, boolean isFolder) + throws FileNotFoundException { if (visibleFile != null) { final ContentResolver resolver = getContext().getContentResolver(); final Uri externalUri = MediaStore.Files.getContentUri("external"); // Remove media store entries for any files inside this directory, using // path prefix match. Logic borrowed from MtpDatabase. - if (visibleFile.isDirectory()) { + if (isFolder) { final String path = visibleFile.getAbsolutePath() + "/"; resolver.delete(externalUri, "_data LIKE ?1 AND lower(substr(_data,1,?2))=lower(?3)", diff --git a/core/java/com/android/internal/notification/SystemNotificationChannels.java b/core/java/com/android/internal/notification/SystemNotificationChannels.java index ec3aac21cb19..c840f26a0d53 100644 --- a/core/java/com/android/internal/notification/SystemNotificationChannels.java +++ b/core/java/com/android/internal/notification/SystemNotificationChannels.java @@ -48,12 +48,12 @@ public class SystemNotificationChannels { List<NotificationChannel> channelsList = new ArrayList<NotificationChannel>(); channelsList.add(new NotificationChannel( VIRTUAL_KEYBOARD, - R.string.notification_channel_virtual_keyboard, + context.getString(R.string.notification_channel_virtual_keyboard), NotificationManager.IMPORTANCE_LOW)); final NotificationChannel physicalKeyboardChannel = new NotificationChannel( PHYSICAL_KEYBOARD, - R.string.notification_channel_physical_keyboard, + context.getString(R.string.notification_channel_physical_keyboard), NotificationManager.IMPORTANCE_DEFAULT); physicalKeyboardChannel.setSound(Settings.System.DEFAULT_NOTIFICATION_URI, Notification.AUDIO_ATTRIBUTES_DEFAULT); @@ -61,32 +61,32 @@ public class SystemNotificationChannels { channelsList.add(new NotificationChannel( SECURITY, - R.string.notification_channel_security, + context.getString(R.string.notification_channel_security), NotificationManager.IMPORTANCE_LOW)); channelsList.add(new NotificationChannel( CAR_MODE, - R.string.notification_channel_car_mode, + context.getString(R.string.notification_channel_car_mode), NotificationManager.IMPORTANCE_LOW)); channelsList.add(new NotificationChannel( DEVELOPER, - R.string.notification_channel_developer, + context.getString(R.string.notification_channel_developer), NotificationManager.IMPORTANCE_LOW)); channelsList.add(new NotificationChannel( UPDATES, - R.string.notification_channel_updates, + context.getString(R.string.notification_channel_updates), NotificationManager.IMPORTANCE_LOW)); channelsList.add(new NotificationChannel( NETWORK_STATUS, - R.string.notification_channel_network_status, + context.getString(R.string.notification_channel_network_status), NotificationManager.IMPORTANCE_LOW)); final NotificationChannel networkAlertsChannel = new NotificationChannel( NETWORK_ALERTS, - R.string.notification_channel_network_alerts, + context.getString(R.string.notification_channel_network_alerts), NotificationManager.IMPORTANCE_HIGH); networkAlertsChannel.setSound(Settings.System.DEFAULT_NOTIFICATION_URI, Notification.AUDIO_ATTRIBUTES_DEFAULT); @@ -94,17 +94,17 @@ public class SystemNotificationChannels { channelsList.add(new NotificationChannel( VPN, - R.string.notification_channel_vpn, + context.getString(R.string.notification_channel_vpn), NotificationManager.IMPORTANCE_LOW)); channelsList.add(new NotificationChannel( DEVICE_ADMIN, - R.string.notification_channel_device_admin, + context.getString(R.string.notification_channel_device_admin), NotificationManager.IMPORTANCE_LOW)); final NotificationChannel alertsChannel = new NotificationChannel( ALERTS, - R.string.notification_channel_alerts, + context.getString(R.string.notification_channel_alerts), NotificationManager.IMPORTANCE_DEFAULT); alertsChannel.setSound(Settings.System.DEFAULT_NOTIFICATION_URI, Notification.AUDIO_ATTRIBUTES_DEFAULT); @@ -112,12 +112,12 @@ public class SystemNotificationChannels { channelsList.add(new NotificationChannel( RETAIL_MODE, - R.string.notification_channel_retail_mode, + context.getString(R.string.notification_channel_retail_mode), NotificationManager.IMPORTANCE_LOW)); channelsList.add(new NotificationChannel( USB, - R.string.notification_channel_usb, + context.getString(R.string.notification_channel_usb), NotificationManager.IMPORTANCE_MIN)); nm.createNotificationChannels(channelsList); @@ -128,7 +128,7 @@ public class SystemNotificationChannels { final NotificationManager nm = context.getSystemService(NotificationManager.class); nm.createNotificationChannelsForPackage(pkg, Arrays.asList(new NotificationChannel( ACCOUNT, - R.string.notification_channel_account, + context.getString(R.string.notification_channel_account), NotificationManager.IMPORTANCE_LOW))); } diff --git a/core/java/com/android/internal/os/WrapperInit.java b/core/java/com/android/internal/os/WrapperInit.java index 96468abd9047..0a9faa15dcc2 100644 --- a/core/java/com/android/internal/os/WrapperInit.java +++ b/core/java/com/android/internal/os/WrapperInit.java @@ -142,6 +142,21 @@ public class WrapperInit { Slog.d(RuntimeInit.TAG, "RuntimeInit: Starting application from wrapper"); } - RuntimeInit.applicationInit(targetSdkVersion, argv, null); + // Check whether the first argument is a "-cp" in argv, and assume the next argument is the + // classpath. If found, create a PathClassLoader and use it for applicationInit. + ClassLoader classLoader = null; + if (argv != null && argv.length > 2 && argv[0].equals("-cp")) { + classLoader = ZygoteInit.createPathClassLoader(argv[1], targetSdkVersion); + + // Install this classloader as the context classloader, too. + Thread.currentThread().setContextClassLoader(classLoader); + + // Remove the classpath from the arguments. + String removedArgs[] = new String[argv.length - 2]; + System.arraycopy(argv, 2, removedArgs, 0, argv.length - 2); + argv = removedArgs; + } + + RuntimeInit.applicationInit(targetSdkVersion, argv, classLoader); } } diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java index a7e900a189c7..b2a2fec879e6 100644 --- a/core/java/com/android/internal/os/ZygoteInit.java +++ b/core/java/com/android/internal/os/ZygoteInit.java @@ -132,9 +132,6 @@ public class ZygoteInit { Trace.traceBegin(Trace.TRACE_TAG_DALVIK, "PreloadOpenGL"); preloadOpenGL(); Trace.traceEnd(Trace.TRACE_TAG_DALVIK); - Trace.traceBegin(Trace.TRACE_TAG_DALVIK, "PreloadOpenGL"); - preloadOpenGL(); - Trace.traceEnd(Trace.TRACE_TAG_DALVIK); preloadSharedLibraries(); preloadTextResources(); // Ask the WebViewFactory to do any initialization that must run in the zygote process, @@ -471,7 +468,8 @@ public class ZygoteInit { String[] amendedArgs = new String[args.length + 2]; amendedArgs[0] = "-cp"; amendedArgs[1] = systemServerClasspath; - System.arraycopy(parsedArgs.remainingArgs, 0, amendedArgs, 2, parsedArgs.remainingArgs.length); + System.arraycopy(args, 0, amendedArgs, 2, args.length); + args = amendedArgs; } WrapperInit.execApplication(parsedArgs.invokeWith, @@ -480,8 +478,7 @@ public class ZygoteInit { } else { ClassLoader cl = null; if (systemServerClasspath != null) { - cl = createSystemServerClassLoader(systemServerClasspath, - parsedArgs.targetSdkVersion); + cl = createPathClassLoader(systemServerClasspath, parsedArgs.targetSdkVersion); Thread.currentThread().setContextClassLoader(cl); } @@ -496,15 +493,14 @@ public class ZygoteInit { } /** - * Creates a PathClassLoader for the system server. It also creates - * a shared namespace associated with the classloader to let it access - * platform-private native libraries. + * Creates a PathClassLoader for the given class path that is associated with a shared + * namespace, i.e., this classloader can access platform-private native libraries. The + * classloader will use java.library.path as the native library path. */ - private static PathClassLoader createSystemServerClassLoader(String systemServerClasspath, - int targetSdkVersion) { + static PathClassLoader createPathClassLoader(String classPath, int targetSdkVersion) { String libraryPath = System.getProperty("java.library.path"); - return PathClassLoaderFactory.createClassLoader(systemServerClasspath, + return PathClassLoaderFactory.createClassLoader(classPath, libraryPath, libraryPath, ClassLoader.getSystemClassLoader(), @@ -585,7 +581,6 @@ public class ZygoteInit { OsConstants.CAP_SYS_MODULE, OsConstants.CAP_SYS_NICE, OsConstants.CAP_SYS_PTRACE, - OsConstants.CAP_SYS_RESOURCE, OsConstants.CAP_SYS_TIME, OsConstants.CAP_SYS_TTY_CONFIG, OsConstants.CAP_WAKE_ALARM diff --git a/core/java/com/android/internal/policy/PipSnapAlgorithm.java b/core/java/com/android/internal/policy/PipSnapAlgorithm.java index ae31873492e9..95d714f1c3c7 100644 --- a/core/java/com/android/internal/policy/PipSnapAlgorithm.java +++ b/core/java/com/android/internal/policy/PipSnapAlgorithm.java @@ -321,7 +321,7 @@ public class PipSnapAlgorithm { stackBounds.top)); boundsOut.set(stackBounds); if (mIsMinimized) { - boundsOut.offsetTo(boundedLeft, boundsOut.top); + boundsOut.offsetTo(boundedLeft, boundedTop); return; } diff --git a/core/java/com/android/internal/view/BaseIWindow.java b/core/java/com/android/internal/view/BaseIWindow.java index ce51dc4ead9e..361fd3da97c7 100644 --- a/core/java/com/android/internal/view/BaseIWindow.java +++ b/core/java/com/android/internal/view/BaseIWindow.java @@ -16,12 +16,12 @@ package com.android.internal.view; -import android.content.res.Configuration; import android.graphics.Rect; import android.hardware.input.InputManager; import android.os.Bundle; import android.os.ParcelFileDescriptor; import android.os.RemoteException; +import android.util.MergedConfiguration; import android.view.DragEvent; import android.view.IWindow; import android.view.IWindowSession; @@ -39,8 +39,9 @@ public class BaseIWindow extends IWindow.Stub { @Override public void resized(Rect frame, Rect overscanInsets, Rect contentInsets, Rect visibleInsets, - Rect stableInsets, Rect outsets, boolean reportDraw, Configuration newConfig, - Rect backDropFrame, boolean forceLayout, boolean alwaysConsumeNavBar, int displayId) { + Rect stableInsets, Rect outsets, boolean reportDraw, + MergedConfiguration mergedConfiguration, Rect backDropFrame, boolean forceLayout, + boolean alwaysConsumeNavBar, int displayId) { if (reportDraw) { try { mSession.finishDrawing(this); diff --git a/core/java/com/android/internal/widget/FloatingToolbar.java b/core/java/com/android/internal/widget/FloatingToolbar.java index 79b0cd1aced8..818cc2c2421d 100644 --- a/core/java/com/android/internal/widget/FloatingToolbar.java +++ b/core/java/com/android/internal/widget/FloatingToolbar.java @@ -1163,21 +1163,21 @@ public final class FloatingToolbar { isLastItem && menuItemButtonWidth <= availableWidth - extraPadding; if (canFitWithOverflow || canFitNoOverflow) { if (isNewGroup) { - final View border = createBorder(mContext); - final int borderWidth = border.getLayoutParams().width; + final View divider = createDivider(mContext); + final int dividerWidth = divider.getLayoutParams().width; // Add extra padding to the end of the previous button. // Half of the extra padding (less borderWidth) goes to the previous button. View previousButton = mMainPanel.getChildAt(mMainPanel.getChildCount() - 1); final int prevPaddingEnd = previousButton.getPaddingEnd() - + extraPadding / 2 - borderWidth; + + extraPadding / 2 - dividerWidth; previousButton.setPaddingRelative( previousButton.getPaddingStart(), previousButton.getPaddingTop(), prevPaddingEnd, previousButton.getPaddingBottom()); final ViewGroup.LayoutParams prevParams = previousButton.getLayoutParams(); - prevParams.width += extraPadding / 2 - borderWidth; + prevParams.width += extraPadding / 2 - dividerWidth; previousButton.setLayoutParams(prevParams); // Add extra padding to the start of this button. @@ -1190,8 +1190,8 @@ public final class FloatingToolbar { menuItemButton.getPaddingEnd(), menuItemButton.getPaddingBottom()); - // Include a border. - mMainPanel.addView(border); + // Include a divider. + mMainPanel.addView(divider); } setButtonTagAndClickListener(menuItemButton, menuItem); @@ -1670,21 +1670,28 @@ public final class FloatingToolbar { return popupWindow; } - private static View createBorder(Context context) { + private static View createDivider(Context context) { // TODO: Inflate this instead. - View border = new View(context); + View divider = new View(context); + int _1dp = (int) TypedValue.applyDimension( TypedValue.COMPLEX_UNIT_DIP, 1, context.getResources().getDisplayMetrics()); LinearLayout.LayoutParams params = new LinearLayout.LayoutParams( _1dp, ViewGroup.LayoutParams.MATCH_PARENT); params.setMarginsRelative(0, _1dp * 10, 0, _1dp * 10); - border.setLayoutParams(params); - border.setBackgroundColor(Color.parseColor("#9E9E9E")); - border.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO); - border.setEnabled(false); - border.setFocusable(false); - border.setContentDescription(null); - return border; + divider.setLayoutParams(params); + + TypedArray a = context.obtainStyledAttributes( + new TypedValue().data, new int[] { R.attr.floatingToolbarDividerColor }); + divider.setBackgroundColor(a.getColor(0, 0)); + a.recycle(); + + divider.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO); + divider.setEnabled(false); + divider.setFocusable(false); + divider.setContentDescription(null); + + return divider; } /** diff --git a/core/java/com/android/internal/widget/ImageFloatingTextView.java b/core/java/com/android/internal/widget/ImageFloatingTextView.java index 80207ee854b8..e86932cf3b29 100644 --- a/core/java/com/android/internal/widget/ImageFloatingTextView.java +++ b/core/java/com/android/internal/widget/ImageFloatingTextView.java @@ -115,6 +115,7 @@ public class ImageFloatingTextView extends TextView { // Lets calculate how many lines the given measurement allows us. int availableHeight = height - mPaddingTop - mPaddingBottom; int maxLines = availableHeight / getLineHeight(); + maxLines = Math.max(1, maxLines); if (getMaxLines() > 0) { maxLines = Math.min(getMaxLines(), maxLines); } diff --git a/core/java/com/android/internal/widget/MessagingLinearLayout.java b/core/java/com/android/internal/widget/MessagingLinearLayout.java index b259ad1643f8..1104318763c0 100644 --- a/core/java/com/android/internal/widget/MessagingLinearLayout.java +++ b/core/java/com/android/internal/widget/MessagingLinearLayout.java @@ -16,8 +16,6 @@ package com.android.internal.widget; -import com.android.internal.R; - import android.annotation.Nullable; import android.content.Context; import android.content.res.TypedArray; @@ -28,6 +26,8 @@ import android.view.View; import android.view.ViewGroup; import android.widget.RemoteViews; +import com.android.internal.R; + /** * A custom-built layout for the Notification.MessagingStyle. * @@ -119,23 +119,30 @@ public class MessagingLinearLayout extends ViewGroup { } final View child = getChildAt(i); LayoutParams lp = (LayoutParams) getChildAt(i).getLayoutParams(); - + ImageFloatingTextView textChild = null; if (child instanceof ImageFloatingTextView) { // Pretend we need the image padding for all views, we don't know which // one will end up needing to do this (might end up not using all the space, // but calculating this exactly would be more expensive). - ((ImageFloatingTextView) child).setNumIndentLines( - mIndentLines == 2 ? 3 : mIndentLines); + textChild = (ImageFloatingTextView) child; + textChild.setNumIndentLines(mIndentLines == 2 ? 3 : mIndentLines); } - measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0); + int spacing = first ? 0 : mSpacing; + measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, totalHeight + - mPaddingTop - mPaddingBottom + spacing); final int childHeight = child.getMeasuredHeight(); int newHeight = Math.max(totalHeight, totalHeight + childHeight + lp.topMargin + - lp.bottomMargin + (first ? 0 : mSpacing)); + lp.bottomMargin + spacing); first = false; + boolean measuredTooSmall = false; + if (textChild != null) { + measuredTooSmall = childHeight < textChild.getLayout().getHeight() + + textChild.getPaddingTop() + textChild.getPaddingBottom(); + } - if (newHeight <= targetHeight) { + if (newHeight <= targetHeight && !measuredTooSmall) { totalHeight = newHeight; lp.hide = false; } else { @@ -168,7 +175,15 @@ public class MessagingLinearLayout extends ViewGroup { } boolean changed = textChild.setNumIndentLines(Math.max(0, imageLines)); if (changed || !recalculateVisibility) { - measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0); + final int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec, + mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin, + lp.width); + // we want to measure it at most as high as it is currently, otherwise we'll + // drop later lines + final int childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec, + targetHeight - child.getMeasuredHeight(), lp.height); + + child.measure(childWidthMeasureSpec, childHeightMeasureSpec);; } imageLines -= textChild.getLineCount(); } diff --git a/core/jni/android/graphics/Bitmap.cpp b/core/jni/android/graphics/Bitmap.cpp index f85219453212..a8d683028c13 100755 --- a/core/jni/android/graphics/Bitmap.cpp +++ b/core/jni/android/graphics/Bitmap.cpp @@ -453,7 +453,8 @@ bool GraphicsJNI::SetPixels(JNIEnv* env, jintArray srcColors, int srcOffset, int dst = dstBitmap.getAddr(x, y); SkColorSpace* colorSpace = dstBitmap.colorSpace(); - if (GraphicsJNI::isColorSpaceSRGB(colorSpace)) { + if (dstBitmap.colorType() == kRGBA_F16_SkColorType || + GraphicsJNI::isColorSpaceSRGB(colorSpace)) { // now copy/convert each scanline for (int y = 0; y < height; y++) { proc(dst, src, width, x, y); @@ -1267,7 +1268,8 @@ static jint Bitmap_getPixel(JNIEnv* env, jobject, jlong bitmapHandle, proc(dst, src, 1, bitmap.getColorTable()); SkColorSpace* colorSpace = bitmap.colorSpace(); - if (!GraphicsJNI::isColorSpaceSRGB(colorSpace)) { + if (bitmap.colorType() != kRGBA_F16_SkColorType && + !GraphicsJNI::isColorSpaceSRGB(colorSpace)) { auto sRGB = SkColorSpace::MakeSRGB(); auto xform = SkColorSpaceXform::New(colorSpace, sRGB.get()); xform->apply(SkColorSpaceXform::kBGRA_8888_ColorFormat, &dst[0], @@ -1299,7 +1301,8 @@ static void Bitmap_getPixels(JNIEnv* env, jobject, jlong bitmapHandle, SkColor* d = (SkColor*)dst + offset; SkColorSpace* colorSpace = bitmap.colorSpace(); - if (GraphicsJNI::isColorSpaceSRGB(colorSpace)) { + if (bitmap.colorType() == kRGBA_F16_SkColorType || + GraphicsJNI::isColorSpaceSRGB(colorSpace)) { while (--height >= 0) { proc(d, src, width, ctable); d += stride; @@ -1342,7 +1345,8 @@ static void Bitmap_setPixel(JNIEnv* env, jobject, jlong bitmapHandle, } SkColorSpace* colorSpace = bitmap.colorSpace(); - if (!GraphicsJNI::isColorSpaceSRGB(colorSpace)) { + if (bitmap.colorType() != kRGBA_F16_SkColorType && + !GraphicsJNI::isColorSpaceSRGB(colorSpace)) { auto sRGB = SkColorSpace::MakeSRGB(); auto xform = SkColorSpaceXform::New(sRGB.get(), colorSpace); xform->apply(SkColorSpaceXform::kBGRA_8888_ColorFormat, &color, diff --git a/core/jni/android/graphics/BitmapFactory.cpp b/core/jni/android/graphics/BitmapFactory.cpp index c1bb69da3179..e64a57447e6b 100644 --- a/core/jni/android/graphics/BitmapFactory.cpp +++ b/core/jni/android/graphics/BitmapFactory.cpp @@ -417,11 +417,7 @@ static jobject doDecode(JNIEnv* env, SkStreamRewindable* stream, jobject padding // For wide gamut images, we will leave the color space on the SkBitmap. Otherwise, // use the default. SkImageInfo bitmapInfo = decodeInfo; - sk_sp<SkColorSpace> srgb = - SkColorSpace::MakeRGB(SkColorSpace::kSRGB_RenderTargetGamma, - SkColorSpace::kSRGB_Gamut, - SkColorSpace::kNonLinearBlending_ColorSpaceFlag); - if (decodeInfo.colorSpace() == srgb.get()) { + if (decodeInfo.colorSpace() && decodeInfo.colorSpace()->isSRGB()) { bitmapInfo = bitmapInfo.makeColorSpace(GraphicsJNI::colorSpaceForType(decodeColorType)); } diff --git a/core/jni/android/graphics/ColorFilter.cpp b/core/jni/android/graphics/ColorFilter.cpp index 79439e2350ff..5553a3ed22c5 100644 --- a/core/jni/android/graphics/ColorFilter.cpp +++ b/core/jni/android/graphics/ColorFilter.cpp @@ -30,7 +30,7 @@ using namespace uirenderer; class SkColorFilterGlue { public: - static void finalizer(JNIEnv* env, jobject clazz, jlong skFilterHandle) { + static void SafeUnref(JNIEnv* env, jobject clazz, jlong skFilterHandle) { SkColorFilter* filter = reinterpret_cast<SkColorFilter *>(skFilterHandle); SkSafeUnref(filter); } @@ -57,19 +57,19 @@ public: }; static const JNINativeMethod colorfilter_methods[] = { - {"destroyFilter", "(J)V", (void*) SkColorFilterGlue::finalizer} + {"nSafeUnref", "(J)V", (void*) SkColorFilterGlue::SafeUnref} }; static const JNINativeMethod porterduff_methods[] = { - { "native_CreatePorterDuffFilter", "(II)J", (void*) SkColorFilterGlue::CreatePorterDuffFilter }, + { "native_CreatePorterDuffFilter", "(II)J", (void*) SkColorFilterGlue::CreatePorterDuffFilter }, }; static const JNINativeMethod lighting_methods[] = { - { "native_CreateLightingFilter", "(II)J", (void*) SkColorFilterGlue::CreateLightingFilter }, + { "native_CreateLightingFilter", "(II)J", (void*) SkColorFilterGlue::CreateLightingFilter }, }; static const JNINativeMethod colormatrix_methods[] = { - { "nativeColorMatrixFilter", "([F)J", (void*) SkColorFilterGlue::CreateColorMatrixFilter }, + { "nativeColorMatrixFilter", "([F)J", (void*) SkColorFilterGlue::CreateColorMatrixFilter }, }; int register_android_graphics_ColorFilter(JNIEnv* env) { diff --git a/core/jni/android/graphics/FontFamily.cpp b/core/jni/android/graphics/FontFamily.cpp index 49024b6a0b5f..fb7c5c46843a 100644 --- a/core/jni/android/graphics/FontFamily.cpp +++ b/core/jni/android/graphics/FontFamily.cpp @@ -44,6 +44,7 @@ struct NativeFamilyBuilder { uint32_t langId; int variant; std::vector<minikin::Font> fonts; + std::vector<minikin::FontVariation> axes; }; static jlong FontFamily_initBuilder(JNIEnv* env, jobject clazz, jstring lang, jint variant) { @@ -155,32 +156,16 @@ static jboolean FontFamily_addFont(JNIEnv* env, jobject clazz, jlong builderPtr, } static jboolean FontFamily_addFontWeightStyle(JNIEnv* env, jobject clazz, jlong builderPtr, - jobject font, jint ttcIndex, jobject listOfAxis, jint weight, jboolean isItalic) { + jobject font, jint ttcIndex, jint weight, jboolean isItalic) { NPE_CHECK_RETURN_ZERO(env, font); + NativeFamilyBuilder* builder = reinterpret_cast<NativeFamilyBuilder*>(builderPtr); + // Declare axis native type. - std::unique_ptr<SkFontMgr::FontParameters::Axis[]> skiaAxes; - int skiaAxesLength = 0; - if (listOfAxis) { - ListHelper list(env, listOfAxis); - jint listSize = list.size(); - - skiaAxes.reset(new SkFontMgr::FontParameters::Axis[listSize]); - skiaAxesLength = listSize; - for (jint i = 0; i < listSize; ++i) { - jobject axisObject = list.get(i); - if (!axisObject) { - skiaAxes[i].fTag = 0; - skiaAxes[i].fStyleValue = 0; - continue; - } - AxisHelper axis(env, axisObject); - - jint tag = axis.getTag(); - jfloat stylevalue = axis.getStyleValue(); - skiaAxes[i].fTag = tag; - skiaAxes[i].fStyleValue = SkFloatToScalar(stylevalue); - } + std::vector<SkFontMgr::FontParameters::Axis> skiaAxes; + skiaAxes.reserve(builder->axes.size()); + for (const minikin::FontVariation& minikinAxis : builder->axes) { + skiaAxes.push_back({minikinAxis.axisTag, minikinAxis.value}); } const void* fontPtr = env->GetDirectBufferAddress(font); @@ -200,7 +185,7 @@ static jboolean FontFamily_addFontWeightStyle(JNIEnv* env, jobject clazz, jlong SkFontMgr::FontParameters params; params.setCollectionIndex(ttcIndex); - params.setAxes(skiaAxes.get(), skiaAxesLength); + params.setAxes(skiaAxes.data(), skiaAxes.size()); sk_sp<SkFontMgr> fm(SkFontMgr::RefDefault()); sk_sp<SkTypeface> face(fm->createFromStream(fontData.release(), params)); @@ -211,7 +196,6 @@ static jboolean FontFamily_addFontWeightStyle(JNIEnv* env, jobject clazz, jlong std::shared_ptr<minikin::MinikinFont> minikinFont = std::make_shared<MinikinFontSkia>(std::move(face), fontPtr, fontSize, ttcIndex, std::vector<minikin::FontVariation>()); - NativeFamilyBuilder* builder = reinterpret_cast<NativeFamilyBuilder*>(builderPtr); builder->fonts.push_back(minikin::Font(std::move(minikinFont), minikin::FontStyle(weight / 100, isItalic))); return true; @@ -270,6 +254,11 @@ static jboolean FontFamily_addFontFromAssetManager(JNIEnv* env, jobject, jlong b return true; } +static void FontFamily_addAxisValue(jlong builderPtr, jint tag, jfloat value) { + NativeFamilyBuilder* builder = reinterpret_cast<NativeFamilyBuilder*>(builderPtr); + builder->axes.push_back({static_cast<minikin::AxisTag>(tag), value}); +} + /////////////////////////////////////////////////////////////////////////////// static const JNINativeMethod gFontFamilyMethods[] = { @@ -278,10 +267,11 @@ static const JNINativeMethod gFontFamilyMethods[] = { { "nAbort", "(J)V", (void*)FontFamily_abort }, { "nUnrefFamily", "(J)V", (void*)FontFamily_unref }, { "nAddFont", "(JLjava/nio/ByteBuffer;I)Z", (void*)FontFamily_addFont }, - { "nAddFontWeightStyle", "(JLjava/nio/ByteBuffer;ILjava/util/List;IZ)Z", + { "nAddFontWeightStyle", "(JLjava/nio/ByteBuffer;IIZ)Z", (void*)FontFamily_addFontWeightStyle }, { "nAddFontFromAssetManager", "(JLandroid/content/res/AssetManager;Ljava/lang/String;IZIZ)Z", (void*)FontFamily_addFontFromAssetManager }, + { "nAddAxisValue", "(JIF)V", (void*)FontFamily_addAxisValue }, }; int register_android_graphics_FontFamily(JNIEnv* env) diff --git a/core/jni/android/graphics/Graphics.cpp b/core/jni/android/graphics/Graphics.cpp index 7c56c7bf4158..e66587a9d8fe 100644 --- a/core/jni/android/graphics/Graphics.cpp +++ b/core/jni/android/graphics/Graphics.cpp @@ -461,12 +461,7 @@ sk_sp<SkColorSpace> GraphicsJNI::colorSpaceForType(SkColorType type) { } bool GraphicsJNI::isColorSpaceSRGB(SkColorSpace* colorSpace) { - return colorSpace == nullptr - || colorSpace == SkColorSpace::MakeSRGB().get() - || colorSpace == SkColorSpace::MakeRGB( - SkColorSpace::kSRGB_RenderTargetGamma, - SkColorSpace::kSRGB_Gamut, - SkColorSpace::kNonLinearBlending_ColorSpaceFlag).get(); + return colorSpace == nullptr || colorSpace->isSRGB(); } /////////////////////////////////////////////////////////////////////////////// diff --git a/core/jni/android/graphics/Paint.cpp b/core/jni/android/graphics/Paint.cpp index 18462376cd20..fa25a8f0c596 100644 --- a/core/jni/android/graphics/Paint.cpp +++ b/core/jni/android/graphics/Paint.cpp @@ -239,30 +239,38 @@ namespace PaintGlue { return result; } - static jint doTextRunCursor(JNIEnv *env, Paint* paint, const jchar *text, jint start, - jint count, jint flags, jint offset, jint opt) { + static jint doTextRunCursor(JNIEnv *env, Paint* paint, Typeface* typeface, const jchar *text, + jint start, jint count, jint dir, jint offset, jint opt) { minikin::GraphemeBreak::MoveOpt moveOpt = minikin::GraphemeBreak::MoveOpt(opt); - size_t result = minikin::GraphemeBreak::getTextRunCursor(text, start, count, offset, - moveOpt); + int bidiFlags = dir == 1 ? minikin::kBidi_Force_RTL : minikin::kBidi_Force_LTR; + std::unique_ptr<float[]> advancesArray(new float[count]); + MinikinUtils::measureText(paint, bidiFlags, typeface, text, start, count, start + count, + advancesArray.get()); + size_t result = minikin::GraphemeBreak::getTextRunCursor(advancesArray.get(), text, + start, count, offset, moveOpt); return static_cast<jint>(result); } - static jint getTextRunCursor___C(JNIEnv* env, jobject clazz, jlong paintHandle, jcharArray text, - jint contextStart, jint contextCount, jint dir, jint offset, jint cursorOpt) { + static jint getTextRunCursor___C(JNIEnv* env, jobject clazz, jlong paintHandle, + jlong typefaceHandle, jcharArray text, jint contextStart, jint contextCount, jint dir, + jint offset, jint cursorOpt) { Paint* paint = reinterpret_cast<Paint*>(paintHandle); + Typeface* typeface = reinterpret_cast<Typeface*>(typefaceHandle); jchar* textArray = env->GetCharArrayElements(text, nullptr); - jint result = doTextRunCursor(env, paint, textArray, contextStart, contextCount, dir, - offset, cursorOpt); + jint result = doTextRunCursor(env, paint, typeface, textArray, + contextStart, contextCount, dir, offset, cursorOpt); env->ReleaseCharArrayElements(text, textArray, JNI_ABORT); return result; } - static jint getTextRunCursor__String(JNIEnv* env, jobject clazz, jlong paintHandle, jstring text, - jint contextStart, jint contextEnd, jint dir, jint offset, jint cursorOpt) { + static jint getTextRunCursor__String(JNIEnv* env, jobject clazz, jlong paintHandle, + jlong typefaceHandle, jstring text, jint contextStart, jint contextEnd, jint dir, + jint offset, jint cursorOpt) { Paint* paint = reinterpret_cast<Paint*>(paintHandle); + Typeface* typeface = reinterpret_cast<Typeface*>(typefaceHandle); const jchar* textArray = env->GetStringChars(text, nullptr); - jint result = doTextRunCursor(env, paint, textArray, contextStart, - contextEnd - contextStart, dir, offset, cursorOpt); + jint result = doTextRunCursor(env, paint, typeface, textArray, + contextStart, contextEnd - contextStart, dir, offset, cursorOpt); env->ReleaseStringChars(text, textArray); return result; } @@ -983,8 +991,8 @@ static const JNINativeMethod methods[] = { {"nGetTextAdvances","(JJLjava/lang/String;IIIII[FI)F", (void*) PaintGlue::getTextAdvances__StringIIIII_FI}, - {"nGetTextRunCursor", "(J[CIIIII)I", (void*) PaintGlue::getTextRunCursor___C}, - {"nGetTextRunCursor", "(JLjava/lang/String;IIIII)I", + {"nGetTextRunCursor", "(JJ[CIIIII)I", (void*) PaintGlue::getTextRunCursor___C}, + {"nGetTextRunCursor", "(JJLjava/lang/String;IIIII)I", (void*) PaintGlue::getTextRunCursor__String}, {"nGetTextPath", "(JJI[CIIFFJ)V", (void*) PaintGlue::getTextPath___C}, {"nGetTextPath", "(JJILjava/lang/String;IIFFJ)V", (void*) PaintGlue::getTextPath__String}, diff --git a/core/jni/android_os_HwParcel.cpp b/core/jni/android_os_HwParcel.cpp index 1bd233313ca3..678041f85d17 100644 --- a/core/jni/android_os_HwParcel.cpp +++ b/core/jni/android_os_HwParcel.cpp @@ -404,6 +404,11 @@ static void JHwParcel_native_verifySuccess(JNIEnv *env, jobject thiz) { signalExceptionForError(env, err); } +static void JHwParcel_native_release( + JNIEnv *env, jobject thiz) { + JHwParcel::GetNativeContext(env, thiz)->setParcel(NULL, false /* assumeOwnership */); +} + static void JHwParcel_native_releaseTemporaryStorage( JNIEnv *env, jobject thiz) { JHwParcel::GetNativeContext(env, thiz)->getStorage()->release(env); @@ -955,6 +960,10 @@ static JNINativeMethod gMethods[] = { { "writeBuffer", "(L" PACKAGE_PATH "/HwBlob;)V", (void *)JHwParcel_native_writeBuffer }, + + { "release", "()V", + (void *)JHwParcel_native_release }, + }; namespace android { diff --git a/core/jni/fd_utils.cpp b/core/jni/fd_utils.cpp index 9660de4dbba4..956b7249660f 100644 --- a/core/jni/fd_utils.cpp +++ b/core/jni/fd_utils.cpp @@ -26,7 +26,9 @@ #include <sys/un.h> #include <unistd.h> +#include <android-base/file.h> #include <android-base/logging.h> +#include <android-base/stringprintf.h> #include <android-base/strings.h> // Static whitelist of open paths that the zygote is allowed to keep open. @@ -65,9 +67,10 @@ bool FileDescriptorWhitelist::IsAllowed(const std::string& path) const { return true; } - static const std::string kFrameworksPrefix = "/system/framework/"; - static const std::string kJarSuffix = ".jar"; - if (StartsWith(path, kFrameworksPrefix) && EndsWith(path, kJarSuffix)) { + static const char* kFrameworksPrefix = "/system/framework/"; + static const char* kJarSuffix = ".jar"; + if (android::base::StartsWith(path, kFrameworksPrefix) + && android::base::EndsWith(path, kJarSuffix)) { return true; } @@ -79,28 +82,31 @@ bool FileDescriptorWhitelist::IsAllowed(const std::string& path) const { // /data/resource-cache/system@vendor@overlay@framework-res.apk@idmap // /data/resource-cache/system@vendor@overlay-subdir@pg@framework-res.apk@idmap // See AssetManager.cpp for more details on overlay-subdir. - static const std::string kOverlayDir = "/system/vendor/overlay/"; - static const std::string kVendorOverlayDir = "/vendor/overlay"; - static const std::string kOverlaySubdir = "/system/vendor/overlay-subdir/"; - static const std::string kApkSuffix = ".apk"; - - if ((StartsWith(path, kOverlayDir) || StartsWith(path, kOverlaySubdir) - || StartsWith(path, kVendorOverlayDir)) - && EndsWith(path, kApkSuffix) + static const char* kOverlayDir = "/system/vendor/overlay/"; + static const char* kVendorOverlayDir = "/vendor/overlay"; + static const char* kOverlaySubdir = "/system/vendor/overlay-subdir/"; + static const char* kApkSuffix = ".apk"; + + if ((android::base::StartsWith(path, kOverlayDir) + || android::base::StartsWith(path, kOverlaySubdir) + || android::base::StartsWith(path, kVendorOverlayDir)) + && android::base::EndsWith(path, kApkSuffix) && path.find("/../") == std::string::npos) { return true; } - static const std::string kOverlayIdmapPrefix = "/data/resource-cache/"; - static const std::string kOverlayIdmapSuffix = ".apk@idmap"; - if (StartsWith(path, kOverlayIdmapPrefix) && EndsWith(path, kOverlayIdmapSuffix) + static const char* kOverlayIdmapPrefix = "/data/resource-cache/"; + static const char* kOverlayIdmapSuffix = ".apk@idmap"; + if (android::base::StartsWith(path, kOverlayIdmapPrefix) + && android::base::EndsWith(path, kOverlayIdmapSuffix) && path.find("/../") == std::string::npos) { return true; } // All regular files that are placed under this path are whitelisted automatically. - static const std::string kZygoteWhitelistPath = "/vendor/zygote_whitelist/"; - if (StartsWith(path, kZygoteWhitelistPath) && path.find("/../") == std::string::npos) { + static const char* kZygoteWhitelistPath = "/vendor/zygote_whitelist/"; + if (android::base::StartsWith(path, kZygoteWhitelistPath) + && path.find("/../") == std::string::npos) { return true; } @@ -111,24 +117,6 @@ FileDescriptorWhitelist::FileDescriptorWhitelist() : whitelist_() { } -// TODO: Call android::base::StartsWith instead of copying the code here. -// static -bool FileDescriptorWhitelist::StartsWith(const std::string& str, - const std::string& prefix) { - return str.compare(0, prefix.size(), prefix) == 0; -} - -// TODO: Call android::base::EndsWith instead of copying the code here. -// static -bool FileDescriptorWhitelist::EndsWith(const std::string& str, - const std::string& suffix) { - if (suffix.size() > str.size()) { - return false; - } - - return str.compare(str.size() - suffix.size(), suffix.size(), suffix) == 0; -} - FileDescriptorWhitelist* FileDescriptorWhitelist::instance_ = nullptr; // static @@ -174,7 +162,8 @@ FileDescriptorInfo* FileDescriptorInfo::CreateFromFd(int fd) { } std::string file_path; - if (!Readlink(fd, &file_path)) { + const std::string fd_path = android::base::StringPrintf("/proc/self/fd/%d", fd); + if (!android::base::Readlink(fd_path, &file_path)) { return NULL; } @@ -299,30 +288,6 @@ FileDescriptorInfo::FileDescriptorInfo(struct stat stat, const std::string& file is_sock(false) { } -// TODO: Call android::base::Readlink instead of copying the code here. -// static -bool FileDescriptorInfo::Readlink(const int fd, std::string* result) { - char path[64]; - snprintf(path, sizeof(path), "/proc/self/fd/%d", fd); - - // Code copied from android::base::Readlink starts here : - - // Annoyingly, the readlink system call returns EINVAL for a zero-sized buffer, - // and truncates to whatever size you do supply, so it can't be used to query. - // We could call lstat first, but that would introduce a race condition that - // we couldn't detect. - // ext2 and ext4 both have PAGE_SIZE limitations, so we assume that here. - char buf[4096]; - ssize_t len = readlink(path, buf, sizeof(buf)); - if (len == -1) { - PLOG(ERROR) << "Readlink on " << fd << " failed."; - return false; - } - - result->assign(buf, len); - return true; -} - // static bool FileDescriptorInfo::GetSocketName(const int fd, std::string* result) { sockaddr_storage ss; diff --git a/core/jni/fd_utils.h b/core/jni/fd_utils.h index 03298c38dca9..a39e387fde6c 100644 --- a/core/jni/fd_utils.h +++ b/core/jni/fd_utils.h @@ -59,10 +59,6 @@ class FileDescriptorWhitelist { private: FileDescriptorWhitelist(); - static bool StartsWith(const std::string& str, const std::string& prefix); - - static bool EndsWith(const std::string& str, const std::string& suffix); - static FileDescriptorWhitelist* instance_; std::vector<std::string> whitelist_; @@ -99,8 +95,6 @@ class FileDescriptorInfo { FileDescriptorInfo(struct stat stat, const std::string& file_path, int fd, int open_flags, int fd_flags, int fs_flags, off_t offset); - static bool Readlink(const int fd, std::string* result); - // Returns the locally-bound name of the socket |fd|. Returns true // iff. all of the following hold : // diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index c991f22b6294..58e4051b5111 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -3593,7 +3593,7 @@ android:permission="android.permission.BIND_JOB_SERVICE" > </service> - <service android:name="com.android.server.BackgroundDexOptJobService" + <service android:name="com.android.server.pm.BackgroundDexOptService" android:exported="true" android:permission="android.permission.BIND_JOB_SERVICE"> </service> diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index 248fd159f9c5..4432e3c90f1a 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -576,6 +576,7 @@ <attr name="floatingToolbarItemBackgroundDrawable" format="reference" /> <attr name="floatingToolbarOpenDrawable" format="reference" /> <attr name="floatingToolbarPopupBackgroundDrawable" format="reference" /> + <attr name="floatingToolbarDividerColor" format="reference" /> <!-- ============ --> <!-- Alert Dialog styles --> @@ -2307,37 +2308,9 @@ </attr> <!-- Describes the content of a view so that a autofill service can fill in the appropriate - data. Multiple flags can be combined to mean e.g. emailAddress or postalAddress. --> - <attr name="autofillHint"> - <!-- No hint. --> - <flag name="none" value="0" /> - <!-- The view contains an email address. --> - <flag name="emailAddress" value="0x1" /> - <!-- The view contains a real name. --> - <flag name="name" value="0x2" /> - <!-- The view contains a user name. --> - <flag name="username" value="0x4" /> - <!-- The view contains a password. --> - <flag name="password" value="0x8" /> - <!-- The view contains a phone number. --> - <flag name="phone" value="0x10" /> - <!-- The view contains a postal address. --> - <flag name="postalAddress" value="0x20" /> - <!-- The view contains a postal code. --> - <flag name="postalCode" value="0x40" /> - <!-- The view contains a credit card number. --> - <flag name="creditCardNumber" value="0x80" /> - <!-- The view contains a credit card security code --> - <flag name="creditCardSecurityCode" value="0x100" /> - <!-- The view contains a credit card expiration date --> - <flag name="creditCardExpirationDate" value="0x200" /> - <!-- The view contains the month a credit card expires --> - <flag name="creditCardExpirationMonth" value="0x400" /> - <!-- The view contains the year a credit card expires --> - <flag name="creditCardExpirationYear" value="0x800" /> - <!-- The view contains the day a credit card expires --> - <flag name="creditCardExpirationDay" value="0x1000" /> - </attr> + data. Multiple hints can be combined in a comma separated list or an array of strings + to mean e.g. emailAddress or postalAddress. --> + <attr name="autofillHint" format="string|reference" /> <!-- Hints the Android System whether the view node associated with this View should be included in a view structure used for autofill purposes. --> @@ -8407,7 +8380,9 @@ <!-- Component name of an activity that allows the user to set up this service. --> <attr name="setupActivity" format="string" /> <!-- Component name of an activity that allows the user to modify the settings for this - service. --> + service. + {@deprecated This value is deprecated and not used by the framework starting from API + level 26. Use setupActivity instead.} --> <attr name="settingsActivity" /> <!-- Attribute whether the TV input service can record programs. This value can be changed at runtime by calling diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml index bfe666eed6e9..67050f751f5e 100644 --- a/core/res/res/values/attrs_manifest.xml +++ b/core/res/res/values/attrs_manifest.xml @@ -2401,6 +2401,9 @@ <!-- Load order of overlay package. --> <attr name="priority" /> + <!-- Whether the given RRO is static or not. --> + <attr name="isStatic" format="boolean" /> + </declare-styleable> <!-- Declaration of an {@link android.content.Intent} object in XML. May diff --git a/core/res/res/values/colors.xml b/core/res/res/values/colors.xml index 6015ed5259d8..f9fd57cf5df9 100644 --- a/core/res/res/values/colors.xml +++ b/core/res/res/values/colors.xml @@ -194,4 +194,8 @@ <color name="tooltip_background_dark">#e6616161</color> <color name="tooltip_background_light">#e6FFFFFF</color> + + <!-- FloatingToolbar --> + <color name="floating_popup_divider_dark">#2F2F2F</color> + <color name="floating_popup_divider_light">#E9E9E9</color> </resources> diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 6e0d9dccea0f..385f256f5d1c 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -1386,6 +1386,9 @@ <!-- Boolean indicating if current platform supports BLE peripheral mode --> <bool name="config_bluetooth_le_peripheral_mode_supported">false</bool> + <!-- Boolean indicating if current platform supports HFP inband ringing --> + <bool name="config_bluetooth_hfp_inband_ringing_support">false</bool> + <!-- Max number of scan filters supported by blutooth controller. 0 if the device does not support hardware scan filters--> <integer translatable="false" name="config_bluetooth_max_scan_filters">0</integer> diff --git a/core/res/res/values/ids.xml b/core/res/res/values/ids.xml index f8a071d04372..cd3624db9afa 100644 --- a/core/res/res/values/ids.xml +++ b/core/res/res/values/ids.xml @@ -138,4 +138,8 @@ <!-- Accessibility action identifier for {@link android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction#ACTION_MOVE_WINDOW}. --> <item type="id" name="accessibilityActionMoveWindow" /> + + <!-- Action used to manually trigger an autofill request --> + <item type="id" name="autofill" /> + </resources> diff --git a/core/res/res/values/locale_config.xml b/core/res/res/values/locale_config.xml index 483d05b6cbd2..ba148431cd19 100644 --- a/core/res/res/values/locale_config.xml +++ b/core/res/res/values/locale_config.xml @@ -75,7 +75,7 @@ <item>ce-RU</item> <!-- Chechen (Russia) --> <item>cgg-UG</item> <!-- Chiga (Uganda) --> <item>chr-US</item> <!-- Cherokee (United States) --> - <item>cs-CZ</item> <!-- Czech (Czech Republic) --> + <item>cs-CZ</item> <!-- Czech (Czechia) --> <item>cy-GB</item> <!-- Welsh (United Kingdom) --> <item>da-DK</item> <!-- Danish (Denmark) --> <item>da-GL</item> <!-- Danish (Greenland) --> diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index bb3f1c3b8c58..2897c62a8aa5 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -2806,6 +2806,7 @@ <public name="fontProviderPackage" /> <public name="importantForAutofill" /> <public name="recycleEnabled"/> + <public name="isStatic" /> </public-group> <public-group type="style" first-id="0x010302e0"> @@ -2814,6 +2815,7 @@ <public-group type="id" first-id="0x01020041"> <public name="textAssist" /> <public name="accessibilityActionMoveWindow" /> + <public name="autofill" /> </public-group> diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index d1d406d8fa3e..d1c14e976003 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -2640,6 +2640,9 @@ <!-- Item on EditText context menu. This action is used to redo a text edit operation. --> <string name="redo">Redo</string> + <!-- Item on EditText context menu. This action is used to request autofill. --> + <string name="autofill">Autofill</string> + <!-- Text selection contextual mode title, displayed in the CAB. [CHAR LIMIT=20] --> <string name="textSelectionCABTitle">Text selection</string> @@ -4564,22 +4567,25 @@ <!-- Accessibility string used for describing the button in time picker that changes the dialog to circular clock mode. [CHAR LIMIT=NONE] --> <string name="time_picker_radial_mode_description">Switch to clock mode for the time input.</string> - <!-- Title for the auto-fill save dialog shown when the the contents of the activity can be saved - by an auto-fill service, but the service does not know what the activity represents [CHAR LIMIT=NONE] --> + <!-- Accessibility title for the autofill dialog used to select a list of options to autofill an activity. [CHAR LIMIT=NONE] --> + <string name="autofill_picker_accessibility_title">Autofill options</string> + + <!-- Title for the autofill save dialog shown when the the contents of the activity can be saved + by an autofill service, but the service does not know what the activity represents [CHAR LIMIT=NONE] --> <string name="autofill_save_title">Save to <xliff:g id="label" example="MyPass">%1$s</xliff:g>?</string> - <!-- Title for the auto-fill save dialog shown when the the contents of the activity can be saved - by an auto-fill service, and the service does knows what the activity represents (for example, credit card info) [CHAR LIMIT=NONE] --> + <!-- Title for the autofill save dialog shown when the the contents of the activity can be saved + by an autofill service, and the service does knows what the activity represents (for example, credit card info) [CHAR LIMIT=NONE] --> <string name="autofill_save_title_with_type">Save <xliff:g id="type" example="Credit Card">%1$s</xliff:g> to <xliff:g id="label" example="MyPass">%2$s</xliff:g>?</string> - <!-- Label for the auto-fill save button [CHAR LIMIT=NONE] --> + <!-- Label for the autofill save button [CHAR LIMIT=NONE] --> <string name="autofill_save_yes">Save</string> - <!-- Label for the auto-fill cancel button [CHAR LIMIT=NONE] --> + <!-- Label for the autofill cancel button [CHAR LIMIT=NONE] --> <string name="autofill_save_no">No thanks</string> - <!-- Label for the type of data being saved for auto-fill when it represent user credentials with a password [CHAR LIMIT=NONE] --> + <!-- Label for the type of data being saved for autofill when it represent user credentials with a password [CHAR LIMIT=NONE] --> <string name="autofill_save_type_password">password</string> - <!-- Label for the type of data being saved for auto-fill when it represent an address (street, city, etc.) [CHAR LIMIT=NONE] --> + <!-- Label for the type of data being saved for autofill when it represent an address (street, city, etc.) [CHAR LIMIT=NONE] --> <string name="autofill_save_type_address">address</string> - <!-- Label for the type of data being saved for auto-fill when it represents a credit card [CHAR LIMIT=NONE] --> + <!-- Label for the type of data being saved for autofill when it represents a credit card [CHAR LIMIT=NONE] --> <string name="autofill_save_type_credit_card">credit card</string> </resources> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 0c318cf5e355..0d4a407db242 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -242,6 +242,7 @@ <java-symbol type="bool" name="config_bluetooth_address_validation" /> <java-symbol type="bool" name="config_bluetooth_sco_off_call" /> <java-symbol type="bool" name="config_bluetooth_le_peripheral_mode_supported" /> + <java-symbol type="bool" name="config_bluetooth_hfp_inband_ringing_support" /> <java-symbol type="bool" name="config_cellBroadcastAppLinks" /> <java-symbol type="bool" name="config_duplicate_port_omadm_wappush" /> <java-symbol type="bool" name="config_enableAutoPowerModes" /> @@ -2425,6 +2426,7 @@ <java-symbol type="drawable" name="ft_avd_toarrow" /> <java-symbol type="drawable" name="ft_avd_toarrow_animation" /> <java-symbol type="drawable" name="ft_avd_tooverflow_animation" /> + <java-symbol type="attr" name="floatingToolbarDividerColor" /> <java-symbol type="string" name="date_picker_prev_month_button" /> <java-symbol type="string" name="date_picker_next_month_button" /> @@ -2840,11 +2842,14 @@ <java-symbol type="dimen" name="autofill_fill_min_margin" /> <java-symbol type="layout" name="autofill_save"/> <java-symbol type="layout" name="autofill_dataset_picker"/> + <java-symbol type="id" name="autofill" /> <java-symbol type="id" name="autofill_save_title" /> <java-symbol type="id" name="autofill_save_subtitle" /> <java-symbol type="id" name="autofill_save_no" /> <java-symbol type="id" name="autofill_save_yes" /> <java-symbol type="id" name="autofill_save_close" /> + <java-symbol type="string" name="autofill" /> + <java-symbol type="string" name="autofill_picker_accessibility_title " /> <java-symbol type="string" name="autofill_save_title" /> <java-symbol type="string" name="autofill_save_title_with_type" /> <java-symbol type="string" name="autofill_save_yes" /> diff --git a/core/res/res/values/themes.xml b/core/res/res/values/themes.xml index d100c63d4ec1..a661b070872d 100644 --- a/core/res/res/values/themes.xml +++ b/core/res/res/values/themes.xml @@ -398,6 +398,7 @@ please see themes_device_defaults.xml. <item name="floatingToolbarItemBackgroundDrawable">@drawable/item_background_material_dark</item> <item name="floatingToolbarOpenDrawable">@drawable/ic_menu_moreoverflow_material_dark</item> <item name="floatingToolbarPopupBackgroundDrawable">@drawable/floating_popup_background_dark</item> + <item name="floatingToolbarDividerColor">@color/floating_popup_divider_dark</item> <!-- SearchView attributes --> <item name="searchViewStyle">@style/Widget.Holo.SearchView</item> @@ -559,6 +560,7 @@ please see themes_device_defaults.xml. <item name="floatingToolbarItemBackgroundDrawable">@drawable/item_background_material_light</item> <item name="floatingToolbarOpenDrawable">@drawable/ic_menu_moreoverflow_material_light</item> <item name="floatingToolbarPopupBackgroundDrawable">@drawable/floating_popup_background_light</item> + <item name="floatingToolbarDividerColor">@color/floating_popup_divider_light</item> <!-- Tooltip popup colors --> <item name="tooltipForegroundColor">@color/bright_foreground_dark</item> diff --git a/core/res/res/xml/sms_short_codes.xml b/core/res/res/xml/sms_short_codes.xml index 29c6b7965543..1ae922aba716 100644 --- a/core/res/res/xml/sms_short_codes.xml +++ b/core/res/res/xml/sms_short_codes.xml @@ -79,7 +79,7 @@ <!-- Cyprus: 4-6 digits (not confirmed), known premium codes listed, plus EU --> <shortcode country="cy" pattern="\\d{4,6}" premium="7510" free="116\\d{3}" /> - <!-- Czech Republic: 7-8 digits, starting with 9, plus EU: + <!-- Czechia: 7-8 digits, starting with 9, plus EU: http://www.o2.cz/osobni/en/services-by-alphabet/91670-premium_sms.html --> <shortcode country="cz" premium="9\\d{6,7}" free="116\\d{3}" /> diff --git a/core/tests/coretests/Android.mk b/core/tests/coretests/Android.mk index fba8e2300607..dbc9e5d55e37 100644 --- a/core/tests/coretests/Android.mk +++ b/core/tests/coretests/Android.mk @@ -40,6 +40,7 @@ LOCAL_STATIC_JAVA_LIBRARIES := \ LOCAL_JAVA_LIBRARIES := android.test.runner conscrypt telephony-common org.apache.http.legacy LOCAL_PACKAGE_NAME := FrameworksCoreTests +LOCAL_COMPATIBILITY_SUITE := device-tests LOCAL_CERTIFICATE := platform diff --git a/core/tests/coretests/src/android/content/res/FontResourcesParserTest.java b/core/tests/coretests/src/android/content/res/FontResourcesParserTest.java index 23d3aa5a64ab..82f46909cfa8 100644 --- a/core/tests/coretests/src/android/content/res/FontResourcesParserTest.java +++ b/core/tests/coretests/src/android/content/res/FontResourcesParserTest.java @@ -20,6 +20,11 @@ import static junit.framework.Assert.assertNull; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; +import static android.content.res.FontResourcesParser.FamilyResourceEntry; +import static android.content.res.FontResourcesParser.ProviderResourceEntry; +import static android.content.res.FontResourcesParser.FontFileResourceEntry; +import static android.content.res.FontResourcesParser.FontFamilyFilesResourceEntry; + import android.app.Instrumentation; import android.support.test.InstrumentationRegistry; import android.support.test.filters.SmallTest; @@ -56,44 +61,40 @@ public class FontResourcesParserTest { public void testParse() throws XmlPullParserException, IOException { XmlResourceParser parser = mResources.getXml(R.font.samplexmlfont); - FontConfig result = FontResourcesParser.parse(parser, mResources); + FamilyResourceEntry result = FontResourcesParser.parse(parser, mResources); assertNotNull(result); - List<FontConfig.Family> families = result.getFamilies(); - assertEquals(1, families.size()); - List<FontConfig.Font> fonts = families.get(0).getFonts(); - assertEquals(4, fonts.size()); - FontConfig.Font font1 = fonts.get(0); + FontFamilyFilesResourceEntry filesEntry = (FontFamilyFilesResourceEntry) result; + FontFileResourceEntry[] fileEntries = filesEntry.getEntries(); + assertEquals(4, fileEntries.length); + FontFileResourceEntry font1 = fileEntries[0]; assertEquals(400, font1.getWeight()); assertEquals(false, font1.isItalic()); - assertEquals("res/font/samplefont.ttf", font1.getFontName()); - FontConfig.Font font2 = fonts.get(1); + assertEquals("res/font/samplefont.ttf", font1.getFileName()); + FontFileResourceEntry font2 = fileEntries[1]; assertEquals(400, font2.getWeight()); assertEquals(true, font2.isItalic()); - assertEquals("res/font/samplefont2.ttf", font2.getFontName()); - FontConfig.Font font3 = fonts.get(2); + assertEquals("res/font/samplefont2.ttf", font2.getFileName()); + FontFileResourceEntry font3 = fileEntries[2]; assertEquals(800, font3.getWeight()); assertEquals(false, font3.isItalic()); - assertEquals("res/font/samplefont3.ttf", font3.getFontName()); - FontConfig.Font font4 = fonts.get(3); + assertEquals("res/font/samplefont3.ttf", font3.getFileName()); + FontFileResourceEntry font4 = fileEntries[3]; assertEquals(800, font4.getWeight()); assertEquals(true, font4.isItalic()); - assertEquals("res/font/samplefont4.ttf", font4.getFontName()); + assertEquals("res/font/samplefont4.ttf", font4.getFileName()); } @Test public void testParseDownloadableFont() throws IOException, XmlPullParserException { XmlResourceParser parser = mResources.getXml(R.font.samplexmldownloadedfont); - FontConfig result = FontResourcesParser.parse(parser, mResources); + FamilyResourceEntry result = FontResourcesParser.parse(parser, mResources); assertNotNull(result); - List<FontConfig.Family> families = result.getFamilies(); - assertEquals(1, families.size()); - FontConfig.Family family = families.get(0); - assertEquals("com.example.test.fontprovider.authority", family.getProviderAuthority()); - assertEquals("com.example.test.fontprovider.package", family.getProviderPackage()); - assertEquals("MyRequestedFont", family.getQuery()); - assertNull(family.getFonts()); + ProviderResourceEntry providerEntry = (ProviderResourceEntry) result; + assertEquals("com.example.test.fontprovider.authority", providerEntry.getAuthority()); + assertEquals("com.example.test.fontprovider.package", providerEntry.getPackage()); + assertEquals("MyRequestedFont", providerEntry.getQuery()); } } diff --git a/core/tests/coretests/src/android/database/PageViewCursorTest.java b/core/tests/coretests/src/android/database/PageViewCursorTest.java new file mode 100644 index 000000000000..0be89d5361d8 --- /dev/null +++ b/core/tests/coretests/src/android/database/PageViewCursorTest.java @@ -0,0 +1,318 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.database; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import android.annotation.Nullable; +import android.content.ContentResolver; +import android.os.Bundle; +import android.support.test.runner.AndroidJUnit4; +import android.util.Log; +import android.util.MathUtils; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.ArrayUtils; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.Random; + +@RunWith(AndroidJUnit4.class) +public class PageViewCursorTest { + + private static final int ITEM_COUNT = 20; + + private static final String NAME_COLUMN = "name"; + private static final String NUM_COLUMN = "num"; + + private static final String[] COLUMNS = new String[]{ + NAME_COLUMN, + NUM_COLUMN + }; + + private static final String[] NAMES = new String[] { + "000", + "111", + "222", + "333", + "444", + "555", + "666", + "777", + "888", + "999", + "aaa", + "bbb", + "ccc", + "ddd", + "eee", + "fff", + "ggg", + "hhh", + "iii", + "jjj" + }; + + private MatrixCursor mDelegate; + private PageViewCursor mCursor; + + @Before + public void setUp() { + Random rand = new Random(); + + mDelegate = new MatrixCursor(COLUMNS); + for (int i = 0; i < ITEM_COUNT; i++) { + MatrixCursor.RowBuilder row = mDelegate.newRow(); + row.add(NAME_COLUMN, NAMES[i]); + row.add(NUM_COLUMN, rand.nextInt()); + } + + mCursor = new PageViewCursor(mDelegate, 10, 5); + } + + @Test + public void testPage_Size() { + assertEquals(5, mCursor.getCount()); + } + + @Test + public void testPage_TotalSize() { + assertEquals(ITEM_COUNT, mCursor.getExtras().getInt(ContentResolver.EXTRA_TOTAL_SIZE)); + } + + @Test + public void testPage_OffsetExceedsCursorCount_EffectivelyEmptyCursor() { + mCursor = new PageViewCursor(mDelegate, ITEM_COUNT * 2, 5); + assertEquals(0, mCursor.getCount()); + } + + @Test + public void testMoveToPosition() { + assertTrue(mCursor.moveToPosition(0)); + assertEquals(NAMES[10], mCursor.getString(0)); + assertTrue(mCursor.moveToPosition(1)); + assertEquals(NAMES[11], mCursor.getString(0)); + assertTrue(mCursor.moveToPosition(4)); + assertEquals(NAMES[14], mCursor.getString(0)); + + // and then back down again for good measure. + assertTrue(mCursor.moveToPosition(1)); + assertEquals(NAMES[11], mCursor.getString(0)); + assertTrue(mCursor.moveToPosition(0)); + assertEquals(NAMES[10], mCursor.getString(0)); + } + + @Test + public void testMoveToPosition_MoveToSamePosition_NoOp() { + assertTrue(mCursor.moveToPosition(1)); + assertEquals(NAMES[11], mCursor.getString(0)); + assertTrue(mCursor.moveToPosition(1)); + assertEquals(NAMES[11], mCursor.getString(0)); + } + + @Test + public void testMoveToPosition_PositionOutOfBounds_MovesToBeforeFirst() { + assertTrue(mCursor.moveToPosition(0)); + assertEquals(NAMES[10], mCursor.getString(0)); + + // move before + assertFalse(mCursor.moveToPosition(-12)); + assertTrue(mCursor.isBeforeFirst()); + } + + @Test + public void testMoveToPosition_PositionOutOfBounds_MovesToAfterLast() { + assertTrue(mCursor.moveToPosition(0)); + assertEquals(NAMES[10], mCursor.getString(0)); + + assertFalse(mCursor.moveToPosition(222)); + assertTrue(mCursor.isAfterLast()); + } + + @Test + public void testPosition() { + assertEquals(-1, mCursor.getPosition()); + } + + @Test + public void testIsBeforeFirst() { + assertTrue(mCursor.isBeforeFirst()); + mCursor.moveToFirst(); + assertFalse(mCursor.isBeforeFirst()); + } + + @Test + public void testCount_ZeroForEmptyCursor() { + mCursor = new PageViewCursor(mDelegate, 0, 0); + assertEquals(0, mCursor.getCount()); + } + + @Test + public void testIsBeforeFirst_TrueForEmptyCursor() { + mCursor = new PageViewCursor(mDelegate, 0, 0); + assertTrue(mCursor.isBeforeFirst()); + } + + @Test + public void testIsAfterLast() { + assertFalse(mCursor.isAfterLast()); + mCursor.moveToLast(); + mCursor.moveToNext(); + assertTrue(mCursor.isAfterLast()); + } + + @Test + public void testIsAfterLast_TrueForEmptyCursor() { + mCursor = new PageViewCursor(mDelegate, 0, 0); + assertTrue(mCursor.isAfterLast()); + } + + @Test + public void testIsFirst() { + assertFalse(mCursor.isFirst()); + mCursor.moveToFirst(); + assertTrue(mCursor.isFirst()); + } + + @Test + public void testIsLast() { + assertFalse(mCursor.isLast()); + mCursor.moveToLast(); + assertTrue(mCursor.isLast()); + } + + @Test + public void testMove() { + // note that initial position is -1, so moving + // 2 will only put as at 1. + mCursor.move(2); + assertEquals(NAMES[11], mCursor.getString(0)); + mCursor.move(-1); + assertEquals(NAMES[10], mCursor.getString(0)); + } + + @Test + public void testMoveToFist() { + mCursor.moveToPosition(3); + mCursor.moveToFirst(); + assertEquals(NAMES[10], mCursor.getString(0)); + } + + @Test + public void testMoveToLast() { + mCursor.moveToLast(); + assertEquals(NAMES[14], mCursor.getString(0)); + } + + @Test + public void testMoveToNext() { + // default position is -1, so next is 0. + mCursor.moveToNext(); + assertEquals(NAMES[10], mCursor.getString(0)); + } + + @Test + public void testMoveToNext_AfterLastReturnsFalse() { + mCursor.moveToLast(); + assertFalse(mCursor.moveToNext()); + } + + @Test + public void testMoveToPrevious() { + mCursor.moveToPosition(3); + mCursor.moveToPrevious(); + assertEquals(NAMES[12], mCursor.getString(0)); + } + + @Test + public void testMoveToPrevious_BeforeFirstReturnsFalse() { + assertFalse(mCursor.moveToPrevious()); + } + + @Test + public void testWindow_ReadPastEnd() { + assertFalse(mCursor.moveToPosition(10)); + } + + @Test + public void testOffset_LimitOutOfBounds() { + mCursor = new PageViewCursor(mDelegate, 5, 100); + assertEquals(15, mCursor.getCount()); + } + + @Test + public void testPagingMarker() { + mCursor = new PageViewCursor(mDelegate, 5, 100); + assertTrue(mCursor.getExtras().getBoolean(PageViewCursor.EXTRA_AUTO_PAGED)); + } + + @Test + public void testWrap() { + Bundle queryArgs = new Bundle(); + queryArgs.putInt(ContentResolver.QUERY_ARG_OFFSET, 5); + queryArgs.putInt(ContentResolver.QUERY_ARG_LIMIT, 5); + Cursor wrapped = PageViewCursor.wrap(mDelegate, queryArgs); + assertTrue(wrapped instanceof PageViewCursor); + assertEquals(5, wrapped.getCount()); + } + + @Test + public void testWrap_NoOpWithoutPagingArgs() { + Cursor wrapped = PageViewCursor.wrap(mDelegate, Bundle.EMPTY); + assertTrue(mDelegate == wrapped); + } + + @Test + public void testWrap_NoOpCursorsWithExistingPaging_ByTotalSize() { + Bundle extras = new Bundle(); + extras.putInt(ContentResolver.EXTRA_TOTAL_SIZE, 5); + mDelegate.setExtras(extras); + + Bundle queryArgs = new Bundle(); + queryArgs.putInt(ContentResolver.QUERY_ARG_OFFSET, 5); + queryArgs.putInt(ContentResolver.QUERY_ARG_LIMIT, 5); + Cursor wrapped = PageViewCursor.wrap(mDelegate, queryArgs); + assertTrue(mDelegate == wrapped); + } + + @Test + public void testWrap_NoOpCursorsWithExistingPaging_ByHonoredArgs() { + Bundle extras = new Bundle(); + extras.putStringArray( + ContentResolver.EXTRA_HONORED_ARGS, + new String[] { + ContentResolver.QUERY_ARG_OFFSET, + ContentResolver.QUERY_ARG_LIMIT + }); + mDelegate.setExtras(extras); + + Bundle queryArgs = new Bundle(); + queryArgs.putInt(ContentResolver.QUERY_ARG_OFFSET, 5); + queryArgs.putInt(ContentResolver.QUERY_ARG_LIMIT, 5); + Cursor wrapped = PageViewCursor.wrap(mDelegate, queryArgs); + assertTrue(mDelegate == wrapped); + } + + private void assertStringAt(int row, int column, String expected) { + mCursor.moveToPosition(row); + assertEquals(expected, mCursor.getString(column)); + } +} diff --git a/data/fonts/fonts.xml b/data/fonts/fonts.xml index 76598a06c165..38a1a466400d 100644 --- a/data/fonts/fonts.xml +++ b/data/fonts/fonts.xml @@ -248,6 +248,9 @@ <font weight="400" style="normal">NotoSansCham-Regular.ttf</font> <font weight="700" style="normal">NotoSansCham-Bold.ttf</font> </family> + <family lang="und-Avst"> + <font weight="400" style="normal">NotoSansAvestan-Regular.ttf</font> + </family> <family lang="und-Bali"> <font weight="400" style="normal">NotoSansBalinese-Regular.ttf</font> </family> @@ -257,6 +260,9 @@ <family lang="und-Batk"> <font weight="400" style="normal">NotoSansBatak-Regular.ttf</font> </family> + <family lang="und-Brah"> + <font weight="400" style="normal">NotoSansBrahmi-Regular.ttf</font> + </family> <family lang="und-Bugi"> <font weight="400" style="normal">NotoSansBuginese-Regular.ttf</font> </family> @@ -266,33 +272,75 @@ <family lang="und-Cans"> <font weight="400" style="normal">NotoSansCanadianAboriginal-Regular.ttf</font> </family> + <family lang="und-Cari"> + <font weight="400" style="normal">NotoSansCarian-Regular.ttf</font> + </family> <family lang="und-Cher"> <font weight="400" style="normal">NotoSansCherokee-Regular.ttf</font> </family> <family lang="und-Copt"> <font weight="400" style="normal">NotoSansCoptic-Regular.ttf</font> </family> + <family lang="und-Xsux"> + <font weight="400" style="normal">NotoSansCuneiform-Regular.ttf</font> + </family> + <family lang="und-Cprt"> + <font weight="400" style="normal">NotoSansCypriot-Regular.ttf</font> + </family> + <family lang="und-Dsrt"> + <font weight="400" style="normal">NotoSansDeseret-Regular.ttf</font> + </family> + <family lang="und-Egyp"> + <font weight="400" style="normal">NotoSansEgyptianHieroglyphs-Regular.ttf</font> + </family> <family lang="und-Glag"> <font weight="400" style="normal">NotoSansGlagolitic-Regular.ttf</font> </family> + <family lang="und-Goth"> + <font weight="400" style="normal">NotoSansGothic-Regular.ttf</font> + </family> <family lang="und-Hano"> <font weight="400" style="normal">NotoSansHanunoo-Regular.ttf</font> </family> + <family lang="und-Armi"> + <font weight="400" style="normal">NotoSansImperialAramaic-Regular.ttf</font> + </family> + <family lang="und-Phli"> + <font weight="400" style="normal">NotoSansInscriptionalPahlavi-Regular.ttf</font> + </family> + <family lang="und-Prti"> + <font weight="400" style="normal">NotoSansInscriptionalParthian-Regular.ttf</font> + </family> <family lang="und-Java"> <font weight="400" style="normal">NotoSansJavanese-Regular.ttf</font> </family> + <family lang="und-Kthi"> + <font weight="400" style="normal">NotoSansKaithi-Regular.ttf</font> + </family> <family lang="und-Kali"> <font weight="400" style="normal">NotoSansKayahLi-Regular.ttf</font> </family> + <family lang="und-Khar"> + <font weight="400" style="normal">NotoSansKharoshthi-Regular.ttf</font> + </family> <family lang="und-Lepc"> <font weight="400" style="normal">NotoSansLepcha-Regular.ttf</font> </family> <family lang="und-Limb"> <font weight="400" style="normal">NotoSansLimbu-Regular.ttf</font> </family> + <family lang="und-Linb"> + <font weight="400" style="normal">NotoSansLinearB-Regular.ttf</font> + </family> <family lang="und-Lisu"> <font weight="400" style="normal">NotoSansLisu-Regular.ttf</font> </family> + <family lang="und-Lyci"> + <font weight="400" style="normal">NotoSansLycian-Regular.ttf</font> + </family> + <family lang="und-Lydi"> + <font weight="400" style="normal">NotoSansLydian-Regular.ttf</font> + </family> <family lang="und-Mand"> <font weight="400" style="normal">NotoSansMandaic-Regular.ttf</font> </family> @@ -305,12 +353,33 @@ <family lang="und-Nkoo"> <font weight="400" style="normal">NotoSansNKo-Regular.ttf</font> </family> + <family lang="und-Ogam"> + <font weight="400" style="normal">NotoSansOgham-Regular.ttf</font> + </family> <family lang="und-Olck"> <font weight="400" style="normal">NotoSansOlChiki-Regular.ttf</font> </family> + <family lang="und-Ital"> + <font weight="400" style="normal">NotoSansOldItalic-Regular.ttf</font> + </family> + <family lang="und-Xpeo"> + <font weight="400" style="normal">NotoSansOldPersian-Regular.ttf</font> + </family> + <family lang="und-Sarb"> + <font weight="400" style="normal">NotoSansOldSouthArabian-Regular.ttf</font> + </family> + <family lang="und-Orkh"> + <font weight="400" style="normal">NotoSansOldTurkic-Regular.ttf</font> + </family> + <family lang="und-Osma"> + <font weight="400" style="normal">NotoSansOsmanya-Regular.ttf</font> + </family> <family lang="und-Phag"> <font weight="400" style="normal">NotoSansPhagsPa-Regular.ttf</font> </family> + <family lang="und-Phnx"> + <font weight="400" style="normal">NotoSansPhoenician-Regular.ttf</font> + </family> <family lang="und-Rjng"> <font weight="400" style="normal">NotoSansRejang-Regular.ttf</font> </family> @@ -323,6 +392,9 @@ <family lang="und-Saur"> <font weight="400" style="normal">NotoSansSaurashtra-Regular.ttf</font> </family> + <family lang="und-Shaw"> + <font weight="400" style="normal">NotoSansShavian-Regular.ttf</font> + </family> <family lang="und-Sund"> <font weight="400" style="normal">NotoSansSundanese-Regular.ttf</font> </family> @@ -358,6 +430,9 @@ <family lang="und-Tfng"> <font weight="400" style="normal">NotoSansTifinagh-Regular.ttf</font> </family> + <family lang="und-Ugar"> + <font weight="400" style="normal">NotoSansUgaritic-Regular.ttf</font> + </family> <family lang="und-Vaii"> <font weight="400" style="normal">NotoSansVai-Regular.ttf</font> </family> diff --git a/drm/java/android/drm/DrmManagerClient.java b/drm/java/android/drm/DrmManagerClient.java index 704f0ce7624b..5973d3e33323 100644 --- a/drm/java/android/drm/DrmManagerClient.java +++ b/drm/java/android/drm/DrmManagerClient.java @@ -275,6 +275,10 @@ public class DrmManagerClient implements AutoCloseable { * the {@link DrmManagerClient} object is no longer needed in your * application. After this method is called, {@link DrmManagerClient} is no * longer usable since it has lost all of its required resource. + * + * This method was added in API 24. In API versions 16 through 23, release() + * should be called instead. There is no need to do anything for API + * versions prior to 16. */ @Override public void close() { diff --git a/graphics/java/android/graphics/Bitmap.java b/graphics/java/android/graphics/Bitmap.java index 3d5ba7938b31..ed587bbaed24 100644 --- a/graphics/java/android/graphics/Bitmap.java +++ b/graphics/java/android/graphics/Bitmap.java @@ -791,12 +791,12 @@ public final class Bitmap implements Parcelable { int neww = width; int newh = height; - Canvas canvas = new Canvas(); Bitmap bitmap; Paint paint; Rect srcR = new Rect(x, y, x + width, y + height); RectF dstR = new RectF(0, 0, width, height); + RectF deviceR = new RectF(); Config newConfig = Config.ARGB_8888; final Config config = source.getConfig(); @@ -827,7 +827,6 @@ public final class Bitmap implements Parcelable { } else { final boolean transformed = !m.rectStaysRect(); - RectF deviceR = new RectF(); m.mapRect(deviceR, dstR); neww = Math.round(deviceR.width()); @@ -841,9 +840,6 @@ public final class Bitmap implements Parcelable { } bitmap = createBitmap(neww, newh, transformedConfig, transformed || source.hasAlpha()); - canvas.translate(-deviceR.left, -deviceR.top); - canvas.concat(m); - paint = new Paint(); paint.setFilterBitmap(filter); if (transformed) { @@ -857,7 +853,9 @@ public final class Bitmap implements Parcelable { bitmap.setHasAlpha(source.hasAlpha()); bitmap.setPremultiplied(source.mRequestPremultiplied); - canvas.setBitmap(bitmap); + Canvas canvas = new Canvas(bitmap); + canvas.translate(-deviceR.left, -deviceR.top); + canvas.concat(m); canvas.drawBitmap(source, srcR, dstR, paint); canvas.setBitmap(null); if (isHardware) { diff --git a/graphics/java/android/graphics/Canvas.java b/graphics/java/android/graphics/Canvas.java index 2a2e14b2df6d..7289429537fe 100644 --- a/graphics/java/android/graphics/Canvas.java +++ b/graphics/java/android/graphics/Canvas.java @@ -157,12 +157,10 @@ public class Canvas extends BaseCanvas { /** * Specify a bitmap for the canvas to draw into. All canvas state such as - * layers, filters, and the save/restore stack are reset. Additionally, + * layers, filters, and the save/restore stack are reset with the exception + * of the current matrix and clip stack. Additionally, as a side-effect * the canvas' target density is updated to match that of the bitmap. * - * Prior to API level {@value Build.VERSION_CODES#O} the current matrix and - * clip stack were preserved. - * * @param bitmap Specifies a mutable bitmap for the canvas to draw into. * @see #setDensity(int) * @see #getDensity() diff --git a/graphics/java/android/graphics/ColorFilter.java b/graphics/java/android/graphics/ColorFilter.java index ac62bf4d69e9..0ca3729dcc0e 100644 --- a/graphics/java/android/graphics/ColorFilter.java +++ b/graphics/java/android/graphics/ColorFilter.java @@ -28,21 +28,51 @@ package android.graphics; */ public class ColorFilter { /** + * @deprecated Use subclass constructors directly instead. + */ + @Deprecated + public ColorFilter() {} + + /** * Holds the pointer to the native SkColorFilter instance. - * - * @hide */ - public long native_instance; + private long mNativeInstance; + + long createNativeInstance() { + return 0; + } + + void discardNativeInstance() { + if (mNativeInstance != 0) { + nSafeUnref(mNativeInstance); + mNativeInstance = 0; + } + } @Override protected void finalize() throws Throwable { try { - super.finalize(); + if (mNativeInstance != 0) { + nSafeUnref(mNativeInstance); + } + mNativeInstance = -1; } finally { - destroyFilter(native_instance); - native_instance = 0; + super.finalize(); + } + } + + /** @hide */ + public long getNativeInstance() { + if (mNativeInstance == -1) { + throw new IllegalStateException("attempting to use a finalized ColorFilter"); + } + + if (mNativeInstance == 0) { + mNativeInstance = createNativeInstance(); } + return mNativeInstance; + } - static native void destroyFilter(long native_instance); + static native void nSafeUnref(long native_instance); } diff --git a/graphics/java/android/graphics/ColorMatrix.java b/graphics/java/android/graphics/ColorMatrix.java index 1b1849ebb835..6299b2c47ea1 100644 --- a/graphics/java/android/graphics/ColorMatrix.java +++ b/graphics/java/android/graphics/ColorMatrix.java @@ -268,4 +268,21 @@ public class ColorMatrix { m[5] = 1; m[6] = -0.34414f; m[7] = -0.71414f; m[10] = 1; m[11] = 1.772f; m[12] = 0; } + + @Override + public boolean equals(Object obj) { + // if (obj == this) return true; -- NaN value would mean matrix != itself + if (!(obj instanceof ColorMatrix)) { + return false; + } + + // we don't use Arrays.equals(), since that considers NaN == NaN + final float[] other = ((ColorMatrix) obj).mArray; + for (int i = 0; i < 20; i++) { + if (other[i] != mArray[i]) { + return false; + } + } + return true; + } } diff --git a/graphics/java/android/graphics/ColorMatrixColorFilter.java b/graphics/java/android/graphics/ColorMatrixColorFilter.java index 291c8ff301f4..61f6cc5ba4ee 100644 --- a/graphics/java/android/graphics/ColorMatrixColorFilter.java +++ b/graphics/java/android/graphics/ColorMatrixColorFilter.java @@ -16,6 +16,9 @@ package android.graphics; +import android.annotation.NonNull; +import android.annotation.Nullable; + /** * A color filter that transforms colors through a 4x5 color matrix. This filter * can be used to change the saturation of pixels, convert from YUV to RGB, etc. @@ -32,9 +35,8 @@ public class ColorMatrixColorFilter extends ColorFilter { * the filter, so changes made to the matrix after the filter * is constructed will not be reflected in the filter. */ - public ColorMatrixColorFilter(ColorMatrix matrix) { + public ColorMatrixColorFilter(@NonNull ColorMatrix matrix) { mMatrix.set(matrix); - update(); } /** @@ -44,84 +46,76 @@ public class ColorMatrixColorFilter extends ColorFilter { * matrix. The first 20 entries of the array are copied into * the filter. See ColorMatrix. */ - public ColorMatrixColorFilter(float[] array) { + public ColorMatrixColorFilter(@NonNull float[] array) { if (array.length < 20) { throw new ArrayIndexOutOfBoundsException(); } mMatrix.set(array); - update(); } /** - * Returns the {@link ColorMatrix} used by this filter. The returned - * value is never null. Modifying the returned matrix does not have - * any effect until you call {@link #setColorMatrix(ColorMatrix)}. - * - * @see #setColorMatrix(ColorMatrix) + * Copies the ColorMatrix from the filter into the passed ColorMatrix. * - * @hide + * @param colorMatrix Set to the current value of the filter's ColorMatrix. */ - public ColorMatrix getColorMatrix() { - return mMatrix; + public void getColorMatrix(ColorMatrix colorMatrix) { + colorMatrix.set(mMatrix); } /** - * Specifies the color matrix used by this filter. If the specified - * color matrix is null, this filter's color matrix will be reset to - * the identity matrix. + * Copies the provided color matrix to be used by this filter. * - * @param matrix A {@link ColorMatrix} or null + * If the specified color matrix is null, this filter's color matrix will be reset to the + * identity matrix. * - * @see #getColorMatrix() - * @see android.graphics.ColorMatrix#reset() - * @see #setColorMatrix(float[]) + * @param matrix A {@link ColorMatrix} or null * - * @hide + * @see #getColorMatrix(ColorMatrix) + * @see #setColorMatrixArray(float[]) + * @see ColorMatrix#reset() */ - public void setColorMatrix(ColorMatrix matrix) { + public void setColorMatrix(@Nullable ColorMatrix matrix) { + discardNativeInstance(); if (matrix == null) { mMatrix.reset(); - } else if (matrix != mMatrix) { + } else { mMatrix.set(matrix); } - update(); } /** - * Specifies the color matrix used by this filter. If the specified - * color matrix is null, this filter's color matrix will be reset to - * the identity matrix. + * Copies the provided color matrix to be used by this filter. + * + * If the specified color matrix is null, this filter's color matrix will be reset to the + * identity matrix. * * @param array Array of floats used to transform colors, treated as a 4x5 * matrix. The first 20 entries of the array are copied into * the filter. See {@link ColorMatrix}. * - * @see #getColorMatrix() - * @see android.graphics.ColorMatrix#reset() + * @see #getColorMatrix(ColorMatrix) * @see #setColorMatrix(ColorMatrix) + * @see ColorMatrix#reset() * * @throws ArrayIndexOutOfBoundsException if the specified array's * length is < 20 - * - * @hide */ - public void setColorMatrix(float[] array) { + public void setColorMatrixArray(@Nullable float[] array) { + // called '...Array' so that passing null isn't ambiguous + discardNativeInstance(); if (array == null) { mMatrix.reset(); } else { if (array.length < 20) { throw new ArrayIndexOutOfBoundsException(); } - mMatrix.set(array); } - update(); } - private void update() { - final float[] colorMatrix = mMatrix.getArray(); - destroyFilter(native_instance); - native_instance = nativeColorMatrixFilter(colorMatrix); + @Override + long createNativeInstance() { + return nativeColorMatrixFilter(mMatrix.getArray()); } private static native long nativeColorMatrixFilter(float[] array); diff --git a/graphics/java/android/graphics/ColorSpace.java b/graphics/java/android/graphics/ColorSpace.java index 908ec5045b27..929ac22f2960 100644 --- a/graphics/java/android/graphics/ColorSpace.java +++ b/graphics/java/android/graphics/ColorSpace.java @@ -490,16 +490,16 @@ public abstract class ColorSpace { * <tr> * <td>Opto-electronic transfer function (OETF)</td> * <td colspan="4">\(\begin{equation} - * C_{sRGB} = \begin{cases} 12.92 \times C_{linear} & C_{linear} \lt 0.0031308 \\ - * 1.055 \times C_{linear}^{\frac{1}{2.4}} - 0.055 & C_{linear} \ge 0.0031308 \end{cases} + * C_{DisplayP3} = \begin{cases} 12.92 \times C_{linear} & C_{linear} \lt 0.0030186 \\ + * 1.055 \times C_{linear}^{\frac{1}{2.4}} - 0.055 & C_{linear} \ge 0.0030186 \end{cases} * \end{equation}\) * </td> * </tr> * <tr> * <td>Electro-optical transfer function (EOTF)</td> * <td colspan="4">\(\begin{equation} - * C_{linear} = \begin{cases}\frac{C_{sRGB}}{12.92} & C_{sRGB} \lt 0.04045 \\ - * \left( \frac{C_{sRGB} + 0.055}{1.055} \right) ^{2.4} & C_{sRGB} \ge 0.04045 \end{cases} + * C_{linear} = \begin{cases}\frac{C_{DisplayP3}}{12.92} & C_{sRGB} \lt 0.039 \\ + * \left( \frac{C_{DisplayP3} + 0.055}{1.055} \right) ^{2.4} & C_{sRGB} \ge 0.039 \end{cases} * \end{equation}\) * </td> * </tr> @@ -1482,7 +1482,7 @@ public abstract class ColorSpace { "Display P3", new float[] { 0.680f, 0.320f, 0.265f, 0.690f, 0.150f, 0.060f }, ILLUMINANT_D65, - new Rgb.TransferParameters(1 / 1.055, 0.055 / 1.055, 1 / 12.92, 0.04045, 2.4), + new Rgb.TransferParameters(1 / 1.055, 0.055 / 1.055, 1 / 12.92, 0.039, 2.4), Named.DISPLAY_P3.ordinal() ); sNamedColorSpaces[Named.NTSC_1953.ordinal()] = new ColorSpace.Rgb( diff --git a/graphics/java/android/graphics/FontFamily.java b/graphics/java/android/graphics/FontFamily.java index 317f232ef7ec..16fc2b143964 100644 --- a/graphics/java/android/graphics/FontFamily.java +++ b/graphics/java/android/graphics/FontFamily.java @@ -25,7 +25,6 @@ import java.io.FileInputStream; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; -import java.util.List; /** * A family of typefaces with different styles. @@ -48,14 +47,8 @@ public class FontFamily { mBuilderPtr = nInitBuilder(null, 0); } - public FontFamily(String lang, String variant) { - int varEnum = 0; - if ("compact".equals(variant)) { - varEnum = 1; - } else if ("elegant".equals(variant)) { - varEnum = 2; - } - mBuilderPtr = nInitBuilder(lang, varEnum); + public FontFamily(String lang, int variant) { + mBuilderPtr = nInitBuilder(lang, variant); } public void freeze() { @@ -103,12 +96,15 @@ public class FontFamily { } } - public boolean addFontWeightStyle(ByteBuffer font, int ttcIndex, List<FontConfig.Axis> axes, + public boolean addFontWeightStyle(ByteBuffer font, int ttcIndex, FontConfig.Axis[] axes, int weight, boolean style) { if (mBuilderPtr == 0) { throw new IllegalStateException("Unable to call addFontWeightStyle after freezing."); } - return nAddFontWeightStyle(mBuilderPtr, font, ttcIndex, axes, weight, style); + for (FontConfig.Axis axis : axes) { + nAddAxisValue(mBuilderPtr, axis.getTag(), axis.getStyleValue()); + } + return nAddFontWeightStyle(mBuilderPtr, font, ttcIndex, weight, style); } /** @@ -143,8 +139,11 @@ public class FontFamily { private static native void nUnrefFamily(long nativePtr); private static native boolean nAddFont(long builderPtr, ByteBuffer font, int ttcIndex); private static native boolean nAddFontWeightStyle(long builderPtr, ByteBuffer font, - int ttcIndex, List<FontConfig.Axis> listOfAxis, - int weight, boolean isItalic); + int ttcIndex, int weight, boolean isItalic); private static native boolean nAddFontFromAssetManager(long builderPtr, AssetManager mgr, String path, int cookie, boolean isAsset, int weight, boolean isItalic); + + // The added axis values are only valid for the next nAddFont* method call. + @CriticalNative + private static native void nAddAxisValue(long builderPtr, int tag, float value); } diff --git a/graphics/java/android/graphics/FontListParser.java b/graphics/java/android/graphics/FontListParser.java index b757842c4f3f..1b6969f96f82 100644 --- a/graphics/java/android/graphics/FontListParser.java +++ b/graphics/java/android/graphics/FontListParser.java @@ -116,20 +116,23 @@ public class FontListParser { private static FontConfig readFamilies(XmlPullParser parser) throws XmlPullParserException, IOException { - FontConfig config = new FontConfig(); + List<FontConfig.Family> families = new ArrayList<>(); + List<FontConfig.Alias> aliases = new ArrayList<>(); + parser.require(XmlPullParser.START_TAG, null, "familyset"); while (parser.next() != XmlPullParser.END_TAG) { if (parser.getEventType() != XmlPullParser.START_TAG) continue; String tag = parser.getName(); if (tag.equals("family")) { - config.getFamilies().add(readFamily(parser)); + families.add(readFamily(parser)); } else if (tag.equals("alias")) { - config.getAliases().add(readAlias(parser)); + aliases.add(readAlias(parser)); } else { skip(parser); } } - return config; + return new FontConfig(families.toArray(new FontConfig.Family[families.size()]), + aliases.toArray(new FontConfig.Alias[aliases.size()])); } private static FontConfig.Family readFamily(XmlPullParser parser) @@ -147,7 +150,16 @@ public class FontListParser { skip(parser); } } - return new FontConfig.Family(name, fonts, lang, variant); + int intVariant = FontConfig.Family.VARIANT_DEFAULT; + if (variant != null) { + if (variant.equals("compact")) { + intVariant = FontConfig.Family.VARIANT_COMPACT; + } else if (variant.equals("elegant")) { + intVariant = FontConfig.Family.VARIANT_ELEGANT; + } + } + return new FontConfig.Family(name, fonts.toArray(new FontConfig.Font[fonts.size()]), lang, + intVariant); } /** Matches leading and trailing XML whitespace. */ @@ -177,7 +189,8 @@ public class FontListParser { } String fullFilename = "/system/fonts/" + FILENAME_WHITESPACE_PATTERN.matcher(filename).replaceAll(""); - return new FontConfig.Font(fullFilename, index, axes, weight, isItalic); + return new FontConfig.Font(fullFilename, index, + axes.toArray(new FontConfig.Axis[axes.size()]), weight, isItalic); } /** The 'tag' attribute value is read as four character values between U+0020 and U+007E diff --git a/graphics/java/android/graphics/LightingColorFilter.java b/graphics/java/android/graphics/LightingColorFilter.java index ad78430e2472..b0c145beb649 100644 --- a/graphics/java/android/graphics/LightingColorFilter.java +++ b/graphics/java/android/graphics/LightingColorFilter.java @@ -21,6 +21,8 @@ package android.graphics; +import android.annotation.ColorInt; + /** * A color filter that can be used to simulate simple lighting effects. * A <code>LightingColorFilter</code> is defined by two parameters, one @@ -37,7 +39,9 @@ package android.graphics; * The result is pinned to the <code>[0..255]</code> range for each channel. */ public class LightingColorFilter extends ColorFilter { + @ColorInt private int mMul; + @ColorInt private int mAdd; /** @@ -45,10 +49,9 @@ public class LightingColorFilter extends ColorFilter { * and then adds a second color. The alpha components of the mul and add * arguments are ignored. */ - public LightingColorFilter(int mul, int add) { + public LightingColorFilter(@ColorInt int mul, @ColorInt int add) { mMul = mul; mAdd = add; - update(); } /** @@ -56,9 +59,8 @@ public class LightingColorFilter extends ColorFilter { * color filter is applied. * * @see #setColorMultiply(int) - * - * @hide */ + @ColorInt public int getColorMultiply() { return mMul; } @@ -69,12 +71,12 @@ public class LightingColorFilter extends ColorFilter { * The alpha channel of this color is ignored. * * @see #getColorMultiply() - * - * @hide */ - public void setColorMultiply(int mul) { - mMul = mul; - update(); + public void setColorMultiply(@ColorInt int mul) { + if (mMul != mul) { + mMul = mul; + discardNativeInstance(); + } } /** @@ -82,9 +84,8 @@ public class LightingColorFilter extends ColorFilter { * when the color filter is applied. * * @see #setColorAdd(int) - * - * @hide */ + @ColorInt public int getColorAdd() { return mAdd; } @@ -95,17 +96,17 @@ public class LightingColorFilter extends ColorFilter { * The alpha channel of this color is ignored. * * @see #getColorAdd() - * - * @hide */ - public void setColorAdd(int add) { - mAdd = add; - update(); + public void setColorAdd(@ColorInt int add) { + if (mAdd != add) { + mAdd = add; + discardNativeInstance(); + } } - private void update() { - destroyFilter(native_instance); - native_instance = native_CreateLightingFilter(mMul, mAdd); + @Override + long createNativeInstance() { + return native_CreateLightingFilter(mMul, mAdd); } private static native long native_CreateLightingFilter(int mul, int add); diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java index b1d51ec2d92b..5d6aa8a5f16d 100644 --- a/graphics/java/android/graphics/Paint.java +++ b/graphics/java/android/graphics/Paint.java @@ -42,7 +42,8 @@ import libcore.util.NativeAllocationRegistry; public class Paint { private long mNativePaint; - private long mNativeShader = 0; + private long mNativeShader; + private long mNativeColorFilter; // The approximate size of a native paint object. private static final long NATIVE_PAINT_SIZE = 98; @@ -584,6 +585,11 @@ public class Paint { mNativeShader = newNativeShader; nSetShader(mNativePaint, mNativeShader); } + long newNativeColorFilter = mColorFilter == null ? 0 : mColorFilter.getNativeInstance(); + if (newNativeColorFilter != mNativeColorFilter) { + mNativeColorFilter = newNativeColorFilter; + nSetColorFilter(mNativePaint, mNativeColorFilter); + } return mNativePaint; } @@ -1044,10 +1050,13 @@ public class Paint { * @return filter */ public ColorFilter setColorFilter(ColorFilter filter) { - long filterNative = 0; - if (filter != null) - filterNative = filter.native_instance; - nSetColorFilter(mNativePaint, filterNative); + // If mColorFilter changes, cached value of native shader aren't valid, since + // old shader's pointer may be reused by another shader allocation later + if (mColorFilter != filter) { + mNativeColorFilter = -1; + } + + // Defer setting the filter natively until getNativeInstance() is called mColorFilter = filter; return filter; } @@ -2294,7 +2303,7 @@ public class Paint { throw new IndexOutOfBoundsException(); } - return nGetTextRunCursor(mNativePaint, text, + return nGetTextRunCursor(mNativePaint, mNativeTypeface, text, contextStart, contextLength, dir, offset, cursorOpt); } @@ -2380,7 +2389,7 @@ public class Paint { throw new IndexOutOfBoundsException(); } - return nGetTextRunCursor(mNativePaint, text, + return nGetTextRunCursor(mNativePaint, mNativeTypeface, text, contextStart, contextEnd, dir, offset, cursorOpt); } @@ -2686,9 +2695,9 @@ public class Paint { private static native float nGetTextAdvances(long paintPtr, long typefacePtr, String text, int start, int end, int contextStart, int contextEnd, int bidiFlags, float[] advances, int advancesIndex); - private native int nGetTextRunCursor(long paintPtr, char[] text, + private native int nGetTextRunCursor(long paintPtr, long typefacePtr, char[] text, int contextStart, int contextLength, int dir, int offset, int cursorOpt); - private native int nGetTextRunCursor(long paintPtr, String text, + private native int nGetTextRunCursor(long paintPtr, long typefacePtr, String text, int contextStart, int contextEnd, int dir, int offset, int cursorOpt); private static native void nGetTextPath(long paintPtr, long typefacePtr, int bidiFlags, char[] text, int index, int count, float x, float y, long path); diff --git a/graphics/java/android/graphics/PorterDuffColorFilter.java b/graphics/java/android/graphics/PorterDuffColorFilter.java index 69d68910c8b5..ccc6eadc3df5 100644 --- a/graphics/java/android/graphics/PorterDuffColorFilter.java +++ b/graphics/java/android/graphics/PorterDuffColorFilter.java @@ -24,6 +24,7 @@ import android.annotation.NonNull; * color and a specific {@link PorterDuff Porter-Duff composite mode}. */ public class PorterDuffColorFilter extends ColorFilter { + @ColorInt private int mColor; private PorterDuff.Mode mMode; @@ -40,7 +41,6 @@ public class PorterDuffColorFilter extends ColorFilter { public PorterDuffColorFilter(@ColorInt int color, @NonNull PorterDuff.Mode mode) { mColor = color; mMode = mode; - update(); } /** @@ -49,9 +49,8 @@ public class PorterDuffColorFilter extends ColorFilter { * * @see Color * @see #setColor(int) - * - * @hide */ + @ColorInt public int getColor() { return mColor; } @@ -65,12 +64,12 @@ public class PorterDuffColorFilter extends ColorFilter { * @see Color * @see #getColor() * @see #getMode() - * - * @hide */ - public void setColor(int color) { - mColor = color; - update(); + public void setColor(@ColorInt int color) { + if (mColor != color) { + mColor = color; + discardNativeInstance(); + } } /** @@ -79,8 +78,6 @@ public class PorterDuffColorFilter extends ColorFilter { * * @see PorterDuff * @see #setMode(android.graphics.PorterDuff.Mode) - * - * @hide */ public PorterDuff.Mode getMode() { return mMode; @@ -93,17 +90,18 @@ public class PorterDuffColorFilter extends ColorFilter { * @see PorterDuff * @see #getMode() * @see #getColor() - * - * @hide */ public void setMode(@NonNull PorterDuff.Mode mode) { + if (mode == null) { + throw new IllegalArgumentException("mode must be non-null"); + } mMode = mode; - update(); + discardNativeInstance(); } - private void update() { - destroyFilter(native_instance); - native_instance = native_CreatePorterDuffFilter(mColor, mMode.nativeInt); + @Override + long createNativeInstance() { + return native_CreatePorterDuffFilter(mColor, mMode.nativeInt); } @Override @@ -115,10 +113,7 @@ public class PorterDuffColorFilter extends ColorFilter { return false; } final PorterDuffColorFilter other = (PorterDuffColorFilter) object; - if (mColor != other.mColor || mMode != other.mMode) { - return false; - } - return true; + return (mColor == other.mColor && mMode.nativeInt == other.mMode.nativeInt); } @Override diff --git a/graphics/java/android/graphics/Shader.java b/graphics/java/android/graphics/Shader.java index b584e0dd0417..8410ab2a1e02 100644 --- a/graphics/java/android/graphics/Shader.java +++ b/graphics/java/android/graphics/Shader.java @@ -106,8 +106,10 @@ public class Shader { } void discardNativeInstance() { - nativeSafeUnref(mNativeInstance); - mNativeInstance = 0; + if (mNativeInstance != 0) { + nativeSafeUnref(mNativeInstance); + mNativeInstance = 0; + } } /** @@ -120,7 +122,9 @@ public class Shader { @Override protected void finalize() throws Throwable { try { - nativeSafeUnref(mNativeInstance); + if (mNativeInstance != 0) { + nativeSafeUnref(mNativeInstance); + } mNativeInstance = -1; } finally { super.finalize(); diff --git a/graphics/java/android/graphics/Typeface.java b/graphics/java/android/graphics/Typeface.java index 6de19cb0f3b2..341640146d02 100644 --- a/graphics/java/android/graphics/Typeface.java +++ b/graphics/java/android/graphics/Typeface.java @@ -16,9 +16,15 @@ package android.graphics; +import static android.content.res.FontResourcesParser.ProviderResourceEntry; +import static android.content.res.FontResourcesParser.FontFileResourceEntry; +import static android.content.res.FontResourcesParser.FontFamilyFilesResourceEntry; +import static android.content.res.FontResourcesParser.FamilyResourceEntry; + import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.content.Context; import android.content.res.AssetManager; import android.graphics.fonts.FontRequest; import android.graphics.fonts.FontResult; @@ -83,7 +89,7 @@ public class Typeface { @GuardedBy("sLock") private static FontsContract sFontsContract; @GuardedBy("sLock") - private static Handler mHandler; + private static Handler sHandler; /** * Cache for Typeface objects dynamically loaded from assets. Currently max size is 16. @@ -161,46 +167,35 @@ public class Typeface { * Used by Resources to load a font resource of type xml. */ @Nullable - public static Typeface createFromResources(FontConfig config, AssetManager mgr, String path) { + public static Typeface createFromResources( + FamilyResourceEntry entry, AssetManager mgr, String path) { if (sFallbackFonts != null) { Typeface typeface = findFromCache(mgr, path); if (typeface != null) return typeface; - List<FontConfig.Family> families = config.getFamilies(); - if (families == null || families.isEmpty()) { - throw new RuntimeException( - "Font resource " + path + " contained no font families."); - } - if (families.size() > 1) { - throw new RuntimeException( - "Font resource " + path + " contained more than one family."); - } - FontConfig.Family family = families.get(0); - if (family.getProviderAuthority() != null && family.getQuery() != null) { + if (entry instanceof ProviderResourceEntry) { + final ProviderResourceEntry providerEntry = (ProviderResourceEntry) entry; // Downloadable font - typeface = findFromCache( - family.getProviderAuthority(), family.getQuery()); + typeface = findFromCache(providerEntry.getAuthority(), providerEntry.getQuery()); if (typeface != null) { return typeface; } // Downloaded font and it wasn't cached, request it again and return a // default font instead (nothing we can do now). - create(new FontRequest(family.getProviderAuthority(), family.getProviderPackage(), - family.getQuery()), NO_OP_REQUEST_CALLBACK); + create(new FontRequest(providerEntry.getAuthority(), providerEntry.getPackage(), + providerEntry.getQuery()), NO_OP_REQUEST_CALLBACK); return DEFAULT; } + // family is FontFamilyFilesResourceEntry + final FontFamilyFilesResourceEntry filesEntry = + (FontFamilyFilesResourceEntry) entry; + FontFamily fontFamily = new FontFamily(); - List<FontConfig.Font> fonts = family.getFonts(); - if (fonts == null || fonts.isEmpty()) { - throw new RuntimeException("Font resource " + path + " contained no fonts."); - } - for (int i = 0; i < fonts.size(); i++) { - FontConfig.Font font = fonts.get(i); - // TODO: Use style and weight info - if (!fontFamily.addFontFromAssetManager(mgr, font.getFontName(), - 0 /* resourceCookie */, false /* isAsset */, font.getWeight(), - font.isItalic())) { + for (final FontFileResourceEntry fontFile : filesEntry.getEntries()) { + if (!fontFamily.addFontFromAssetManager(mgr, fontFile.getFileName(), + 0 /* resourceCookie */, false /* isAsset */, fontFile.getWeight(), + fontFile.isItalic())) { return null; } } @@ -232,6 +227,20 @@ public class Typeface { } /** + * Set the application context so we can generate font requests from the provider. This should + * be called from ActivityThread when the application binds, as we preload fonts. + * @hide + */ + public static void setApplicationContext(Context context) { + synchronized (sLock) { + if (sFontsContract == null) { + sFontsContract = new FontsContract(context); + sHandler = new Handler(); + } + } + } + + /** * Create a typeface object given a font request. The font will be asynchronously fetched, * therefore the result is delivered to the given callback. See {@link FontRequest}. * Only one of the methods in callback will be invoked, depending on whether the request @@ -247,18 +256,17 @@ public class Typeface { Typeface cachedTypeface = findFromCache( request.getProviderAuthority(), request.getQuery()); if (cachedTypeface != null) { - mHandler.post(() -> callback.onTypefaceRetrieved(cachedTypeface)); + sHandler.post(() -> callback.onTypefaceRetrieved(cachedTypeface)); return; } synchronized (sLock) { if (sFontsContract == null) { - sFontsContract = new FontsContract(); - mHandler = new Handler(); + throw new RuntimeException("Context not initialized, can't query provider"); } final ResultReceiver receiver = new ResultReceiver(null) { @Override public void onReceiveResult(int resultCode, Bundle resultData) { - mHandler.post(() -> receiveResult(request, callback, resultCode, resultData)); + sHandler.post(() -> receiveResult(request, callback, resultCode, resultData)); } }; sFontsContract.getFont(request, receiver); @@ -677,8 +685,8 @@ public class Typeface { List<FontFamily> familyList = new ArrayList<FontFamily>(); // Note that the default typeface is always present in the fallback list; // this is an enhancement from pre-Minikin behavior. - for (int i = 0; i < fontConfig.getFamilies().size(); i++) { - FontConfig.Family f = fontConfig.getFamilies().get(i); + for (int i = 0; i < fontConfig.getFamilies().length; i++) { + FontConfig.Family f = fontConfig.getFamilies()[i]; if (i == 0 || f.getName() == null) { familyList.add(makeFamilyFromParsed(f, bufferForPath)); } @@ -687,9 +695,9 @@ public class Typeface { setDefault(Typeface.createFromFamilies(sFallbackFonts)); Map<String, Typeface> systemFonts = new HashMap<String, Typeface>(); - for (int i = 0; i < fontConfig.getFamilies().size(); i++) { + for (int i = 0; i < fontConfig.getFamilies().length; i++) { Typeface typeface; - FontConfig.Family f = fontConfig.getFamilies().get(i); + FontConfig.Family f = fontConfig.getFamilies()[i]; if (f.getName() != null) { if (i == 0) { // The first entry is the default typeface; no sense in diff --git a/graphics/java/android/graphics/drawable/AdaptiveIconDrawable.java b/graphics/java/android/graphics/drawable/AdaptiveIconDrawable.java index 0722c1856372..c6c92714d1cc 100644 --- a/graphics/java/android/graphics/drawable/AdaptiveIconDrawable.java +++ b/graphics/java/android/graphics/drawable/AdaptiveIconDrawable.java @@ -218,6 +218,8 @@ public class AdaptiveIconDrawable extends Drawable implements Drawable.Callback } /** + * Only call this method after bound is set on this drawable. + * * @return the mask path object used to clip the drawable */ public Path getIconMask() { diff --git a/graphics/java/android/graphics/drawable/VectorDrawable.java b/graphics/java/android/graphics/drawable/VectorDrawable.java index 3a12419c8774..a1539b8f805a 100644 --- a/graphics/java/android/graphics/drawable/VectorDrawable.java +++ b/graphics/java/android/graphics/drawable/VectorDrawable.java @@ -333,7 +333,7 @@ public class VectorDrawable extends Drawable { // Color filters always override tint filters. final ColorFilter colorFilter = (mColorFilter == null ? mTintFilter : mColorFilter); final long colorFilterNativeInstance = colorFilter == null ? 0 : - colorFilter.native_instance; + colorFilter.getNativeInstance(); boolean canReuseCache = mVectorState.canReuseCache(); int pixelCount = nDraw(mVectorState.getNativeRenderer(), canvas.getNativeCanvasWrapper(), colorFilterNativeInstance, mTmpBounds, needMirroring(), diff --git a/libs/common_time/Android.mk b/libs/common_time/Android.mk index 1fec504e7969..636f05752ee4 100644 --- a/libs/common_time/Android.mk +++ b/libs/common_time/Android.mk @@ -15,7 +15,8 @@ LOCAL_SRC_FILES := \ clock_recovery.cpp \ common_clock.cpp \ main.cpp \ - utils.cpp + utils.cpp \ + LinearTransform.cpp # Uncomment to enable vesbose logging and debug service. #TIME_SERVICE_DEBUG=true diff --git a/libs/common_time/LinearTransform.cpp b/libs/common_time/LinearTransform.cpp new file mode 100644 index 000000000000..67308550fa4b --- /dev/null +++ b/libs/common_time/LinearTransform.cpp @@ -0,0 +1,281 @@ +/* + * Copyright (C) 2011 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. + */ + +#define __STDC_LIMIT_MACROS + +#include "LinearTransform.h" +#include <assert.h> + + +// disable sanitize as these functions may intentionally overflow (see comments below). +// the ifdef can be removed when host builds use clang. +#if defined(__clang__) +#define ATTRIBUTE_NO_SANITIZE_INTEGER __attribute__((no_sanitize("integer"))) +#else +#define ATTRIBUTE_NO_SANITIZE_INTEGER +#endif + +namespace android { + +// sanitize failure with T = int32_t and x = 0x80000000 +template<class T> +ATTRIBUTE_NO_SANITIZE_INTEGER +static inline T ABS(T x) { return (x < 0) ? -x : x; } + +// Static math methods involving linear transformations +// remote sanitize failure on overflow case. +ATTRIBUTE_NO_SANITIZE_INTEGER +static bool scale_u64_to_u64( + uint64_t val, + uint32_t N, + uint32_t D, + uint64_t* res, + bool round_up_not_down) { + uint64_t tmp1, tmp2; + uint32_t r; + + assert(res); + assert(D); + + // Let U32(X) denote a uint32_t containing the upper 32 bits of a 64 bit + // integer X. + // Let L32(X) denote a uint32_t containing the lower 32 bits of a 64 bit + // integer X. + // Let X[A, B] with A <= B denote bits A through B of the integer X. + // Let (A | B) denote the concatination of two 32 bit ints, A and B. + // IOW X = (A | B) => U32(X) == A && L32(X) == B + // + // compute M = val * N (a 96 bit int) + // --------------------------------- + // tmp2 = U32(val) * N (a 64 bit int) + // tmp1 = L32(val) * N (a 64 bit int) + // which means + // M = val * N = (tmp2 << 32) + tmp1 + tmp2 = (val >> 32) * N; + tmp1 = (val & UINT32_MAX) * N; + + // compute M[32, 95] + // tmp2 = tmp2 + U32(tmp1) + // = (U32(val) * N) + U32(L32(val) * N) + // = M[32, 95] + tmp2 += tmp1 >> 32; + + // if M[64, 95] >= D, then M/D has bits > 63 set and we have + // an overflow. + if ((tmp2 >> 32) >= D) { + *res = UINT64_MAX; + return false; + } + + // Divide. Going in we know + // tmp2 = M[32, 95] + // U32(tmp2) < D + r = tmp2 % D; + tmp2 /= D; + + // At this point + // tmp1 = L32(val) * N + // tmp2 = M[32, 95] / D + // = (M / D)[32, 95] + // r = M[32, 95] % D + // U32(tmp2) = 0 + // + // compute tmp1 = (r | M[0, 31]) + tmp1 = (tmp1 & UINT32_MAX) | ((uint64_t)r << 32); + + // Divide again. Keep the remainder around in order to round properly. + r = tmp1 % D; + tmp1 /= D; + + // At this point + // tmp2 = (M / D)[32, 95] + // tmp1 = (M / D)[ 0, 31] + // r = M % D + // U32(tmp1) = 0 + // U32(tmp2) = 0 + + // Pack the result and deal with the round-up case (As well as the + // remote possiblility over overflow in such a case). + *res = (tmp2 << 32) | tmp1; + if (r && round_up_not_down) { + ++(*res); + if (!(*res)) { + *res = UINT64_MAX; + return false; + } + } + + return true; +} + +// at least one known sanitize failure (see comment below) +ATTRIBUTE_NO_SANITIZE_INTEGER +static bool linear_transform_s64_to_s64( + int64_t val, + int64_t basis1, + int32_t N, + uint32_t D, + bool invert_frac, + int64_t basis2, + int64_t* out) { + uint64_t scaled, res; + uint64_t abs_val; + bool is_neg; + + if (!out) + return false; + + // Compute abs(val - basis_64). Keep track of whether or not this delta + // will be negative after the scale opertaion. + if (val < basis1) { + is_neg = true; + abs_val = basis1 - val; + } else { + is_neg = false; + abs_val = val - basis1; + } + + if (N < 0) + is_neg = !is_neg; + + if (!scale_u64_to_u64(abs_val, + invert_frac ? D : ABS(N), + invert_frac ? ABS(N) : D, + &scaled, + is_neg)) + return false; // overflow/undeflow + + // if scaled is >= 0x8000<etc>, then we are going to overflow or + // underflow unless ABS(basis2) is large enough to pull us back into the + // non-overflow/underflow region. + if (scaled & INT64_MIN) { + if (is_neg && (basis2 < 0)) + return false; // certain underflow + + if (!is_neg && (basis2 >= 0)) + return false; // certain overflow + + if (ABS(basis2) <= static_cast<int64_t>(scaled & INT64_MAX)) + return false; // not enough + + // Looks like we are OK + *out = (is_neg ? (-scaled) : scaled) + basis2; + } else { + // Scaled fits within signed bounds, so we just need to check for + // over/underflow for two signed integers. Basically, if both scaled + // and basis2 have the same sign bit, and the result has a different + // sign bit, then we have under/overflow. An easy way to compute this + // is + // (scaled_signbit XNOR basis_signbit) && + // (scaled_signbit XOR res_signbit) + // == + // (scaled_signbit XOR basis_signbit XOR 1) && + // (scaled_signbit XOR res_signbit) + + if (is_neg) + scaled = -scaled; // known sanitize failure + res = scaled + basis2; + + if ((scaled ^ basis2 ^ INT64_MIN) & (scaled ^ res) & INT64_MIN) + return false; + + *out = res; + } + + return true; +} + +bool LinearTransform::doForwardTransform(int64_t a_in, int64_t* b_out) const { + if (0 == a_to_b_denom) + return false; + + return linear_transform_s64_to_s64(a_in, + a_zero, + a_to_b_numer, + a_to_b_denom, + false, + b_zero, + b_out); +} + +bool LinearTransform::doReverseTransform(int64_t b_in, int64_t* a_out) const { + if (0 == a_to_b_numer) + return false; + + return linear_transform_s64_to_s64(b_in, + b_zero, + a_to_b_numer, + a_to_b_denom, + true, + a_zero, + a_out); +} + +template <class T> void LinearTransform::reduce(T* N, T* D) { + T a, b; + if (!N || !D || !(*D)) { + assert(false); + return; + } + + a = *N; + b = *D; + + if (a == 0) { + *D = 1; + return; + } + + // This implements Euclid's method to find GCD. + if (a < b) { + T tmp = a; + a = b; + b = tmp; + } + + while (1) { + // a is now the greater of the two. + const T remainder = a % b; + if (remainder == 0) { + *N /= b; + *D /= b; + return; + } + // by swapping remainder and b, we are guaranteeing that a is + // still the greater of the two upon entrance to the loop. + a = b; + b = remainder; + } +}; + +template void LinearTransform::reduce<uint64_t>(uint64_t* N, uint64_t* D); +template void LinearTransform::reduce<uint32_t>(uint32_t* N, uint32_t* D); + +// sanitize failure if *N = 0x80000000 +ATTRIBUTE_NO_SANITIZE_INTEGER +void LinearTransform::reduce(int32_t* N, uint32_t* D) { + if (N && D && *D) { + if (*N < 0) { + *N = -(*N); + reduce(reinterpret_cast<uint32_t*>(N), D); + *N = -(*N); + } else { + reduce(reinterpret_cast<uint32_t*>(N), D); + } + } +} + +} // namespace android diff --git a/libs/common_time/LinearTransform.h b/libs/common_time/LinearTransform.h new file mode 100644 index 000000000000..bf6ab8e78eb8 --- /dev/null +++ b/libs/common_time/LinearTransform.h @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2011 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. + */ + +#ifndef _LINEAR_TRANSFORM_H +#define _LINEAR_TRANSFORM_H + +#include <stdint.h> + +namespace android { + +// LinearTransform defines a structure which hold the definition of a +// transformation from single dimensional coordinate system A into coordinate +// system B (and back again). Values in A and in B are 64 bit, the linear +// scale factor is expressed as a rational number using two 32 bit values. +// +// Specifically, let +// f(a) = b +// F(b) = f^-1(b) = a +// then +// +// f(a) = (((a - a_zero) * a_to_b_numer) / a_to_b_denom) + b_zero; +// +// and +// +// F(b) = (((b - b_zero) * a_to_b_denom) / a_to_b_numer) + a_zero; +// +struct LinearTransform { + int64_t a_zero; + int64_t b_zero; + int32_t a_to_b_numer; + uint32_t a_to_b_denom; + + // Transform from A->B + // Returns true on success, or false in the case of a singularity or an + // overflow. + bool doForwardTransform(int64_t a_in, int64_t* b_out) const; + + // Transform from B->A + // Returns true on success, or false in the case of a singularity or an + // overflow. + bool doReverseTransform(int64_t b_in, int64_t* a_out) const; + + // Helpers which will reduce the fraction N/D using Euclid's method. + template <class T> static void reduce(T* N, T* D); + static void reduce(int32_t* N, uint32_t* D); +}; + + +} + +#endif // _LINEAR_TRANSFORM_H diff --git a/libs/common_time/clock_recovery.h b/libs/common_time/clock_recovery.h index 278a75ef2b05..8066a3968b59 100644 --- a/libs/common_time/clock_recovery.h +++ b/libs/common_time/clock_recovery.h @@ -19,9 +19,10 @@ #include <stdint.h> #include <common_time/ICommonClock.h> -#include <utils/LinearTransform.h> #include <utils/threads.h> +#include "LinearTransform.h" + #ifdef TIME_SERVICE_DEBUG #include "diag_thread.h" #endif diff --git a/libs/common_time/common_clock.cpp b/libs/common_time/common_clock.cpp index ee326e14d12b..aed52f177c80 100644 --- a/libs/common_time/common_clock.cpp +++ b/libs/common_time/common_clock.cpp @@ -23,7 +23,6 @@ #include <stdint.h> #include <utils/Errors.h> -#include <utils/LinearTransform.h> #include "common_clock.h" diff --git a/libs/common_time/common_clock.h b/libs/common_time/common_clock.h index b786fdceba5f..5e4e5f51b446 100644 --- a/libs/common_time/common_clock.h +++ b/libs/common_time/common_clock.h @@ -20,9 +20,10 @@ #include <stdint.h> #include <utils/Errors.h> -#include <utils/LinearTransform.h> #include <utils/threads.h> +#include "LinearTransform.h" + namespace android { class CommonClock { diff --git a/libs/hwui/FloatColor.h b/libs/hwui/FloatColor.h index d8afa35d32bf..a738ba4fe97e 100644 --- a/libs/hwui/FloatColor.h +++ b/libs/hwui/FloatColor.h @@ -38,13 +38,14 @@ struct FloatColor { } // "color" is a gamma-encoded sRGB color - // After calling this method, the color is stored as a linear color. The color - // is not pre-multiplied. - void setUnPreMultipliedSRGB(uint32_t color) { + // After calling this method, the color is stored as a un-premultiplied linear color + // if linear blending is enabled. Otherwise, the color is stored as a un-premultiplied + // gamma-encoded sRGB color + void setUnPreMultiplied(uint32_t color) { a = ((color >> 24) & 0xff) / 255.0f; - r = EOCF_sRGB(((color >> 16) & 0xff) / 255.0f); - g = EOCF_sRGB(((color >> 8) & 0xff) / 255.0f); - b = EOCF_sRGB(((color ) & 0xff) / 255.0f); + r = EOCF(((color >> 16) & 0xff) / 255.0f); + g = EOCF(((color >> 8) & 0xff) / 255.0f); + b = EOCF(((color ) & 0xff) / 255.0f); } bool isNotBlack() { diff --git a/libs/hwui/GradientCache.cpp b/libs/hwui/GradientCache.cpp index 18bfcc2bbddf..dceb28518db3 100644 --- a/libs/hwui/GradientCache.cpp +++ b/libs/hwui/GradientCache.cpp @@ -189,9 +189,9 @@ void GradientCache::mixBytes(const FloatColor& start, const FloatColor& end, float amount, uint8_t*& dst) const { float oppAmount = 1.0f - amount; float a = start.a * oppAmount + end.a * amount; - *dst++ = uint8_t(a * OECF_sRGB((start.r * oppAmount + end.r * amount)) * 255.0f); - *dst++ = uint8_t(a * OECF_sRGB((start.g * oppAmount + end.g * amount)) * 255.0f); - *dst++ = uint8_t(a * OECF_sRGB((start.b * oppAmount + end.b * amount)) * 255.0f); + *dst++ = uint8_t(a * OECF(start.r * oppAmount + end.r * amount) * 255.0f); + *dst++ = uint8_t(a * OECF(start.g * oppAmount + end.g * amount) * 255.0f); + *dst++ = uint8_t(a * OECF(start.b * oppAmount + end.b * amount) * 255.0f); *dst++ = uint8_t(a * 255.0f); } @@ -201,17 +201,14 @@ void GradientCache::mixFloats(const FloatColor& start, const FloatColor& end, float a = start.a * oppAmount + end.a * amount; float* d = (float*) dst; #ifdef ANDROID_ENABLE_LINEAR_BLENDING + // We want to stay linear *d++ = a * (start.r * oppAmount + end.r * amount); *d++ = a * (start.g * oppAmount + end.g * amount); *d++ = a * (start.b * oppAmount + end.b * amount); #else - // What we're doing to the alpha channel here is technically incorrect - // but reproduces Android's old behavior when the alpha was pre-multiplied - // with gamma-encoded colors - a = EOCF_sRGB(a); - *d++ = a * OECF_sRGB(start.r * oppAmount + end.r * amount); - *d++ = a * OECF_sRGB(start.g * oppAmount + end.g * amount); - *d++ = a * OECF_sRGB(start.b * oppAmount + end.b * amount); + *d++ = a * OECF(start.r * oppAmount + end.r * amount); + *d++ = a * OECF(start.g * oppAmount + end.g * amount); + *d++ = a * OECF(start.b * oppAmount + end.b * amount); #endif *d++ = a; dst += 4 * sizeof(float); @@ -232,10 +229,10 @@ void GradientCache::generateTexture(uint32_t* colors, float* positions, ChannelMixer mix = gMixers[mUseFloatTexture]; FloatColor start; - start.setUnPreMultipliedSRGB(colors[0]); + start.setUnPreMultiplied(colors[0]); FloatColor end; - end.setUnPreMultipliedSRGB(colors[1]); + end.setUnPreMultiplied(colors[1]); int currentPos = 1; float startPos = positions[0]; @@ -250,7 +247,7 @@ void GradientCache::generateTexture(uint32_t* colors, float* positions, currentPos++; - end.setUnPreMultipliedSRGB(colors[currentPos]); + end.setUnPreMultiplied(colors[currentPos]); distance = positions[currentPos] - startPos; } diff --git a/libs/hwui/ProgramCache.cpp b/libs/hwui/ProgramCache.cpp index 40ab7788f218..38c23e4babe8 100644 --- a/libs/hwui/ProgramCache.cpp +++ b/libs/hwui/ProgramCache.cpp @@ -190,7 +190,7 @@ const char* gFS_Transfer_Functions = R"__SHADER__( // Dithering must be done in the quantization space // When we are writing to an sRGB framebuffer, we must do the following: // EOTF(OETF(color) + dither) -// The dithering pattern is generated with a triangle noise generator in the range [-0.0,1.0] +// The dithering pattern is generated with a triangle noise generator in the range [-1.0,1.0] // TODO: Handle linear fp16 render targets const char* gFS_Gradient_Functions = R"__SHADER__( float triangleNoise(const highp vec2 n) { @@ -202,23 +202,26 @@ const char* gFS_Gradient_Functions = R"__SHADER__( )__SHADER__"; const char* gFS_Gradient_Preamble[2] = { // Linear framebuffer - "\nvec4 dither(const vec4 color) {\n" - " return vec4(color.rgb + (triangleNoise(gl_FragCoord.xy * screenSize.xy) / 255.0), color.a);\n" - "}\n" - "\nvec4 gammaMix(const vec4 a, const vec4 b, float v) {\n" - " vec4 c = mix(a, b, v);\n" - " c.a = EOTF_sRGB(c.a);\n" // This is technically incorrect but preserves compatibility - " return vec4(OETF_sRGB(c.rgb) * c.a, c.a);\n" - "}\n", + R"__SHADER__( + vec4 dither(const vec4 color) { + return color + (triangleNoise(gl_FragCoord.xy * screenSize.xy) / 255.0); + } + vec4 gradientMix(const vec4 a, const vec4 b, float v) { + vec4 c = mix(a, b, v); + return vec4(c.rgb * c.a, c.a); + } + )__SHADER__", // sRGB framebuffer - "\nvec4 dither(const vec4 color) {\n" - " vec3 dithered = sqrt(color.rgb) + (triangleNoise(gl_FragCoord.xy * screenSize.xy) / 255.0);\n" - " return vec4(dithered * dithered, color.a);\n" - "}\n" - "\nvec4 gammaMix(const vec4 a, const vec4 b, float v) {\n" - " vec4 c = mix(a, b, v);\n" - " return vec4(c.rgb * c.a, c.a);\n" - "}\n" + R"__SHADER__( + vec4 dither(const vec4 color) { + vec3 dithered = sqrt(color.rgb) + (triangleNoise(gl_FragCoord.xy * screenSize.xy) / 255.0); + return vec4(dithered * dithered, color.a); + } + vec4 gradientMixMix(const vec4 a, const vec4 b, float v) { + vec4 c = mix(a, b, v); + return vec4(c.rgb * c.a, c.a); + } + )__SHADER__", }; // Uses luminance coefficients from Rec.709 to choose the appropriate gamma @@ -272,19 +275,19 @@ const char* gFS_Main_FetchGradient[6] = { // Linear " vec4 gradientColor = texture2D(gradientSampler, linear);\n", - " vec4 gradientColor = gammaMix(startColor, endColor, clamp(linear, 0.0, 1.0));\n", + " vec4 gradientColor = gradientMix(startColor, endColor, clamp(linear, 0.0, 1.0));\n", // Circular " vec4 gradientColor = texture2D(gradientSampler, vec2(length(circular), 0.5));\n", - " vec4 gradientColor = gammaMix(startColor, endColor, clamp(length(circular), 0.0, 1.0));\n", + " vec4 gradientColor = gradientMix(startColor, endColor, clamp(length(circular), 0.0, 1.0));\n", // Sweep " highp float index = atan(sweep.y, sweep.x) * 0.15915494309; // inv(2 * PI)\n" " vec4 gradientColor = texture2D(gradientSampler, vec2(index - floor(index), 0.5));\n", " highp float index = atan(sweep.y, sweep.x) * 0.15915494309; // inv(2 * PI)\n" - " vec4 gradientColor = gammaMix(startColor, endColor, clamp(index - floor(index), 0.0, 1.0));\n" + " vec4 gradientColor = gradientMix(startColor, endColor, clamp(index - floor(index), 0.0, 1.0));\n" }; const char* gFS_Main_FetchBitmap = " vec4 bitmapColor = OETF(texture2D(bitmapSampler, outBitmapTexCoords));\n"; diff --git a/libs/hwui/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp index 363aa8380fa2..812e4d885b39 100644 --- a/libs/hwui/SkiaCanvas.cpp +++ b/libs/hwui/SkiaCanvas.cpp @@ -73,9 +73,39 @@ void SkiaCanvas::reset(SkCanvas* skiaCanvas) { // Canvas state operations: Replace Bitmap // ---------------------------------------------------------------------------- +class ClipCopier : public SkCanvas::ClipVisitor { +public: + explicit ClipCopier(SkCanvas* dstCanvas) : m_dstCanvas(dstCanvas) {} + + virtual void clipRect(const SkRect& rect, SkClipOp op, bool antialias) { + m_dstCanvas->clipRect(rect, op, antialias); + } + virtual void clipRRect(const SkRRect& rrect, SkClipOp op, bool antialias) { + m_dstCanvas->clipRRect(rrect, op, antialias); + } + virtual void clipPath(const SkPath& path, SkClipOp op, bool antialias) { + m_dstCanvas->clipPath(path, op, antialias); + } + +private: + SkCanvas* m_dstCanvas; +}; + void SkiaCanvas::setBitmap(const SkBitmap& bitmap) { - mCanvasOwned.reset(new SkCanvas(bitmap)); - mCanvas = mCanvasOwned.get(); + SkCanvas* newCanvas = new SkCanvas(bitmap); + + if (!bitmap.isNull()) { + // Copy the canvas matrix & clip state. + newCanvas->setMatrix(mCanvas->getTotalMatrix()); + + ClipCopier copier(newCanvas); + mCanvas->replayClips(&copier); + } + + // deletes the previously owned canvas (if any) + mCanvasOwned = std::unique_ptr<SkCanvas>(newCanvas); + mCanvas = newCanvas; + // clean up the old save stack mSaveStack.reset(nullptr); } diff --git a/libs/hwui/SkiaShader.cpp b/libs/hwui/SkiaShader.cpp index 760c10c6130e..4f7f9d7f9b9a 100644 --- a/libs/hwui/SkiaShader.cpp +++ b/libs/hwui/SkiaShader.cpp @@ -173,8 +173,8 @@ bool tryStoreGradient(Caches& caches, const SkShader& shader, const Matrix4 mode outData->gradientSampler = 0; outData->gradientTexture = nullptr; - outData->startColor.setUnPreMultipliedSRGB(gradInfo.fColors[0]); - outData->endColor.setUnPreMultipliedSRGB(gradInfo.fColors[1]); + outData->startColor.setUnPreMultiplied(gradInfo.fColors[0]); + outData->endColor.setUnPreMultiplied(gradInfo.fColors[1]); } return true; diff --git a/libs/hwui/VkLayer.cpp b/libs/hwui/VkLayer.cpp index 537b3eaa3ad5..ef4784b3d65b 100644 --- a/libs/hwui/VkLayer.cpp +++ b/libs/hwui/VkLayer.cpp @@ -29,7 +29,7 @@ void VkLayer::updateTexture() { SkImageInfo info = SkImageInfo::MakeS32(mWidth, mHeight, kPremul_SkAlphaType); surface = SkSurface::MakeRenderTarget(mRenderState.getGrContext(), SkBudgeted::kNo, info); surface->getCanvas()->clear(SK_ColorBLUE); - mImage = surface->makeImageSnapshot(SkBudgeted::kNo); + mImage = surface->makeImageSnapshot(); } void VkLayer::onVkContextDestroyed() { diff --git a/libs/hwui/hwui/MinikinUtils.cpp b/libs/hwui/hwui/MinikinUtils.cpp index d1871ffaf92b..415eef77f44e 100644 --- a/libs/hwui/hwui/MinikinUtils.cpp +++ b/libs/hwui/hwui/MinikinUtils.cpp @@ -58,8 +58,9 @@ minikin::Layout MinikinUtils::doLayout(const Paint* paint, int bidiFlags, size_t bufSize) { minikin::MinikinPaint minikinPaint; minikin::FontStyle minikinStyle = prepareMinikinPaint(&minikinPaint, paint, typeface); - minikin::Layout layout(Typeface::resolveDefault(typeface)->fFontCollection); - layout.doLayout(buf, start, count, bufSize, bidiFlags, minikinStyle, minikinPaint); + minikin::Layout layout; + layout.doLayout(buf, start, count, bufSize, bidiFlags, minikinStyle, minikinPaint, + Typeface::resolveDefault(typeface)->fFontCollection); return layout; } diff --git a/media/java/android/media/AudioRecord.java b/media/java/android/media/AudioRecord.java index 39184f1af878..0906ba50f7df 100644 --- a/media/java/android/media/AudioRecord.java +++ b/media/java/android/media/AudioRecord.java @@ -677,7 +677,7 @@ public class AudioRecord implements AudioRouting ((audioSource > MediaRecorder.getAudioSourceMax()) && (audioSource != MediaRecorder.AudioSource.RADIO_TUNER) && (audioSource != MediaRecorder.AudioSource.HOTWORD)) ) { - throw new IllegalArgumentException("Invalid audio source."); + throw new IllegalArgumentException("Invalid audio source " + audioSource); } mRecordSource = audioSource; @@ -703,8 +703,8 @@ public class AudioRecord implements AudioRouting mAudioFormat = audioFormat; break; default: - throw new IllegalArgumentException("Unsupported sample encoding." - + " Should be ENCODING_PCM_8BIT, ENCODING_PCM_16BIT, or ENCODING_PCM_FLOAT."); + throw new IllegalArgumentException("Unsupported sample encoding " + audioFormat + + ". Should be ENCODING_PCM_8BIT, ENCODING_PCM_16BIT, or ENCODING_PCM_FLOAT."); } } @@ -722,7 +722,8 @@ public class AudioRecord implements AudioRouting int frameSizeInBytes = mChannelCount * (AudioFormat.getBytesPerSample(mAudioFormat)); if ((audioBufferSize % frameSizeInBytes != 0) || (audioBufferSize < 1)) { - throw new IllegalArgumentException("Invalid audio buffer size."); + throw new IllegalArgumentException("Invalid audio buffer size " + audioBufferSize + + " (frame size " + frameSizeInBytes + ")"); } mNativeBufferSizeInBytes = audioBufferSize; diff --git a/media/java/android/media/MediaCodec.java b/media/java/android/media/MediaCodec.java index d26412789f39..13a22b49ff87 100644 --- a/media/java/android/media/MediaCodec.java +++ b/media/java/android/media/MediaCodec.java @@ -23,6 +23,7 @@ import android.graphics.ImageFormat; import android.graphics.Rect; import android.graphics.SurfaceTexture; import android.media.MediaCodecInfo.CodecCapabilities; +import android.media.MediaMetricsSet; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; @@ -3186,59 +3187,22 @@ final public class MediaCodec { public native final String getName(); /** - * Returns Analytics/Metrics data about the current content being + * Return Metrics data about the current codec instance. * - * @return a Bundle containing the set of attributes and values available - * for the media being handled by this instance of MediaCodec + * @return a MediaMetricsSet containing the set of attributes and values + * available for the media being handled by this instance of MediaCodec + * The attributes are descibed in {@link MediaMetricsSet.MediaCodec}. * - * <table style="width: 0%"> - * <thead> - * <tr> - * <th>Key</th> - * <th>Type</th> - * <th>Description</th> - * </tr> - * </thead> - * <tbody> - * <tr> - * <td>{@code "codec"}</td> - * <td>String</td> - * <td>Identifies the particular codec in use</td> - * </tr><tr> - * <td>{@code "mime"}</td> - * <td>String</td> - * <td>Mime type of the media being encoded/decoded</td> - * </tr><tr> - * <td>{@code "mode"}</td> - * <td>String</td> - * <td>"Audio" or "Video"</td> - * </tr><tr> - * <td>{@code "secure"}</td> - * <td>Integer</td> - * <td>Indicates whether the code is operating on secure content and - * may also use capabilities in android.media.MediaCrypto</td> - * </tr><tr> - * <td>{@code "height"}</td> - * <td>Integer</td> - * <td>Height (pixels); valid only when mode=video</td> - * </tr><tr> - * <td>{@code "width"}</td> - * <td>Integer</td> - * <td>Width (pixels); valid only when mode=video</td> - * </tr><tr> - * <td>{@code "rotation"}</td> - * <td>Integer</td> - * <td>rotation (degrees) to orient the video onto the target surface; - * valid only when mode=video. Note there may be additional - * rotations applied when the surface is mapped to the screen.</td> - * </tr> - * </tbody> - * </table> - * - * Additional fields specific to individual codecs will also appear in + * Additional vendor-specific fields may also be present in * the return value. */ - public native Bundle getMetrics(); + public MediaMetricsSet getMetrics() { + Bundle bundle = native_getMetrics(); + MediaMetricsSet mSet = new MediaMetricsSet(bundle); + return mSet; + } + + private native Bundle native_getMetrics(); /** * Change a video encoder's target bitrate on the fly. The value is an diff --git a/media/java/android/media/MediaExtractor.java b/media/java/android/media/MediaExtractor.java index b9e409d17748..2ed6668112ce 100644 --- a/media/java/android/media/MediaExtractor.java +++ b/media/java/android/media/MediaExtractor.java @@ -25,6 +25,7 @@ import android.content.res.AssetFileDescriptor; import android.media.MediaCodec; import android.media.MediaFormat; import android.media.MediaHTTPService; +import android.media.MediaMetricsSet; import android.net.Uri; import android.os.Bundle; import android.os.IBinder; @@ -651,41 +652,24 @@ final public class MediaExtractor { public native boolean hasCacheReachedEndOfStream(); /** - * Returns Analytics/Metrics data about the current media container. + * Return Metrics data about the current media container. * - * @return the set of keys and values available for the media being - * handled by this instance of MediaExtractor + * @return a MediaMetricsSet containing the set of attributes and values + * available for the media container being handled by this instance + * of MediaExtractor. + * The attributes are descibed in {@link MediaMetricsSet.MediaExtractor}. * - * <table style="width: 0%"> - * <thead> - * <tr> - * <th>Key</th> - * <th>Type</th> - * <th>Description</th> - * </tr> - * </thead> - * <tbody> - * <tr> - * <td>{@code "fmt"}</td> - * <td>String</td> - * <td>The container format (which determines the handler)</td> - * </tr><tr> - * <td>{@code "mime"}</td> - * <td>String</td> - * <td>Mime type of the container.</td> - * </tr><tr> - * <td>{@code "ntrk"}</td> - * <td>Integer</td> - * <td>Number of tracks in the container</td> - * </tr> - * </tbody> - * </table> - * - * Additional fields specific to individual codecs will also appear in + * Additional vendor-specific fields may also be present in * the return value. */ - public native Bundle getMetrics(); + public MediaMetricsSet getMetrics() { + Bundle bundle = native_getMetrics(); + MediaMetricsSet mSet = new MediaMetricsSet(bundle); + return mSet; + } + + private native Bundle native_getMetrics(); private static native final void native_init(); private native final void native_setup(); diff --git a/media/java/android/media/MediaMetricsSet.java b/media/java/android/media/MediaMetricsSet.java new file mode 100644 index 000000000000..5ecbee2127fc --- /dev/null +++ b/media/java/android/media/MediaMetricsSet.java @@ -0,0 +1,491 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.media; + +import android.os.Bundle; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileDescriptor; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.lang.Runnable; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.ref.WeakReference; +import java.net.HttpCookie; +import java.net.HttpURLConnection; +import java.net.InetSocketAddress; +import java.net.URL; +import java.nio.ByteOrder; +import java.util.Arrays; +import java.util.BitSet; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Scanner; +import java.util.Set; +import java.util.UUID; +import java.util.Vector; + + +/** + * MediaMetricsSet contains the results returned by the getMetrics() + * methods defined in other Media classes such as + * {@link MediaCodec}, {@link MediaExtractor}, {@link MediaPlayer}, + * and {@link MediaRecorder}. + * + * MediaMetricsSet behaves similarly to a {@link Bundle}. It contains + * a set of keys and values. + * Methods such as {@link #getInt} and {@link #getString} are provided + * to extract values of the corresponding types. + * The {@link #keySet} method can be used to discover all of the keys + * that are present in the particular instance. + * + */ +public final class MediaMetricsSet +{ + + /** + * This MediaCodec class holds the constants defining keys related to + * the metrics for a MediaCodec. + */ + public final static class MediaCodec + { + private MediaCodec() {} + + /** + * Key to extract the codec being used + * from the {@link MediaCodec#getMetrics} return value. + * The value is a String. + */ + public static final String KEY_CODEC = "android.media.mediacodec.codec"; + + /** + * Key to extract the MIME type + * from the {@link MediaCodec#getMetrics} return value. + * The value is a String. + */ + public static final String KEY_MIME = "android.media.mediacodec.mime"; + + /** + * Key to extract what the codec mode + * from the {@link MediaCodec#getMetrics} return value. + * The value is a String. Values will be one of the constants + * MODE_AUDIO or MODE_VIDEO. + */ + public static final String KEY_MODE = "android.media.mediacodec.mode"; + + /** + * The value returned for the key {@link #KEY_MODE} when the + * codec is a audio codec. + */ + public static final String MODE_AUDIO = "audio"; + + /** + * The value returned for the key {@link #KEY_MODE} when the + * codec is a video codec. + */ + public static final String MODE_VIDEO = "video"; + + /** + * Key to extract the flag indicating whether the codec is running + * as an encoder or decoder from the {@link MediaCodec#getMetrics} return value. + * The value is an integer. + * A 0 indicates decoder; 1 indicates encoder. + */ + public static final String KEY_ENCODER = "android.media.mediacodec.encoder"; + + /** + * Key to extract the flag indicating whether the codec is running + * in secure (DRM) mode from the {@link MediaCodec#getMetrics} return value. + * The value is an integer. + */ + public static final String KEY_SECURE = "android.media.mediacodec.secure"; + + /** + * Key to extract the width (in pixels) of the video track + * from the {@link MediaCodec#getMetrics} return value. + * The value is an integer. + */ + public static final String KEY_WIDTH = "android.media.mediacodec.width"; + + /** + * Key to extract the height (in pixels) of the video track + * from the {@link MediaCodec#getMetrics} return value. + * The value is an integer. + */ + public static final String KEY_HEIGHT = "android.media.mediacodec.height"; + + /** + * Key to extract the rotation (in degrees) to properly orient the video + * from the {@link MediaCodec#getMetrics} return. + * The value is a integer. + */ + public static final String KEY_ROTATION = "android.media.mediacodec.rotation"; + + } + + /** + * This class holds the constants defining keys related to + * the metrics for a MediaExtractor. + */ + public final static class MediaExtractor + { + private MediaExtractor() {} + + /** + * Key to extract the container format + * from the {@link MediaExtractor#getMetrics} return value. + * The value is a String. + */ + public static final String KEY_FORMAT = "android.media.mediaextractor.fmt"; + + /** + * Key to extract the container MIME type + * from the {@link MediaExtractor#getMetrics} return value. + * The value is a String. + */ + public static final String KEY_MIME = "android.media.mediaextractor.mime"; + + /** + * Key to extract the number of tracks in the container + * from the {@link MediaExtractor#getMetrics} return value. + * The value is an integer. + */ + public static final String KEY_TRACKS = "android.media.mediaextractor.ntrk"; + + } + + /** + * This class holds the constants defining keys related to + * the metrics for a MediaPlayer. + */ + public final static class MediaPlayer + { + private MediaPlayer() {} + + /** + * Key to extract the MIME type of the video track + * from the {@link MediaPlayer#getMetrics} return value. + * The value is a String. + */ + public static final String KEY_MIME_VIDEO = "android.media.mediaplayer.video.mime"; + + /** + * Key to extract the codec being used to decode the video track + * from the {@link MediaPlayer#getMetrics} return value. + * The value is a String. + */ + public static final String KEY_CODEC_VIDEO = "android.media.mediaplayer.video.codec"; + + /** + * Key to extract the width (in pixels) of the video track + * from the {@link MediaPlayer#getMetrics} return value. + * The value is an integer. + */ + public static final String KEY_WIDTH = "android.media.mediaplayer.width"; + + /** + * Key to extract the height (in pixels) of the video track + * from the {@link MediaPlayer#getMetrics} return value. + * The value is an integer. + */ + public static final String KEY_HEIGHT = "android.media.mediaplayer.height"; + + /** + * Key to extract the count of video frames played + * from the {@link MediaPlayer#getMetrics} return value. + * The value is an integer. + */ + public static final String KEY_FRAMES = "android.media.mediaplayer.frames"; + + /** + * Key to extract the count of video frames dropped + * from the {@link MediaPlayer#getMetrics} return value. + * The value is an integer. + */ + public static final String KEY_FRAMES_DROPPED = "android.media.mediaplayer.dropped"; + + /** + * Key to extract the MIME type of the audio track + * from the {@link MediaPlayer#getMetrics} return value. + * The value is a String. + */ + public static final String KEY_MIME_AUDIO = "android.media.mediaplayer.audio.mime"; + + /** + * Key to extract the codec being used to decode the audio track + * from the {@link MediaPlayer#getMetrics} return value. + * The value is a String. + */ + public static final String KEY_CODEC_AUDIO = "android.media.mediaplayer.audio.codec"; + + /** + * Key to extract the duration (in milliseconds) of the + * media being played + * from the {@link MediaPlayer#getMetrics} return value. + * The value is a long. + */ + public static final String KEY_DURATION = "android.media.mediaplayer.durationMs"; + + /** + * Key to extract the playing time (in milliseconds) of the + * media being played + * from the {@link MediaPlayer#getMetrics} return value. + * The value is a long. + */ + public static final String KEY_PLAYING = "android.media.mediaplayer.playingMs"; + + /** + * Key to extract the count of errors encountered while + * playing the media + * from the {@link MediaPlayer#getMetrics} return value. + * The value is an integer. + */ + public static final String KEY_ERRORS = "android.media.mediaplayer.err"; + + /** + * Key to extract an (optional) error code detected while + * playing the media + * from the {@link MediaPlayer#getMetrics} return value. + * The value is an integer. + */ + public static final String KEY_ERROR_CODE = "android.media.mediaplayer.errcode"; + + } + + /** + * This class holds the constants defining keys related to + * the metrics for a MediaRecorder. + */ + public final static class MediaRecorder + { + private MediaRecorder() {} + + /** + * Key to extract the audio bitrate + * from the {@link MediaRecorder#getMetrics} return. + * The value is an integer. + */ + public static final String KEY_AUDIO_BITRATE = "android.media.mediarecorder.audio-bitrate"; + + /** + * Key to extract the number of audio channels + * from the {@link MediaRecorder#getMetrics} return. + * The value is an integer. + */ + public static final String KEY_AUDIO_CHANNELS = "android.media.mediarecorder.audio-channels"; + + /** + * Key to extract the audio samplerate + * from the {@link MediaRecorder#getMetrics} return. + * The value is an integer. + */ + public static final String KEY_AUDIO_SAMPLERATE = "android.media.mediarecorder.audio-samplerate"; + + /** + * Key to extract the audio timescale + * from the {@link MediaRecorder#getMetrics} return. + * The value is an integer. + */ + public static final String KEY_AUDIO_TIMESCALE = "android.media.mediarecorder.audio-timescale"; + + /** + * Key to extract the video capture frame rate + * from the {@link MediaRecorder#getMetrics} return. + * The value is a double. + */ + public static final String KEY_CAPTURE_FPS = "android.media.mediarecorder.capture-fps"; + + /** + * Key to extract the video capture framerate enable value + * from the {@link MediaRecorder#getMetrics} return. + * The value is an integer. + */ + public static final String KEY_CAPTURE_FPS_ENABLE = "android.media.mediarecorder.capture-fpsenable"; + + /** + * Key to extract the intended playback frame rate + * from the {@link MediaRecorder#getMetrics} return. + * The value is an integer. + */ + public static final String KEY_FRAMERATE = "android.media.mediarecorder.frame-rate"; + + /** + * Key to extract the height (in pixels) of the captured video + * from the {@link MediaRecorder#getMetrics} return. + * The value is an integer. + */ + public static final String KEY_HEIGHT = "android.media.mediarecorder.height"; + + /** + * Key to extract the recorded movies time units + * from the {@link MediaRecorder#getMetrics} return. + * The value is an integer. + * A value of 1000 indicates that the movie's timing is in milliseconds. + */ + public static final String KEY_MOVIE_TIMESCALE = "android.media.mediarecorder.movie-timescale"; + + /** + * Key to extract the rotation (in degrees) to properly orient the video + * from the {@link MediaRecorder#getMetrics} return. + * The value is an integer. + */ + public static final String KEY_ROTATION = "android.media.mediarecorder.rotation"; + + /** + * Key to extract the video bitrate from being used + * from the {@link MediaRecorder#getMetrics} return. + * The value is an integer. + */ + public static final String KEY_VIDEO_BITRATE = "android.media.mediarecorder.video-bitrate"; + + /** + * Key to extract the value for how often video iframes are generated + * from the {@link MediaRecorder#getMetrics} return. + * The value is an integer. + */ + public static final String KEY_VIDEO_IFRAME_INTERVAL = "android.media.mediarecorder.video-iframe-interval"; + + /** + * Key to extract the video encoding level + * from the {@link MediaRecorder#getMetrics} return. + * The value is an integer. + */ + public static final String KEY_VIDEO_LEVEL = "android.media.mediarecorder.video-encoder-level"; + + /** + * Key to extract the video encoding profile + * from the {@link MediaRecorder#getMetrics} return. + * The value is an integer. + */ + public static final String KEY_VIDEO_PROFILE = "android.media.mediarecorder.video-encoder-profile"; + + /** + * Key to extract the recorded video time units + * from the {@link MediaRecorder#getMetrics} return. + * The value is an integer. + * A value of 1000 indicates that the video's timing is in milliseconds. + */ + public static final String KEY_VIDEO_TIMESCALE = "android.media.mediarecorder.video-timescale"; + + /** + * Key to extract the width (in pixels) of the captured video + * from the {@link MediaRecorder#getMetrics} return. + * The value is an integer. + */ + public static final String KEY_WIDTH = "android.media.mediarecorder.width"; + + } + + /* + * Methods that we want + */ + + private Bundle mBundle; + + MediaMetricsSet(Bundle bundle) { + mBundle = bundle; + } + + /** + * Returns the number of mappings contained in this Bundle. + * + * @return the number of mappings as an int. + */ + public int size() { + return mBundle.size(); + } + + /** + * Returns true if the mapping of this MediaMetricsSet is empty, + * false otherwise. + */ + public boolean isEmpty() { + return mBundle.isEmpty(); + } + + /** + * Returns the value associated with the given key, or defaultValue if + * no mapping of the desired type exists for the given key. + * + * @param key a String + * @param defaultValue Value to return if key does not exist + * @return a double value + */ + public double getDouble(String key, double defaultValue) { + return mBundle.getDouble(key, defaultValue); + } + + /** + * Returns the value associated with the given key, or defaultValue if + * no mapping of the desired type exists for the given key. + * + * @param key a String + * @param defaultValue Value to return if key does not exist + * @return an int value + */ + public int getInt(String key, int defaultValue) { + return mBundle.getInt(key, defaultValue); + } + + /** + * Returns the value associated with the given key, or defaultValue if + * no mapping of the desired type exists for the given key. + * + * @param key a String + * @param defaultValue Value to return if key does not exist + * @return a long value + */ + public long getLong(String key, long defaultValue) { + return mBundle.getLong(key, defaultValue); + } + + /** + * Returns the value associated with the given key, or defaultValue if + * no mapping of the desired type exists for the given key or if a null + * value is explicitly associated with the given key. + * + * @param key a String + * @param defaultValue Value to return if key does not exist or if a null + * value is associated with the given key. + * @return the String value associated with the given key, or defaultValue + * if no valid String object is currently mapped to that key. + */ + public String getString(String key, String defaultValue) { + return mBundle.getString(key, defaultValue); + } + + /** + * Returns a Set containing the Strings used as keys in this Bundle. + * + * @return a Set of String keys + */ + public Set<String> keySet() { + return mBundle.keySet(); + } + + + + public String toString() { + return mBundle.toString(); + } + +} + diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java index b85c91119384..1ee05b8c3a9a 100644 --- a/media/java/android/media/MediaPlayer.java +++ b/media/java/android/media/MediaPlayer.java @@ -49,6 +49,7 @@ import android.media.AudioManager; import android.media.BufferingParams; import android.media.MediaDrm; import android.media.MediaFormat; +import android.media.MediaMetricsSet; import android.media.MediaTimeProvider; import android.media.PlaybackParams; import android.media.SubtitleController; @@ -1494,70 +1495,22 @@ public class MediaPlayer extends PlayerBase public native int getVideoHeight(); /** - * Returns Analytics/Metrics data about the current video in this player. - * - * @return the a map of attributes and values available for this video - * player or null if no metrics are available. - * - * <table style="width: 0%"> - * <thead> - * <tr> - * <th>Key</th> - * <th>Type</th> - * <th>Description</th> - * </tr> - * </thead> - * <tbody> - * <tr> - * <td>{@code "video/codec"}</td> - * <td>String</td> - * <td>Identifies the video codec in use</td> - * </tr><tr> - * <td>{@code "video/mime"}</td> - * <td>String</td> - * <td>Mime type of the video being encoded/decoded</td> - * </tr><tr> - * <td>{@code "audio/codec"}</td> - * <td>String</td> - * <td>Identifies the audio codec in use</td> - * </tr><tr> - * <td>{@code "audio/mime"}</td> - * <td>String</td> - * <td>Mime type of the audio being encoded/decoded</td> - * </tr><tr> - * <td>{@code "ht"}</td> - * <td>Integer</td> - * <td>Height (pixels); valid only when mode=video</td> - * </tr><tr> - * <td>{@code "wid"}</td> - * <td>Integer</td> - * <td>Width (pixels); valid only when mode=video</td> - * </tr><tr> - * <td>{@code "frame"}</td> - * <td>Integer</td> - * <td>Number of decoded video frames sent to the display</td> - * </tr><tr> - * <td>{@code "dropped"}</td> - * <td>Integer</td> - * <td>Number of decoded video frames that were not sent to display. - * These frames were dropped by the player.</td> - * </tr><tr> - * <td>{@code "durationMs"}</td> - * <td>Integer</td> - * <td>The length of the media being played (in ms), e.g. "This video lasts for 30000 milliseconds". </td> - * </tr><tr> - * <td>{@code "playingMs"}</td> - * <td>Integer</td> - * <td>The time the media has been played (in ms). If you watch a - * 30 second twice through, this will report 60000 ms.</td> - * </tr> - * </tbody> - * </table> - * - * Additional fields specific to individual codecs will also appear in + * Return Metrics data about the current player. + * + * @return a MediaMetricsSet containing the set of attributes and values + * available for the media being handled by this instance of MediaPlayer + * The attributes are descibed in {@link MediaMetricsSet.MediaPlayer}. + * + * Additional vendor-specific fields may also be present in * the return value. */ - public native Bundle getMetrics(); + public MediaMetricsSet getMetrics() { + Bundle bundle = native_getMetrics(); + MediaMetricsSet mSet = new MediaMetricsSet(bundle); + return mSet; + } + + private native Bundle native_getMetrics(); /** * Checks whether the MediaPlayer is playing. diff --git a/media/java/android/media/MediaRecorder.java b/media/java/android/media/MediaRecorder.java index 075a84ff00a7..cdc1d60fa789 100644 --- a/media/java/android/media/MediaRecorder.java +++ b/media/java/android/media/MediaRecorder.java @@ -20,6 +20,7 @@ import android.annotation.NonNull; import android.annotation.SystemApi; import android.app.ActivityThread; import android.hardware.Camera; +import android.media.MediaMetricsSet; import android.os.Bundle; import android.os.Handler; import android.os.Looper; @@ -1259,91 +1260,23 @@ public class MediaRecorder private native void setParameter(String nameValuePair); /** - * Returns Metrics data about the current media container. + * Return Metrics data about the current Mediarecorder instance. * - * @return the set of keys and values available for the media being - * handled by this instance of MediaExtractor. The keys, data types, - * and meaning are described in the following table. + * @return a MediaMetricsSet containing the set of attributes and values + * available for the media being generated by this instance of + * MediaRecorder. + * The attributes are descibed in {@link MediaMetricsSet.MediaRecorder}. * - * <table style="width: 0%"> - * <thead> - * <tr> - * <th>Key</th> - * <th>Type</th> - * <th>Description</th> - * </tr> - * </thead> - * <tbody> - * <tr> - * <td>{@code "ht"}</td> - * <td>Integer</td> - * <td>Height of the recorded video (pixels)</td> - * </tr><tr> - * <td>{@code "wid"}</td> - * <td>Integer</td> - * <td>Width of the recorded video (pixels)</td> - * </tr><tr> - * <td>{@code "frame-rate"}</td> - * <td>Integer</td> - * <td>Framerate of captured Video (frames per second)</td> - * </tr><tr> - * <td>{@code "video-bitrate"}</td> - * <td>Integer</td> - * <td>Bit rate of encoded video (bits per second)</td> - * </tr><tr> - * <td>{@code "video-iframe-interval"}</td> - * <td>Integer</td> - * <td>Interval between encoded IFrames (seconds)</td> - * </tr><tr> - * <td>{@code "video-timescale"}</td> - * <td>Integer</td> - * <td></td> - * </tr><tr> - * <td>{@code "video-encoder-profile"}</td> - * <td>Integer</td> - * <td>Video Encoder Profile, as defined in OpenMAX IL</td> - * </tr><tr> - * <td>{@code "video-encoder-level"}</td> - * <td>Integer</td> - * <td>Video Encoder Level, as defined in OpenMAX IL</td> - * </tr><tr> - * <td>{@code "audio-bitrate"}</td> - * <td>Integer</td> - * <td>Bitrate of encoded audio (bits per second)</td> - * </tr><tr> - * <td>{@code "audio-samplerate"}</td> - * <td>Integer</td> - * <td></td> - * </tr><tr> - * <td>{@code "audio-channels"}</td> - * <td>Integer</td> - * <td>Number of Audio Channels Captured</td> - * </tr><tr> - * <td>{@code "audio-timescale"}</td> - * <td>Integer</td> - * <td></td> - * </tr><tr> - * <td>{@code "movie-timescale"}</td> - * <td>Integer</td> - * <td></td> - * </tr><tr> - * <td>{@code "movie-timescale"}</td> - * <td>Integer</td> - * <td></td> - * </tr><tr> - * <td>{@code "capture-fps"}</td> - * <td>Integer</td> - * <td></td> - * </tr><tr> - * <td>{@code "rotation"}</td> - * <td>Integer</td> - * <td>Orientation of the Video (degrees)</td> - * </tr> - * </tbody> - * </table> - */ - - public native Bundle getMetrics(); + * Additional vendor-specific fields may also be present in + * the return value. + */ + public MediaMetricsSet getMetrics() { + Bundle bundle = native_getMetrics(); + MediaMetricsSet mSet = new MediaMetricsSet(bundle); + return mSet; + } + + private native Bundle native_getMetrics(); @Override protected void finalize() { native_finalize(); } diff --git a/media/java/android/media/tv/TvContract.java b/media/java/android/media/tv/TvContract.java index 6e15d8da0033..62fd39516b85 100644 --- a/media/java/android/media/tv/TvContract.java +++ b/media/java/android/media/tv/TvContract.java @@ -2679,6 +2679,9 @@ public final class TvContract { /** * The ID of the TV channel that provides this TV program. * + * <p>This value cannot be changed once it's set. Trying to modify it will make the update + * fail. + * * <p>This is a part of the channel URI and matches to {@link BaseColumns#_ID}. * * <p>This is a required field. diff --git a/media/java/android/media/tv/TvInputHardwareInfo.java b/media/java/android/media/tv/TvInputHardwareInfo.java index 51fa036a77c4..957c5820838e 100644 --- a/media/java/android/media/tv/TvInputHardwareInfo.java +++ b/media/java/android/media/tv/TvInputHardwareInfo.java @@ -16,11 +16,15 @@ package android.media.tv; +import static java.lang.annotation.RetentionPolicy.SOURCE; + +import android.annotation.IntDef; import android.annotation.SystemApi; import android.media.AudioManager; import android.os.Parcel; import android.os.Parcelable; import android.util.Log; +import java.lang.annotation.Retention; /** * Simple container for information about TV input hardware. @@ -44,6 +48,28 @@ public final class TvInputHardwareInfo implements Parcelable { public static final int TV_INPUT_TYPE_HDMI = 9; public static final int TV_INPUT_TYPE_DISPLAY_PORT = 10; + /** @hide */ + @Retention(SOURCE) + @IntDef({CABLE_CONNECTION_STATUS_UNKNOWN, CABLE_CONNECTION_STATUS_CONNECTED, + CABLE_CONNECTION_STATUS_DISCONNECTED}) + public @interface CableConnectionStatus {} + + // Match hardware/interfaces/tv/input/1.0/types.hal + /** + * The hardware is unsure about the connection status or does not support cable detection. + */ + public static final int CABLE_CONNECTION_STATUS_UNKNOWN = 0; + + /** + * Cable is connected to the hardware. + */ + public static final int CABLE_CONNECTION_STATUS_CONNECTED = 1; + + /** + * Cable is disconnected to the hardware. + */ + public static final int CABLE_CONNECTION_STATUS_DISCONNECTED = 2; + public static final Parcelable.Creator<TvInputHardwareInfo> CREATOR = new Parcelable.Creator<TvInputHardwareInfo>() { @Override @@ -69,6 +95,8 @@ public final class TvInputHardwareInfo implements Parcelable { private int mAudioType; private String mAudioAddress; private int mHdmiPortId; + @CableConnectionStatus + private int mCableConnectionStatus; private TvInputHardwareInfo() { } @@ -96,6 +124,19 @@ public final class TvInputHardwareInfo implements Parcelable { return mHdmiPortId; } + /** + * Gets the cable connection status of the hardware. + * + * @return {@code CABLE_CONNECTION_STATUS_CONNECTED} if cable is connected. + * {@code CABLE_CONNECTION_STATUS_DISCONNECTED} if cable is disconnected. + * {@code CABLE_CONNECTION_STATUS_UNKNOWN} if the hardware is unsure about the + * connection status or does not support cable detection. + */ + @CableConnectionStatus + public int getCableConnectionStatus() { + return mCableConnectionStatus; + } + @Override public String toString() { StringBuilder b = new StringBuilder(128); @@ -106,6 +147,7 @@ public final class TvInputHardwareInfo implements Parcelable { if (mType == TV_INPUT_TYPE_HDMI) { b.append(", hdmi_port=").append(mHdmiPortId); } + b.append(", cable_connection_status=").append(mCableConnectionStatus); b.append("}"); return b.toString(); } @@ -125,6 +167,7 @@ public final class TvInputHardwareInfo implements Parcelable { if (mType == TV_INPUT_TYPE_HDMI) { dest.writeInt(mHdmiPortId); } + dest.writeInt(mCableConnectionStatus); } public void readFromParcel(Parcel source) { @@ -135,6 +178,7 @@ public final class TvInputHardwareInfo implements Parcelable { if (mType == TV_INPUT_TYPE_HDMI) { mHdmiPortId = source.readInt(); } + mCableConnectionStatus = source.readInt(); } public static final class Builder { @@ -143,6 +187,7 @@ public final class TvInputHardwareInfo implements Parcelable { private int mAudioType = AudioManager.DEVICE_NONE; private String mAudioAddress = ""; private Integer mHdmiPortId = null; + private Integer mCableConnectionStatus = CABLE_CONNECTION_STATUS_UNKNOWN; public Builder() { } @@ -172,6 +217,14 @@ public final class TvInputHardwareInfo implements Parcelable { return this; } + /** + * Sets cable connection status. + */ + public Builder cableConnectionStatus(@CableConnectionStatus int cableConnectionStatus) { + mCableConnectionStatus = cableConnectionStatus; + return this; + } + public TvInputHardwareInfo build() { if (mDeviceId == null || mType == null) { throw new UnsupportedOperationException(); @@ -191,6 +244,7 @@ public final class TvInputHardwareInfo implements Parcelable { if (mHdmiPortId != null) { info.mHdmiPortId = mHdmiPortId; } + info.mCableConnectionStatus = mCableConnectionStatus; return info; } } diff --git a/media/java/android/media/tv/TvInputInfo.java b/media/java/android/media/tv/TvInputInfo.java index 35988d40fe5d..a292b8e4f81e 100644 --- a/media/java/android/media/tv/TvInputInfo.java +++ b/media/java/android/media/tv/TvInputInfo.java @@ -138,7 +138,6 @@ public final class TvInputInfo implements Parcelable { // Attributes from XML meta data. private final String mSetupActivity; - private final String mSettingsActivity; private final boolean mCanRecord; private final int mTunerCount; @@ -259,9 +258,8 @@ public final class TvInputInfo implements Parcelable { private TvInputInfo(ResolveInfo service, String id, int type, boolean isHardwareInput, CharSequence label, int labelResId, Icon icon, Icon iconStandby, Icon iconDisconnected, - String setupActivity, String settingsActivity, boolean canRecord, int tunerCount, - HdmiDeviceInfo hdmiDeviceInfo, boolean isConnectedToHdmiSwitch, String parentId, - Bundle extras) { + String setupActivity, boolean canRecord, int tunerCount, HdmiDeviceInfo hdmiDeviceInfo, + boolean isConnectedToHdmiSwitch, String parentId, Bundle extras) { mService = service; mId = id; mType = type; @@ -272,7 +270,6 @@ public final class TvInputInfo implements Parcelable { mIconStandby = iconStandby; mIconDisconnected = iconDisconnected; mSetupActivity = setupActivity; - mSettingsActivity = settingsActivity; mCanRecord = canRecord; mTunerCount = tunerCount; mHdmiDeviceInfo = hdmiDeviceInfo; @@ -340,14 +337,12 @@ public final class TvInputInfo implements Parcelable { /** * Returns an intent to start the settings activity for this TV input. + * + * @deprecated Use {@link #createSetupIntent()} instead. Settings activity is deprecated. + * Use setup activity instead to provide settings. */ + @Deprecated public Intent createSettingsIntent() { - if (!TextUtils.isEmpty(mSettingsActivity)) { - Intent intent = new Intent(Intent.ACTION_MAIN); - intent.setClassName(mService.serviceInfo.packageName, mSettingsActivity); - intent.putExtra(EXTRA_INPUT_ID, getId()); - return intent; - } return null; } @@ -554,7 +549,6 @@ public final class TvInputInfo implements Parcelable { && Objects.equals(mIconStandby, obj.mIconStandby) && Objects.equals(mIconDisconnected, obj.mIconDisconnected) && TextUtils.equals(mSetupActivity, obj.mSetupActivity) - && TextUtils.equals(mSettingsActivity, obj.mSettingsActivity) && mCanRecord == obj.mCanRecord && mTunerCount == obj.mTunerCount && Objects.equals(mHdmiDeviceInfo, obj.mHdmiDeviceInfo) @@ -589,7 +583,6 @@ public final class TvInputInfo implements Parcelable { dest.writeParcelable(mIconStandby, flags); dest.writeParcelable(mIconDisconnected, flags); dest.writeString(mSetupActivity); - dest.writeString(mSettingsActivity); dest.writeByte(mCanRecord ? (byte) 1 : 0); dest.writeInt(mTunerCount); dest.writeParcelable(mHdmiDeviceInfo, flags); @@ -631,7 +624,6 @@ public final class TvInputInfo implements Parcelable { mIconStandby = in.readParcelable(null); mIconDisconnected = in.readParcelable(null); mSetupActivity = in.readString(); - mSettingsActivity = in.readString(); mCanRecord = in.readByte() == 1; mTunerCount = in.readInt(); mHdmiDeviceInfo = in.readParcelable(null); @@ -678,7 +670,6 @@ public final class TvInputInfo implements Parcelable { private Icon mIconStandby; private Icon mIconDisconnected; private String mSetupActivity; - private String mSettingsActivity; private Boolean mCanRecord; private Integer mTunerCount; private TvInputHardwareInfo mTvInputHardwareInfo; @@ -906,7 +897,7 @@ public final class TvInputInfo implements Parcelable { } parseServiceMetadata(type); return new TvInputInfo(mResolveInfo, id, type, isHardwareInput, mLabel, mLabelResId, - mIcon, mIconStandby, mIconDisconnected, mSetupActivity, mSettingsActivity, + mIcon, mIconStandby, mIconDisconnected, mSetupActivity, mCanRecord == null ? false : mCanRecord, mTunerCount == null ? 0 : mTunerCount, mHdmiDeviceInfo, isConnectedToHdmiSwitch, mParentId, mExtras); } @@ -961,8 +952,6 @@ public final class TvInputInfo implements Parcelable { if (inputType == TYPE_TUNER && TextUtils.isEmpty(mSetupActivity)) { throw new IllegalStateException("Setup activity not found for " + si.name); } - mSettingsActivity = sa.getString( - com.android.internal.R.styleable.TvInputService_settingsActivity); if (mCanRecord == null) { mCanRecord = sa.getBoolean( com.android.internal.R.styleable.TvInputService_canRecord, false); diff --git a/media/java/android/media/tv/TvInputManager.java b/media/java/android/media/tv/TvInputManager.java index 1eae8db60833..09b2050b1b1a 100644 --- a/media/java/android/media/tv/TvInputManager.java +++ b/media/java/android/media/tv/TvInputManager.java @@ -224,9 +224,8 @@ public final class TvInputManager { * {@link TvInputCallback#onInputStateChanged(String, int)}: The input source is connected. * * <p>This state indicates that a source device is connected to the input port and is in the - * normal operation mode. It is mostly relevant to hardware inputs such as HDMI input. This is - * the default state for any hardware inputs where their states are unknown. Non-hardware inputs - * are considered connected all the time. + * normal operation mode. It is mostly relevant to hardware inputs such as HDMI input. + * Non-hardware inputs are considered connected all the time. */ public static final int INPUT_STATE_CONNECTED = 0; @@ -236,7 +235,8 @@ public final class TvInputManager { * in standby mode. * * <p>This state indicates that a source device is connected to the input port but is in standby - * mode. It is mostly relevant to hardware inputs such as HDMI input. + * or low power mode. It is mostly relevant to hardware inputs such as HDMI input and Component + * inputs. */ public static final int INPUT_STATE_CONNECTED_STANDBY = 1; diff --git a/media/jni/android_media_MediaCodec.cpp b/media/jni/android_media_MediaCodec.cpp index 293e5dd746da..a8dd3133b275 100644 --- a/media/jni/android_media_MediaCodec.cpp +++ b/media/jni/android_media_MediaCodec.cpp @@ -624,7 +624,7 @@ status_t JMediaCodec::getName(JNIEnv *env, jstring *nameStr) const { return OK; } -status_t JMediaCodec::getMetrics(JNIEnv *, Parcel *reply) const { +status_t JMediaCodec::getMetrics(JNIEnv *, MediaAnalyticsItem * &reply) const { status_t status = mCodec->getMetrics(reply); return status; @@ -1666,9 +1666,9 @@ static jobject android_media_MediaCodec_getName( } static jobject -android_media_MediaCodec_getMetrics(JNIEnv *env, jobject thiz) +android_media_MediaCodec_native_getMetrics(JNIEnv *env, jobject thiz) { - ALOGV("android_media_MediaCodec_getMetrics"); + ALOGV("android_media_MediaCodec_native_getMetrics"); sp<JMediaCodec> codec = getMediaCodec(env, thiz); if (codec == NULL ) { @@ -1677,16 +1677,14 @@ android_media_MediaCodec_getMetrics(JNIEnv *env, jobject thiz) } // get what we have for the metrics from the codec - Parcel reply; - status_t err = codec->getMetrics(env, &reply); + MediaAnalyticsItem *item = NULL; + + status_t err = codec->getMetrics(env, item); if (err != OK) { ALOGE("getMetrics failed"); return (jobject) NULL; } - // build and return the Bundle - MediaAnalyticsItem *item = new MediaAnalyticsItem; - item->readFromParcel(reply); jobject mybundle = MediaMetricsJNI::writeMetricsToBundle(env, item, NULL); // housekeeping @@ -2004,8 +2002,8 @@ static const JNINativeMethod gMethods[] = { { "getName", "()Ljava/lang/String;", (void *)android_media_MediaCodec_getName }, - { "getMetrics", "()Landroid/os/Bundle;", - (void *)android_media_MediaCodec_getMetrics}, + { "native_getMetrics", "()Landroid/os/Bundle;", + (void *)android_media_MediaCodec_native_getMetrics}, { "setParameters", "([Ljava/lang/String;[Ljava/lang/Object;)V", (void *)android_media_MediaCodec_setParameters }, diff --git a/media/jni/android_media_MediaCodec.h b/media/jni/android_media_MediaCodec.h index a8c76c5e61f7..c9a1700215a4 100644 --- a/media/jni/android_media_MediaCodec.h +++ b/media/jni/android_media_MediaCodec.h @@ -19,6 +19,7 @@ #include "jni.h" +#include <media/MediaAnalyticsItem.h> #include <media/hardware/CryptoAPI.h> #include <media/stagefright/foundation/ABase.h> #include <media/stagefright/foundation/AHandler.h> @@ -116,7 +117,7 @@ struct JMediaCodec : public AHandler { status_t getName(JNIEnv *env, jstring *name) const; - status_t getMetrics(JNIEnv *env, Parcel *reply) const; + status_t getMetrics(JNIEnv *env, MediaAnalyticsItem * &reply) const; status_t setParameters(const sp<AMessage> ¶ms); diff --git a/media/jni/android_media_MediaExtractor.cpp b/media/jni/android_media_MediaExtractor.cpp index 3c3349359e82..c2cfed9bfa2f 100644 --- a/media/jni/android_media_MediaExtractor.cpp +++ b/media/jni/android_media_MediaExtractor.cpp @@ -811,9 +811,9 @@ static void android_media_MediaExtractor_native_finalize( } static jobject -android_media_MediaExtractor_getMetrics(JNIEnv * env, jobject thiz) +android_media_MediaExtractor_native_getMetrics(JNIEnv * env, jobject thiz) { - ALOGV("android_media_MediaExtractor_getMetrics"); + ALOGV("android_media_MediaExtractor_native_getMetrics"); sp<JMediaExtractor> extractor = getMediaExtractor(env, thiz); if (extractor == NULL ) { @@ -905,8 +905,8 @@ static const JNINativeMethod gMethods[] = { { "hasCacheReachedEndOfStream", "()Z", (void *)android_media_MediaExtractor_hasCacheReachedEOS }, - {"getMetrics", "()Landroid/os/Bundle;", - (void *)android_media_MediaExtractor_getMetrics}, + {"native_getMetrics", "()Landroid/os/Bundle;", + (void *)android_media_MediaExtractor_native_getMetrics}, }; int register_android_media_MediaExtractor(JNIEnv *env) { diff --git a/media/jni/android_media_MediaPlayer.cpp b/media/jni/android_media_MediaPlayer.cpp index 27724a185c36..1b52cf58cbff 100644 --- a/media/jni/android_media_MediaPlayer.cpp +++ b/media/jni/android_media_MediaPlayer.cpp @@ -708,7 +708,7 @@ android_media_MediaPlayer_getVideoHeight(JNIEnv *env, jobject thiz) } static jobject -android_media_MediaPlayer_getMetrics(JNIEnv *env, jobject thiz) +android_media_MediaPlayer_native_getMetrics(JNIEnv *env, jobject thiz) { sp<MediaPlayer> mp = getMediaPlayer(env, thiz); if (mp == NULL ) { @@ -1393,7 +1393,7 @@ static const JNINativeMethod gMethods[] = { {"_stop", "()V", (void *)android_media_MediaPlayer_stop}, {"getVideoWidth", "()I", (void *)android_media_MediaPlayer_getVideoWidth}, {"getVideoHeight", "()I", (void *)android_media_MediaPlayer_getVideoHeight}, - {"getMetrics", "()Landroid/os/Bundle;", (void *)android_media_MediaPlayer_getMetrics}, + {"native_getMetrics", "()Landroid/os/Bundle;", (void *)android_media_MediaPlayer_native_getMetrics}, {"setPlaybackParams", "(Landroid/media/PlaybackParams;)V", (void *)android_media_MediaPlayer_setPlaybackParams}, {"getPlaybackParams", "()Landroid/media/PlaybackParams;", (void *)android_media_MediaPlayer_getPlaybackParams}, {"setSyncParams", "(Landroid/media/SyncParams;)V", (void *)android_media_MediaPlayer_setSyncParams}, diff --git a/media/jni/android_media_MediaRecorder.cpp b/media/jni/android_media_MediaRecorder.cpp index 77544eb736b5..7a63e003875a 100644 --- a/media/jni/android_media_MediaRecorder.cpp +++ b/media/jni/android_media_MediaRecorder.cpp @@ -628,9 +628,9 @@ void android_media_MediaRecorder_setInputSurface( } static jobject -android_media_MediaRecorder_getMetrics(JNIEnv *env, jobject thiz) +android_media_MediaRecorder_native_getMetrics(JNIEnv *env, jobject thiz) { - ALOGV("android_media_MediaRecorder_getMetrics"); + ALOGV("android_media_MediaRecorder_native_getMetrics"); sp<MediaRecorder> mr = getMediaRecorder(env, thiz); if (mr == NULL) { @@ -688,7 +688,7 @@ static const JNINativeMethod gMethods[] = { {"native_finalize", "()V", (void *)android_media_MediaRecorder_native_finalize}, {"native_setInputSurface", "(Landroid/view/Surface;)V", (void *)android_media_MediaRecorder_setInputSurface }, - {"getMetrics", "()Landroid/os/Bundle;", (void *)android_media_MediaRecorder_getMetrics}, + {"native_getMetrics", "()Landroid/os/Bundle;", (void *)android_media_MediaRecorder_native_getMetrics}, }; // This function only registers the native methods, and is called from diff --git a/media/mca/filterfw/jni/jni_util.h b/media/mca/filterfw/jni/jni_util.h index 803ed29bdf33..9b57958c4473 100644 --- a/media/mca/filterfw/jni/jni_util.h +++ b/media/mca/filterfw/jni/jni_util.h @@ -198,7 +198,8 @@ class ObjectPool { CObjMap objects_; FlagMap owns_; - DISALLOW_COPY_AND_ASSIGN(ObjectPool); + ObjectPool(const ObjectPool&) = delete; + ObjectPool& operator=(const ObjectPool&) = delete; }; template<typename T> ObjectPool<T>* ObjectPool<T>::instance_ = NULL; diff --git a/media/mca/filterfw/native/Android.mk b/media/mca/filterfw/native/Android.mk index 2e900fe29e38..feaefcbad6c5 100644 --- a/media/mca/filterfw/native/Android.mk +++ b/media/mca/filterfw/native/Android.mk @@ -41,7 +41,11 @@ LOCAL_EXPORT_LDLIBS := -llog -lgcc LOCAL_CFLAGS += -Wall -Werror -Wunused -Wunreachable-code -LOCAL_STATIC_LIBRARIES := libarect +LOCAL_STATIC_LIBRARIES := \ + libarect \ + +LOCAL_SHARED_LIBRARIES += \ + libgui \ # TODO: Build a shared library as well? include $(BUILD_STATIC_LIBRARY) diff --git a/media/mca/filterfw/native/base/utilities.h b/media/mca/filterfw/native/base/utilities.h index 6bb3b7f7c9cf..2818f7222dd3 100644 --- a/media/mca/filterfw/native/base/utilities.h +++ b/media/mca/filterfw/native/base/utilities.h @@ -23,18 +23,6 @@ namespace android { namespace filterfw { -// Convenience Macro to make copy constructor and assignment operator private -// (thereby disallowing copying and assigning). -#define DISALLOW_COPY_AND_ASSIGN(TypeName) \ - TypeName(const TypeName&); \ - void operator=(const TypeName&) - -// A macro to disallow all the implicit constructors, namely the -// default constructor, copy constructor and operator= functions. -#define DISALLOW_IMPLICIT_CONSTRUCTORS(TypeName) \ - TypeName(); \ - DISALLOW_COPY_AND_ASSIGN(TypeName) - // STLDeleteContainerPointers() // For a range within a container of pointers, calls delete // (non-array version) on these pointers. diff --git a/media/mca/filterfw/native/core/gl_env.h b/media/mca/filterfw/native/core/gl_env.h index 04453016dc93..6af91af6f6c1 100644 --- a/media/mca/filterfw/native/core/gl_env.h +++ b/media/mca/filterfw/native/core/gl_env.h @@ -256,7 +256,8 @@ class GLEnv { std::map<int, ShaderProgram*> attached_shaders_; std::map<int, VertexFrame*> attached_vframes_; - DISALLOW_COPY_AND_ASSIGN(GLEnv); + GLEnv(const GLEnv&) = delete; + GLEnv& operator=(const GLEnv&) = delete; }; } // namespace filterfw diff --git a/media/mca/filterfw/native/core/native_frame.h b/media/mca/filterfw/native/core/native_frame.h index 2da557dec000..fd5216542910 100644 --- a/media/mca/filterfw/native/core/native_frame.h +++ b/media/mca/filterfw/native/core/native_frame.h @@ -76,7 +76,8 @@ class NativeFrame { // Capacity of data buffer in bytes. int capacity_; - DISALLOW_COPY_AND_ASSIGN(NativeFrame); + NativeFrame(const NativeFrame&) = delete; + NativeFrame& operator=(const NativeFrame&) = delete; }; } // namespace filterfw diff --git a/media/mca/filterfw/native/core/native_program.h b/media/mca/filterfw/native/core/native_program.h index ce704af67aa8..e39afc9931ae 100644 --- a/media/mca/filterfw/native/core/native_program.h +++ b/media/mca/filterfw/native/core/native_program.h @@ -75,7 +75,8 @@ class NativeProgram { // Pointer to user data void* user_data_; - DISALLOW_COPY_AND_ASSIGN(NativeProgram); + NativeProgram(const NativeProgram&) = delete; + NativeProgram& operator=(const NativeProgram&) = delete; }; } // namespace filterfw diff --git a/packages/CarrierDefaultApp/AndroidManifest.xml b/packages/CarrierDefaultApp/AndroidManifest.xml index 2e642ec63cac..8df194c11800 100644 --- a/packages/CarrierDefaultApp/AndroidManifest.xml +++ b/packages/CarrierDefaultApp/AndroidManifest.xml @@ -25,7 +25,6 @@ <uses-permission android:name="android.permission.MODIFY_PHONE_STATE" /> <uses-permission android:name="android.permission.READ_PRIVILEGED_PHONE_STATE" /> <uses-permission android:name="android.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS" /> - <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" /> <uses-permission android:name="android.permission.SUBSTITUTE_NOTIFICATION_APP_NAME" /> <application android:label="@string/app_name" > @@ -34,10 +33,16 @@ <action android:name="com.android.internal.telephony.CARRIER_SIGNAL_REDIRECTED" /> </intent-filter> </receiver> - <activity android:name="com.android.carrierdefaultapp.CaptivePortalLaunchActivity" - android:theme="@android:style/Theme.Translucent.NoTitleBar" - android:excludeFromRecents="true"/> <service android:name="com.android.carrierdefaultapp.ProvisionObserver" android:permission="android.permission.BIND_JOB_SERVICE"/> + <activity + android:name="com.android.carrierdefaultapp.CaptivePortalLoginActivity" + android:label="@string/action_bar_label" + android:theme="@style/AppTheme" + android:configChanges="keyboardHidden|orientation|screenSize" > + <intent-filter> + <category android:name="android.intent.category.DEFAULT"/> + </intent-filter> + </activity> </application> </manifest> diff --git a/packages/CarrierDefaultApp/assets/quantum_ic_warning_amber_96.png b/packages/CarrierDefaultApp/assets/quantum_ic_warning_amber_96.png Binary files differnew file mode 100644 index 000000000000..08294cee4587 --- /dev/null +++ b/packages/CarrierDefaultApp/assets/quantum_ic_warning_amber_96.png diff --git a/packages/CarrierDefaultApp/res/drawable/ic_sim_card.xml b/packages/CarrierDefaultApp/res/drawable/ic_sim_card.xml index dc54fe2a3fac..75aa40522a8e 100644 --- a/packages/CarrierDefaultApp/res/drawable/ic_sim_card.xml +++ b/packages/CarrierDefaultApp/res/drawable/ic_sim_card.xml @@ -22,4 +22,4 @@ <path android:fillColor="#757575" android:pathData="M18,2h-8L4.02,8 4,20c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2L20,4c0,-1.1 -0.9,-2 -2,-2zM13,17h-2v-2h2v2zM13,13h-2L11,8h2v5z"/> -</vector>
\ No newline at end of file +</vector> diff --git a/packages/CarrierDefaultApp/res/layout/activity_captive_portal_login.xml b/packages/CarrierDefaultApp/res/layout/activity_captive_portal_login.xml new file mode 100644 index 000000000000..528576b57e5a --- /dev/null +++ b/packages/CarrierDefaultApp/res/layout/activity_captive_portal_login.xml @@ -0,0 +1,34 @@ +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:id="@+id/container" + android:layout_width="match_parent" + android:layout_height="match_parent" + tools:context="com.android.carrierdefaultapp.CaptivePortalLoginActivity" + tools:ignore="MergeRootFrame"> + <LinearLayout + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical" > + + <TextView + android:id="@+id/url_bar" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:textSize="20sp" + android:singleLine="true" /> + + <ProgressBar + android:id="@+id/progress_bar" + android:layout_width="match_parent" + android:layout_height="wrap_content" + style="?android:attr/progressBarStyleHorizontal" /> + + <WebView + android:id="@+id/webview" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_alignParentBottom="false" + android:layout_alignParentRight="false" /> + +</LinearLayout> +</FrameLayout> diff --git a/packages/CarrierDefaultApp/res/values/dimens.xml b/packages/CarrierDefaultApp/res/values/dimens.xml index a3c5049bfd8b..1ea8c351293d 100644 --- a/packages/CarrierDefaultApp/res/values/dimens.xml +++ b/packages/CarrierDefaultApp/res/values/dimens.xml @@ -1,3 +1,6 @@ <resources> <dimen name="glif_icon_size">32dp</dimen> + <!-- Default screen margins, per the Android Design guidelines. --> + <dimen name="activity_horizontal_margin">16dp</dimen> + <dimen name="activity_vertical_margin">16dp</dimen> </resources> diff --git a/packages/CarrierDefaultApp/res/values/strings.xml b/packages/CarrierDefaultApp/res/values/strings.xml index f904600df772..f9342b7c5899 100644 --- a/packages/CarrierDefaultApp/res/values/strings.xml +++ b/packages/CarrierDefaultApp/res/values/strings.xml @@ -6,9 +6,8 @@ <string name="no_data_notification_id">Your mobile data has been deactivated</string> <string name="portal_notification_detail">Tap to visit the %s website</string> <string name="no_data_notification_detail">Please contact your service provider %s</string> - <string name="progress_dialogue_network_connection">Connecting to captive portal...</string> - <string name="alert_dialogue_network_timeout">Network timeout, would you like to retry?</string> - <string name="alert_dialogue_network_timeout_title">Network unavailable</string> - <string name="quit">Quit</string> - <string name="wait">Wait</string> + <string name="action_bar_label">Sign in to mobile network</string> + <string name="ssl_error_warning">The network you’re trying to join has security issues.</string> + <string name="ssl_error_example">For example, the login page may not belong to the organization shown.</string> + <string name="ssl_error_continue">Continue anyway via browser</string> </resources> diff --git a/packages/CarrierDefaultApp/res/values/styles.xml b/packages/CarrierDefaultApp/res/values/styles.xml index 3d2691505f56..939c1aa4c5da 100644 --- a/packages/CarrierDefaultApp/res/values/styles.xml +++ b/packages/CarrierDefaultApp/res/values/styles.xml @@ -1,3 +1,16 @@ <resources> - <style name="AlertDialog" parent="android:Theme.Material.Light.Dialog.Alert"/> + <style name="AppBaseTheme" parent="@android:style/Theme.Material.Settings"> + <!-- + Theme customizations available in newer API levels can go in + res/values-vXX/styles.xml, while customizations related to + backward-compatibility can go here. + --> + </style> + + <!-- Application theme. --> + <style name="AppTheme" parent="AppBaseTheme"> + <!-- All customizations that are NOT specific to a particular API-level can go here. --> + <!-- Setting's theme's accent color makes ProgressBar useless, reset back. --> + <item name="android:colorAccent">@*android:color/material_deep_teal_500</item> + </style> </resources> diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CaptivePortalLaunchActivity.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CaptivePortalLaunchActivity.java deleted file mode 100644 index 28251cb394e7..000000000000 --- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CaptivePortalLaunchActivity.java +++ /dev/null @@ -1,233 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.carrierdefaultapp; - -import android.app.Activity; -import android.app.AlertDialog; -import android.app.ProgressDialog; -import android.content.DialogInterface; -import android.content.Intent; -import android.net.CaptivePortal; -import android.net.ConnectivityManager; -import android.net.Network; -import android.net.NetworkCapabilities; -import android.net.NetworkInfo; -import android.net.NetworkRequest; -import android.os.Bundle; -import android.telephony.CarrierConfigManager; -import android.telephony.Rlog; -import android.telephony.SubscriptionManager; -import android.text.TextUtils; -import android.net.ICaptivePortal; -import android.view.ContextThemeWrapper; -import android.view.WindowManager; -import com.android.carrierdefaultapp.R; -import com.android.internal.telephony.PhoneConstants; -import com.android.internal.telephony.TelephonyIntents; -import com.android.internal.util.ArrayUtils; - -import static android.net.CaptivePortal.APP_RETURN_DISMISSED; - -/** - * Activity that launches in response to the captive portal notification - * @see com.android.carrierdefaultapp.CarrierActionUtils#CARRIER_ACTION_SHOW_PORTAL_NOTIFICATION - * This activity requests network connection if there is no available one, launches the - * {@link com.android.captiveportallogin portalApp} and keeps track of the portal activation result. - */ -public class CaptivePortalLaunchActivity extends Activity { - private static final String TAG = CaptivePortalLaunchActivity.class.getSimpleName(); - private static final boolean DBG = true; - public static final int NETWORK_REQUEST_TIMEOUT_IN_MS = 5 * 1000; - - private ConnectivityManager mCm = null; - private ConnectivityManager.NetworkCallback mCb = null; - /* Progress dialogue when request network connection for captive portal */ - private AlertDialog mProgressDialog = null; - /* Alert dialogue when network request is timeout */ - private AlertDialog mAlertDialog = null; - - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - mCm = ConnectivityManager.from(this); - // Check network connection before loading portal - Network network = getNetworkForCaptivePortal(); - NetworkInfo nwInfo = mCm.getNetworkInfo(network); - if (nwInfo == null || !nwInfo.isConnected()) { - if (DBG) logd("Network unavailable, request restricted connection"); - requestNetwork(getIntent()); - } else { - launchCaptivePortal(getIntent(), network); - } - } - - // show progress dialog during network connecting - private void showConnectingProgressDialog() { - mProgressDialog = new ProgressDialog(getApplicationContext()); - mProgressDialog.setMessage(getString(R.string.progress_dialogue_network_connection)); - mProgressDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY); - mProgressDialog.show(); - } - - // if network request is timeout, show alert dialog with two option: cancel & wait - private void showConnectionTimeoutAlertDialog() { - mAlertDialog = new AlertDialog.Builder(new ContextThemeWrapper(this, R.style.AlertDialog)) - .setMessage(getString(R.string.alert_dialogue_network_timeout)) - .setTitle(getString(R.string.alert_dialogue_network_timeout_title)) - .setNegativeButton(getString(R.string.quit), - new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - // cancel - dismissDialog(mAlertDialog); - finish(); - } - }) - .setPositiveButton(getString(R.string.wait), - new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - // wait, request network again - dismissDialog(mAlertDialog); - requestNetwork(getIntent()); - } - }) - .create(); - mAlertDialog.show(); - } - - private void requestNetwork(final Intent intent) { - NetworkRequest request = new NetworkRequest.Builder() - .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR) - .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) - .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED) - .build(); - - mCb = new ConnectivityManager.NetworkCallback() { - @Override - public void onAvailable(Network network) { - if (DBG) logd("Network available: " + network); - dismissDialog(mProgressDialog); - mCm.bindProcessToNetwork(network); - launchCaptivePortal(intent, network); - } - - @Override - public void onUnavailable() { - if (DBG) logd("Network unavailable"); - dismissDialog(mProgressDialog); - showConnectionTimeoutAlertDialog(); - } - }; - showConnectingProgressDialog(); - mCm.requestNetwork(request, mCb, NETWORK_REQUEST_TIMEOUT_IN_MS); - } - - private void releaseNetworkRequest() { - logd("release Network Request"); - if (mCb != null) { - mCm.unregisterNetworkCallback(mCb); - mCb = null; - } - } - - private void dismissDialog(AlertDialog dialog) { - if (dialog != null) { - dialog.dismiss(); - } - } - - private Network getNetworkForCaptivePortal() { - Network[] info = mCm.getAllNetworks(); - if (!ArrayUtils.isEmpty(info)) { - for (Network nw : info) { - final NetworkCapabilities nc = mCm.getNetworkCapabilities(nw); - if (nc.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) - && nc.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)) { - return nw; - } - } - } - return null; - } - - private void launchCaptivePortal(final Intent intent, Network network) { - String redirectUrl = intent.getStringExtra(TelephonyIntents.EXTRA_REDIRECTION_URL_KEY); - int subId = intent.getIntExtra(PhoneConstants.SUBSCRIPTION_KEY, - SubscriptionManager.getDefaultVoiceSubscriptionId()); - if (TextUtils.isEmpty(redirectUrl) || !matchUrl(redirectUrl, subId)) { - loge("Launch portal fails due to incorrect redirection URL: " + - Rlog.pii(TAG, redirectUrl)); - return; - } - final Intent portalIntent = new Intent(ConnectivityManager.ACTION_CAPTIVE_PORTAL_SIGN_IN); - portalIntent.putExtra(ConnectivityManager.EXTRA_NETWORK, network); - portalIntent.putExtra(ConnectivityManager.EXTRA_CAPTIVE_PORTAL, - new CaptivePortal(new ICaptivePortal.Stub() { - @Override - public void appResponse(int response) { - logd("portal response code: " + response); - releaseNetworkRequest(); - if (response == APP_RETURN_DISMISSED) { - // Upon success http response code, trigger re-evaluation - CarrierActionUtils.applyCarrierAction( - CarrierActionUtils.CARRIER_ACTION_ENABLE_RADIO, intent, - getApplicationContext()); - CarrierActionUtils.applyCarrierAction( - CarrierActionUtils.CARRIER_ACTION_ENABLE_METERED_APNS, intent, - getApplicationContext()); - CarrierActionUtils.applyCarrierAction( - CarrierActionUtils.CARRIER_ACTION_CANCEL_ALL_NOTIFICATIONS, - intent, getApplicationContext()); - } - } - })); - portalIntent.putExtra(ConnectivityManager.EXTRA_CAPTIVE_PORTAL_URL, redirectUrl); - portalIntent.setFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT | Intent.FLAG_ACTIVITY_NEW_TASK - | Intent.FLAG_ACTIVITY_CLEAR_TASK); - if (DBG) logd("launching portal"); - startActivity(portalIntent); - finish(); - } - - // match configured redirection url - private boolean matchUrl(String url, int subId) { - CarrierConfigManager configManager = getApplicationContext() - .getSystemService(CarrierConfigManager.class); - String[] redirectURLs = configManager.getConfigForSubId(subId).getStringArray( - CarrierConfigManager.KEY_CARRIER_DEFAULT_REDIRECTION_URL_STRING_ARRAY); - if (ArrayUtils.isEmpty(redirectURLs)) { - if (DBG) logd("match is unnecessary without any configured redirection url"); - return true; - } - for (String redirectURL : redirectURLs) { - if (url.startsWith(redirectURL)) { - return true; - } - } - if (DBG) loge("no match found for configured redirection url"); - return false; - } - - private static void logd(String s) { - Rlog.d(TAG, s); - } - - private static void loge(String s) { - Rlog.d(TAG, s); - } -} diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CaptivePortalLoginActivity.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CaptivePortalLoginActivity.java new file mode 100644 index 000000000000..a5820f2da56c --- /dev/null +++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CaptivePortalLoginActivity.java @@ -0,0 +1,434 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.carrierdefaultapp; + +import android.app.Activity; +import android.app.LoadedApk; +import android.content.Context; +import android.content.Intent; +import android.graphics.Bitmap; +import android.net.ConnectivityManager; +import android.net.ConnectivityManager.NetworkCallback; +import android.net.Network; +import android.net.NetworkCapabilities; +import android.net.NetworkRequest; +import android.net.Proxy; +import android.net.TrafficStats; +import android.net.Uri; +import android.net.http.SslError; +import android.os.Bundle; +import android.telephony.CarrierConfigManager; +import android.telephony.Rlog; +import android.telephony.SubscriptionManager; +import android.util.ArrayMap; +import android.util.Log; +import android.util.TypedValue; +import android.webkit.SslErrorHandler; +import android.webkit.WebChromeClient; +import android.webkit.WebSettings; +import android.webkit.WebView; +import android.webkit.WebViewClient; +import android.widget.ProgressBar; +import android.widget.TextView; + +import com.android.internal.telephony.PhoneConstants; +import com.android.internal.telephony.TelephonyIntents; +import com.android.internal.util.ArrayUtils; + +import java.io.IOException; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.net.HttpURLConnection; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Random; + +/** + * Activity that launches in response to the captive portal notification + * @see com.android.carrierdefaultapp.CarrierActionUtils#CARRIER_ACTION_SHOW_PORTAL_NOTIFICATION + * This activity requests network connection if there is no available one before loading the real + * portal page and apply carrier actions on the portal activation result. + */ +public class CaptivePortalLoginActivity extends Activity { + private static final String TAG = CaptivePortalLoginActivity.class.getSimpleName(); + private static final boolean DBG = true; + + private static final int SOCKET_TIMEOUT_MS = 10 * 1000; + public static final int NETWORK_REQUEST_TIMEOUT_MS = 5 * 1000; + + private URL mUrl; + private Network mNetwork; + private NetworkCallback mNetworkCallback; + private ConnectivityManager mCm; + private WebView mWebView; + private MyWebViewClient mWebViewClient; + private boolean mLaunchBrowser = false; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + mCm = ConnectivityManager.from(this); + mUrl = getUrlForCaptivePortal(); + if (mUrl == null) { + done(false); + return; + } + if (DBG) logd(String.format("onCreate for %s", mUrl.toString())); + setContentView(R.layout.activity_captive_portal_login); + getActionBar().setDisplayShowHomeEnabled(false); + + mWebView = (WebView) findViewById(R.id.webview); + mWebView.clearCache(true); + WebSettings webSettings = mWebView.getSettings(); + webSettings.setJavaScriptEnabled(true); + webSettings.setMixedContentMode(WebSettings.MIXED_CONTENT_COMPATIBILITY_MODE); + mWebViewClient = new MyWebViewClient(); + mWebView.setWebViewClient(mWebViewClient); + mWebView.setWebChromeClient(new MyWebChromeClient()); + + mNetwork = getNetworkForCaptivePortal(); + if (mNetwork == null) { + requestNetworkForCaptivePortal(); + } else { + mCm.bindProcessToNetwork(mNetwork); + // Start initial page load so WebView finishes loading proxy settings. + // Actual load of mUrl is initiated by MyWebViewClient. + mWebView.loadData("", "text/html", null); + } + } + + @Override + public void onBackPressed() { + WebView myWebView = (WebView) findViewById(R.id.webview); + if (myWebView.canGoBack() && mWebViewClient.allowBack()) { + myWebView.goBack(); + } else { + super.onBackPressed(); + } + } + + @Override + public void onDestroy() { + super.onDestroy(); + releaseNetworkRequest(); + if (mLaunchBrowser) { + // Give time for this network to become default. After 500ms just proceed. + for (int i = 0; i < 5; i++) { + // TODO: This misses when mNetwork underlies a VPN. + if (mNetwork.equals(mCm.getActiveNetwork())) break; + try { + Thread.sleep(100); + } catch (InterruptedException e) { + } + } + final String url = mUrl.toString(); + if (DBG) logd("starting activity with intent ACTION_VIEW for " + url); + startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(url))); + } + } + + // Find WebView's proxy BroadcastReceiver and prompt it to read proxy system properties. + private void setWebViewProxy() { + LoadedApk loadedApk = getApplication().mLoadedApk; + try { + Field receiversField = LoadedApk.class.getDeclaredField("mReceivers"); + receiversField.setAccessible(true); + ArrayMap receivers = (ArrayMap) receiversField.get(loadedApk); + for (Object receiverMap : receivers.values()) { + for (Object rec : ((ArrayMap) receiverMap).keySet()) { + Class clazz = rec.getClass(); + if (clazz.getName().contains("ProxyChangeListener")) { + Method onReceiveMethod = clazz.getDeclaredMethod("onReceive", Context.class, + Intent.class); + Intent intent = new Intent(Proxy.PROXY_CHANGE_ACTION); + onReceiveMethod.invoke(rec, getApplicationContext(), intent); + Log.v(TAG, "Prompting WebView proxy reload."); + } + } + } + } catch (Exception e) { + loge("Exception while setting WebView proxy: " + e); + } + } + + private void done(boolean success) { + if (DBG) logd(String.format("Result success %b for %s", success, mUrl.toString())); + if (success) { + // Trigger re-evaluation upon success http response code + CarrierActionUtils.applyCarrierAction( + CarrierActionUtils.CARRIER_ACTION_ENABLE_RADIO, getIntent(), + getApplicationContext()); + CarrierActionUtils.applyCarrierAction( + CarrierActionUtils.CARRIER_ACTION_ENABLE_METERED_APNS, getIntent(), + getApplicationContext()); + CarrierActionUtils.applyCarrierAction( + CarrierActionUtils.CARRIER_ACTION_CANCEL_ALL_NOTIFICATIONS, getIntent(), + getApplicationContext()); + + } + finishAndRemoveTask(); + } + + private URL getUrlForCaptivePortal() { + String url = getIntent().getStringExtra(TelephonyIntents.EXTRA_REDIRECTION_URL_KEY); + if (url.isEmpty()) { + url = mCm.getCaptivePortalServerUrl(); + } + final CarrierConfigManager configManager = getApplicationContext() + .getSystemService(CarrierConfigManager.class); + final int subId = getIntent().getIntExtra(PhoneConstants.SUBSCRIPTION_KEY, + SubscriptionManager.getDefaultVoiceSubscriptionId()); + final String[] portalURLs = configManager.getConfigForSubId(subId).getStringArray( + CarrierConfigManager.KEY_CARRIER_DEFAULT_REDIRECTION_URL_STRING_ARRAY); + if (!ArrayUtils.isEmpty(portalURLs)) { + for (String portalUrl : portalURLs) { + if (url.startsWith(portalUrl)) { + break; + } + } + url = null; + } + try { + return new URL(url); + } catch (MalformedURLException e) { + loge("Invalid captive portal URL " + url); + } + return null; + } + + private void testForCaptivePortal() { + new Thread(new Runnable() { + public void run() { + // Give time for captive portal to open. + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + } + HttpURLConnection urlConnection = null; + int httpResponseCode = 500; + int oldTag = TrafficStats.getAndSetThreadStatsTag(TrafficStats.TAG_SYSTEM_PROBE); + try { + urlConnection = (HttpURLConnection) mNetwork.openConnection(mUrl); + urlConnection.setInstanceFollowRedirects(false); + urlConnection.setConnectTimeout(SOCKET_TIMEOUT_MS); + urlConnection.setReadTimeout(SOCKET_TIMEOUT_MS); + urlConnection.setUseCaches(false); + urlConnection.getInputStream(); + httpResponseCode = urlConnection.getResponseCode(); + } catch (IOException e) { + } finally { + if (urlConnection != null) urlConnection.disconnect(); + TrafficStats.setThreadStatsTag(oldTag); + } + if (httpResponseCode == 204) { + done(true); + } + } + }).start(); + } + + private Network getNetworkForCaptivePortal() { + Network[] info = mCm.getAllNetworks(); + if (!ArrayUtils.isEmpty(info)) { + for (Network nw : info) { + final NetworkCapabilities nc = mCm.getNetworkCapabilities(nw); + if (nc.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) + && nc.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)) { + return nw; + } + } + } + return null; + } + + private void requestNetworkForCaptivePortal() { + NetworkRequest request = new NetworkRequest.Builder() + .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR) + .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) + .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED) + .build(); + + mNetworkCallback = new ConnectivityManager.NetworkCallback() { + @Override + public void onAvailable(Network network) { + if (DBG) logd("Network available: " + network); + mCm.bindProcessToNetwork(network); + mNetwork = network; + runOnUiThreadIfNotFinishing(() -> { + // Start initial page load so WebView finishes loading proxy settings. + // Actual load of mUrl is initiated by MyWebViewClient. + mWebView.loadData("", "text/html", null); + }); + } + + @Override + public void onUnavailable() { + if (DBG) logd("Network unavailable"); + runOnUiThreadIfNotFinishing(() -> { + // Instead of not loading anything in webview, simply load the page and return + // HTTP error page in the absence of network connection. + mWebView.loadUrl(mUrl.toString()); + }); + } + }; + logd("request Network for captive portal"); + mCm.requestNetwork(request, mNetworkCallback, NETWORK_REQUEST_TIMEOUT_MS); + } + + private void releaseNetworkRequest() { + logd("release Network for captive portal"); + if (mNetworkCallback != null) { + mCm.unregisterNetworkCallback(mNetworkCallback); + mNetworkCallback = null; + mNetwork = null; + } + } + + private class MyWebViewClient extends WebViewClient { + private static final String INTERNAL_ASSETS = "file:///android_asset/"; + private final String mBrowserBailOutToken = Long.toString(new Random().nextLong()); + // How many Android device-independent-pixels per scaled-pixel + // dp/sp = (px/sp) / (px/dp) = (1/sp) / (1/dp) + private final float mDpPerSp = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 1, + getResources().getDisplayMetrics()) + / TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 1, + getResources().getDisplayMetrics()); + private int mPagesLoaded; + + // If we haven't finished cleaning up the history, don't allow going back. + public boolean allowBack() { + return mPagesLoaded > 1; + } + + @Override + public void onPageStarted(WebView view, String url, Bitmap favicon) { + if (url.contains(mBrowserBailOutToken)) { + mLaunchBrowser = true; + done(false); + return; + } + // The first page load is used only to cause the WebView to + // fetch the proxy settings. Don't update the URL bar, and + // don't check if the captive portal is still there. + if (mPagesLoaded == 0) return; + // For internally generated pages, leave URL bar listing prior URL as this is the URL + // the page refers to. + if (!url.startsWith(INTERNAL_ASSETS)) { + final TextView myUrlBar = (TextView) findViewById(R.id.url_bar); + myUrlBar.setText(url); + } + if (mNetwork != null) { + testForCaptivePortal(); + } + } + + @Override + public void onPageFinished(WebView view, String url) { + mPagesLoaded++; + if (mPagesLoaded == 1) { + // Now that WebView has loaded at least one page we know it has read in the proxy + // settings. Now prompt the WebView read the Network-specific proxy settings. + setWebViewProxy(); + // Load the real page. + view.loadUrl(mUrl.toString()); + return; + } else if (mPagesLoaded == 2) { + // Prevent going back to empty first page. + view.clearHistory(); + } + if (mNetwork != null) { + testForCaptivePortal(); + } + } + + // Convert Android device-independent-pixels (dp) to HTML size. + private String dp(int dp) { + // HTML px's are scaled just like dp's, so just add "px" suffix. + return Integer.toString(dp) + "px"; + } + + // Convert Android scaled-pixels (sp) to HTML size. + private String sp(int sp) { + // Convert sp to dp's. + float dp = sp * mDpPerSp; + // Apply a scale factor to make things look right. + dp *= 1.3; + // Convert dp's to HTML size. + return dp((int) dp); + } + + // A web page consisting of a large broken lock icon to indicate SSL failure. + private final String SSL_ERROR_HTML = "<html><head><style>" + + "body { margin-left:" + dp(48) + "; margin-right:" + dp(48) + "; " + + "margin-top:" + dp(96) + "; background-color:#fafafa; }" + + "img { width:" + dp(48) + "; height:" + dp(48) + "; }" + + "div.warn { font-size:" + sp(16) + "; margin-top:" + dp(16) + "; " + + " opacity:0.87; line-height:1.28; }" + + "div.example { font-size:" + sp(14) + "; margin-top:" + dp(16) + "; " + + " opacity:0.54; line-height:1.21905; }" + + "a { font-size:" + sp(14) + "; text-decoration:none; text-transform:uppercase; " + + " margin-top:" + dp(24) + "; display:inline-block; color:#4285F4; " + + " height:" + dp(48) + "; font-weight:bold; }" + + "</style></head><body><p><img src=quantum_ic_warning_amber_96.png><br>" + + "<div class=warn>%s</div>" + + "<div class=example>%s</div>" + "<a href=%s>%s</a></body></html>"; + + @Override + public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) { + Log.w(TAG, "SSL error (error: " + error.getPrimaryError() + " host: " + // Only show host to avoid leaking private info. + + Uri.parse(error.getUrl()).getHost() + " certificate: " + + error.getCertificate() + "); displaying SSL warning."); + final String html = String.format(SSL_ERROR_HTML, getString(R.string.ssl_error_warning), + getString(R.string.ssl_error_example), mBrowserBailOutToken, + getString(R.string.ssl_error_continue)); + view.loadDataWithBaseURL(INTERNAL_ASSETS, html, "text/HTML", "UTF-8", null); + } + + @Override + public boolean shouldOverrideUrlLoading(WebView view, String url) { + if (url.startsWith("tel:")) { + startActivity(new Intent(Intent.ACTION_DIAL, Uri.parse(url))); + return true; + } + return false; + } + } + + private class MyWebChromeClient extends WebChromeClient { + @Override + public void onProgressChanged(WebView view, int newProgress) { + final ProgressBar myProgressBar = (ProgressBar) findViewById(R.id.progress_bar); + myProgressBar.setProgress(newProgress); + } + } + + private void runOnUiThreadIfNotFinishing(Runnable r) { + if (!isFinishing()) { + runOnUiThread(r); + } + } + + private static void logd(String s) { + Rlog.d(TAG, s); + } + + private static void loge(String s) { + Rlog.d(TAG, s); + } + +} diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CarrierActionUtils.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CarrierActionUtils.java index d9bd2fcc0acb..73ff3a9b5d1e 100644 --- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CarrierActionUtils.java +++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CarrierActionUtils.java @@ -112,8 +112,10 @@ public class CarrierActionUtils { logd("onShowCaptivePortalNotification"); final NotificationManager notificationMgr = context.getSystemService( NotificationManager.class); - Intent portalIntent = new Intent(context, CaptivePortalLaunchActivity.class); + Intent portalIntent = new Intent(context, CaptivePortalLoginActivity.class); portalIntent.putExtras(intent); + portalIntent.setFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT + | Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, portalIntent, PendingIntent.FLAG_UPDATE_CURRENT); Notification notification = getNotification(context, R.string.portal_notification_id, diff --git a/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/LaunchCaptivePortalActivityTest.java b/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/LaunchCaptivePortalActivityTest.java deleted file mode 100644 index 8a18d7229435..000000000000 --- a/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/LaunchCaptivePortalActivityTest.java +++ /dev/null @@ -1,108 +0,0 @@ -package com.android.carrierdefaultapp; - -import android.annotation.TargetApi; -import android.content.Intent; -import android.net.ConnectivityManager; -import android.net.Network; -import android.net.NetworkCapabilities; -import android.net.NetworkInfo; -import android.net.NetworkRequest; - -import com.android.internal.telephony.TelephonyIntents; - -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.mockito.ArgumentCaptor; -import org.mockito.Captor; -import org.mockito.Mock; - -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.anyInt; -import static org.mockito.Matchers.eq; -import static org.mockito.Mockito.atLeast; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; - -public class LaunchCaptivePortalActivityTest extends - CarrierDefaultActivityTestCase<CaptivePortalLaunchActivity> { - - @Mock - private ConnectivityManager mCm; - @Mock - private NetworkInfo mNetworkInfo; - @Mock - private Network mNetwork; - - @Captor - private ArgumentCaptor<Integer> mInt; - @Captor - private ArgumentCaptor<NetworkRequest> mNetworkReq; - - private NetworkCapabilities mNetworkCapabilities; - - public LaunchCaptivePortalActivityTest() { - super(CaptivePortalLaunchActivity.class); - } - - @Before - public void setUp() throws Exception { - super.setUp(); - injectSystemService(ConnectivityManager.class, mCm); - } - - @After - public void tearDown() throws Exception { - super.tearDown(); - } - - @Override - protected Intent createActivityIntent() { - Intent intent = new Intent(getInstrumentation().getTargetContext(), - CaptivePortalLaunchActivity.class); - intent.putExtra(TelephonyIntents.EXTRA_REDIRECTION_URL_KEY, "url"); - return intent; - } - - @Test - public void testWithoutInternetConnection() throws Throwable { - startActivity(); - TestContext.waitForMs(100); - verify(mCm, atLeast(1)).requestNetwork(mNetworkReq.capture(), any(), mInt.capture()); - // verify network request - assert(mNetworkReq.getValue().networkCapabilities.hasCapability( - NetworkCapabilities.NET_CAPABILITY_INTERNET)); - assert(mNetworkReq.getValue().networkCapabilities.hasTransport( - NetworkCapabilities.TRANSPORT_CELLULAR)); - assertFalse(mNetworkReq.getValue().networkCapabilities.hasCapability( - NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)); - assertEquals(CaptivePortalLaunchActivity.NETWORK_REQUEST_TIMEOUT_IN_MS, - (int) mInt.getValue()); - // verify captive portal app is not launched due to unavailable network - assertNull(getStartedActivityIntent()); - stopActivity(); - } - - @Test - public void testWithInternetConnection() throws Throwable { - // Mock internet connection - mNetworkCapabilities = new NetworkCapabilities() - .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) - .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR); - doReturn(new Network[]{mNetwork}).when(mCm).getAllNetworks(); - doReturn(mNetworkCapabilities).when(mCm).getNetworkCapabilities(eq(mNetwork)); - doReturn(mNetworkInfo).when(mCm).getNetworkInfo(eq(mNetwork)); - doReturn(true).when(mNetworkInfo).isConnected(); - - startActivity(); - TestContext.waitForMs(100); - // verify there is no network request with internet connection - verify(mCm, times(0)).requestNetwork(any(), any(), anyInt()); - // verify captive portal app is launched - assertNotNull(getStartedActivityIntent()); - assertEquals(ConnectivityManager.ACTION_CAPTIVE_PORTAL_SIGN_IN, - getStartedActivityIntent().getAction()); - stopActivity(); - } -} diff --git a/packages/MtpDocumentsProvider/tests/Android.mk b/packages/MtpDocumentsProvider/tests/Android.mk index e50d6fb693c9..148cd0d930ab 100644 --- a/packages/MtpDocumentsProvider/tests/Android.mk +++ b/packages/MtpDocumentsProvider/tests/Android.mk @@ -8,5 +8,6 @@ LOCAL_STATIC_JAVA_LIBRARIES := junit legacy-android-test LOCAL_PACKAGE_NAME := MtpDocumentsProviderTests LOCAL_INSTRUMENTATION_FOR := MtpDocumentsProvider LOCAL_CERTIFICATE := media +LOCAL_COMPATIBILITY_SUITE := device-tests include $(BUILD_PACKAGE) diff --git a/packages/PrintSpooler/tests/outofprocess/Android.mk b/packages/PrintSpooler/tests/outofprocess/Android.mk index d1d0ee40ba01..3c02453c78a1 100644 --- a/packages/PrintSpooler/tests/outofprocess/Android.mk +++ b/packages/PrintSpooler/tests/outofprocess/Android.mk @@ -24,5 +24,6 @@ LOCAL_JAVA_LIBRARIES := android.test.runner LOCAL_STATIC_JAVA_LIBRARIES := android-support-test ub-uiautomator mockito-target-minus-junit4 LOCAL_PACKAGE_NAME := PrintSpoolerOutOfProcessTests +LOCAL_COMPATIBILITY_SUITE := device-tests include $(BUILD_PACKAGE) diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml index f77d466ef87e..c64574fde216 100644 --- a/packages/SettingsLib/res/values/strings.xml +++ b/packages/SettingsLib/res/values/strings.xml @@ -422,11 +422,11 @@ <!-- Setting Checkbox title whether to enable WiFi Verbose Logging. [CHAR LIMIT=40] --> <string name="wifi_verbose_logging">Enable Wi\u2011Fi Verbose Logging</string> <!-- Setting Checkbox title whether to enable WiFi Aggressive Handover. [CHAR LIMIT=40] --> - <string name="wifi_aggressive_handover">Aggressive Wi\u2011Fi to Cellular handover</string> + <string name="wifi_aggressive_handover">Aggressive Wi\u2011Fi to mobile handover</string> <!-- Setting Checkbox title whether to enable WiFi Scanning in the presence of traffic. [CHAR LIMIT=80] --> <string name="wifi_allow_scan_with_traffic">Always allow Wi\u2011Fi Roam Scans</string> - <!-- Setting Checkbox title whether to always keep cellular data active. [CHAR LIMIT=80] --> - <string name="mobile_data_always_on">Cellular data always active</string> + <!-- Setting Checkbox title whether to always keep mobile data active. [CHAR LIMIT=80] --> + <string name="mobile_data_always_on">Mobile data always active</string> <!-- Setting Checkbox title for disabling Bluetooth absolute volume --> <string name="bluetooth_disable_absolute_volume">Disable absolute volume</string> @@ -463,7 +463,7 @@ <!-- Setting Checkbox summary whether to enable Wifi verbose Logging [CHAR LIMIT=80] --> <string name="wifi_verbose_logging_summary">Increase Wi\u2011Fi logging level, show per SSID RSSI in Wi\u2011Fi Picker</string> <!-- Setting Checkbox summary whether to enable Wifi aggressive handover [CHAR LIMIT=130] --> - <string name="wifi_aggressive_handover_summary">When enabled, Wi\u2011Fi will be more aggressive in handing over the data connection to Cellular, when Wi\u2011Fi signal is low</string> + <string name="wifi_aggressive_handover_summary">When enabled, Wi\u2011Fi will be more aggressive in handing over the data connection to mobile, when Wi\u2011Fi signal is low</string> <!-- Setting Checkbox summary whether to always allow WiFi Roam Scans [CHAR LIMIT=130] --> <string name="wifi_allow_scan_with_traffic_summary">Allow/Disallow Wi\u2011Fi Roam Scans based on the amount of data traffic present at the interface</string> <!-- UI debug setting: limit size of Android logger buffers --> diff --git a/packages/SettingsLib/src/com/android/settingslib/drawer/CategoryManager.java b/packages/SettingsLib/src/com/android/settingslib/drawer/CategoryManager.java index fa5ba73b318f..ee7885d2a077 100644 --- a/packages/SettingsLib/src/com/android/settingslib/drawer/CategoryManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/drawer/CategoryManager.java @@ -132,7 +132,7 @@ public class CategoryManager { mCategoryByKeyMap.put(category.key, category); } backwardCompatCleanupForCategory(mTileByComponentCache, mCategoryByKeyMap); - normalizePriority(context, mCategoryByKeyMap); + sortCategories(context, mCategoryByKeyMap); filterDuplicateTiles(mCategoryByKeyMap); } } @@ -188,17 +188,17 @@ public class CategoryManager { } /** - * Normalize priority values on tiles across injected from all apps to make sure they don't set - * the same priority value. However internal tiles' priority remains unchanged. + * Sort the tiles injected from all apps such that if they have the same priority value, + * they wil lbe sorted by package name. * <p/> - * A list of tiles are considered normalized when their priority value increases in a linear + * A list of tiles are considered sorted when their priority value decreases in a linear * scan. */ @VisibleForTesting - synchronized void normalizePriority(Context context, + synchronized void sortCategories(Context context, Map<String, DashboardCategory> categoryByKeyMap) { for (Entry<String, DashboardCategory> categoryEntry : categoryByKeyMap.entrySet()) { - normalizePriorityForExternalTiles(context, categoryEntry.getValue()); + sortCategoriesForExternalTiles(context, categoryEntry.getValue()); } } @@ -228,39 +228,34 @@ public class CategoryManager { } /** - * Normalize priority value for tiles within a single {@code DashboardCategory}. + * Sort priority value for tiles within a single {@code DashboardCategory}. * - * @see #normalizePriority(Context, Map) + * @see #sortCategories(Context, Map) */ - private synchronized void normalizePriorityForExternalTiles(Context context, + private synchronized void sortCategoriesForExternalTiles(Context context, DashboardCategory dashboardCategory) { final String skipPackageName = context.getPackageName(); - // Sort tiles based on [package, priority within package] + // Sort tiles based on [priority, package within priority] Collections.sort(dashboardCategory.tiles, (tile1, tile2) -> { final String package1 = tile1.intent.getComponent().getPackageName(); final String package2 = tile2.intent.getComponent().getPackageName(); final int packageCompare = CASE_INSENSITIVE_ORDER.compare(package1, package2); - // First sort by package name + // First sort by priority + final int priorityCompare = tile2.priority - tile1.priority; + if (priorityCompare != 0) { + return priorityCompare; + } + // Then sort by package name, skip package take precedence if (packageCompare != 0) { - return packageCompare; - } else if (TextUtils.equals(package1, skipPackageName)) { - return 0; + if (TextUtils.equals(package1, skipPackageName)) { + return -1; + } + if (TextUtils.equals(package2, skipPackageName)) { + return 1; + } } - // Then sort by priority - return tile1.priority - tile2.priority; + return packageCompare; }); - // Update priority for all items so no package define the same priority value. - final int count = dashboardCategory.tiles.size(); - for (int i = 0; i < count; i++) { - final String packageName = - dashboardCategory.tiles.get(i).intent.getComponent().getPackageName(); - if (TextUtils.equals(packageName, skipPackageName)) { - // We skip this tile because it's a intent pointing to our own app. We trust the - // priority is set correctly, so don't normalize. - continue; - } - dashboardCategory.tiles.get(i).priority = i; - } } } diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java index 6fe581eab86d..48f3e2a855f3 100644 --- a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java +++ b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java @@ -47,6 +47,7 @@ import android.text.TextUtils; import android.text.style.TtsSpan; import android.util.Log; +import com.android.internal.annotations.VisibleForTesting; import com.android.settingslib.R; import java.util.ArrayList; @@ -112,9 +113,14 @@ public class AccessPoint implements Comparable<AccessPoint> { private static final int PSK_WPA2 = 2; private static final int PSK_WPA_WPA2 = 3; - public static final int SIGNAL_LEVELS = 4; + /** + * The number of distinct wifi levels. + * + * <p>Must keep in sync with {@link R.array.wifi_signal} and {@link WifiManager#RSSI_LEVELS}. + */ + public static final int SIGNAL_LEVELS = 5; - static final int UNREACHABLE_RSSI = Integer.MAX_VALUE; + public static final int UNREACHABLE_RSSI = Integer.MIN_VALUE; private final Context mContext; @@ -170,8 +176,8 @@ public class AccessPoint implements Comparable<AccessPoint> { } } update(mConfig, mInfo, mNetworkInfo); - mRssi = getRssi(); - mSeen = getSeen(); + updateRssi(); + updateSeen(); mId = sLastId.incrementAndGet(); } @@ -369,27 +375,57 @@ public class AccessPoint implements Comparable<AccessPoint> { return mInfo; } + /** + * Returns the number of levels to show for a Wifi icon, from 0 to {@link #SIGNAL_LEVELS}-1. + * + * <p>Use {@#isReachable()} to determine if an AccessPoint is in range, as this method will + * always return at least 0. + */ public int getLevel() { - if (!isReachable()) { - return -1; - } return WifiManager.calculateSignalLevel(mRssi, SIGNAL_LEVELS); } public int getRssi() { + return mRssi; + } + + /** + * Updates {@link #mRssi}. + * + * <p>If the given connection is active, the existing value of {@link #mRssi} will be returned. + * If the given AccessPoint is not active, a value will be calculated from previous scan + * results, returning the best RSSI for all matching AccessPoints. If the access point is not + * connected and there are no scan results, the rssi will be set to {@link #UNREACHABLE_RSSI}. + * + * <p>Old scan results will be evicted from the cache when this method is invoked. + */ + private void updateRssi() { evictOldScanResults(); - int rssi = Integer.MIN_VALUE; + + if (this.isActive()) { + return; + } + + int rssi = UNREACHABLE_RSSI; for (ScanResult result : mScanResultCache.values()) { if (result.level > rssi) { rssi = result.level; } } - return rssi; + mRssi = rssi; } - public long getSeen() { + /** + * Updates {@link #mSeen} based on the scan result cache. + * + * <p>Old scan results will be evicted from the cache when this method is invoked. + */ + private void updateSeen() { evictOldScanResults(); + + // TODO(sghuman): Set to now if connected + long seen = 0; for (ScanResult result : mScanResultCache.values()) { if (result.timestamp > seen) { @@ -397,7 +433,7 @@ public class AccessPoint implements Comparable<AccessPoint> { } } - return seen; + mSeen = seen; } public NetworkInfo getNetworkInfo() { @@ -822,13 +858,12 @@ public class AccessPoint implements Comparable<AccessPoint> { boolean update(ScanResult result) { if (matches(result)) { + int oldLevel = getLevel(); + /* Add or update the scan result for the BSSID */ mScanResultCache.put(result.BSSID, result); - - int oldLevel = getLevel(); - int oldRssi = getRssi(); - mSeen = getSeen(); - mRssi = (getRssi() + oldRssi)/2; + updateSeen(); + updateRssi(); int newLevel = getLevel(); if (newLevel > 0 && newLevel != oldLevel && mAccessPointListener != null) { @@ -848,7 +883,7 @@ public class AccessPoint implements Comparable<AccessPoint> { return false; } - boolean update(WifiConfiguration config, WifiInfo info, NetworkInfo networkInfo) { + public boolean update(WifiConfiguration config, WifiInfo info, NetworkInfo networkInfo) { boolean reorder = false; if (info != null && isInfoForThisAccessPoint(config, info)) { reorder = (mInfo == null); @@ -877,10 +912,16 @@ public class AccessPoint implements Comparable<AccessPoint> { } } + @VisibleForTesting void setRssi(int rssi) { mRssi = rssi; } + /** Sets the rssi to {@link #UNREACHABLE_RSSI}. */ + void setUnreachable() { + setRssi(AccessPoint.UNREACHABLE_RSSI); + } + int getRankingScore() { return mRankingScore; } @@ -890,7 +931,7 @@ public class AccessPoint implements Comparable<AccessPoint> { } /** Return true if the current RSSI is reachable, and false otherwise. */ - boolean isReachable() { + public boolean isReachable() { return mRssi != UNREACHABLE_RSSI; } diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java index 50972c709863..c9fa0170e7c4 100644 --- a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java +++ b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java @@ -223,8 +223,7 @@ public class AccessPointPreference extends Preference { } final Context context = getContext(); - int level = WifiManager.calculateSignalLevel( - mAccessPoint.getRssi(), WifiManager.RSSI_LEVELS); + int level = mAccessPoint.getLevel(); int wifiBadge = mAccessPoint.getBadge(); if (level != mLevel || wifiBadge != mWifiBadge) { mLevel = level; diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java index 8421c2c9dee3..55c886e6fb1c 100644 --- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java +++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java @@ -471,7 +471,8 @@ public class WifiTracker { accessPoint.update(connectionConfig, mLastInfo, mLastNetworkInfo); } if (mIncludeSaved) { - // If saved network not present in scan result then set its Rssi to MAX_VALUE + // If saved network not present in scan result then set its Rssi to + // UNREACHABLE_RSSI boolean apFound = false; for (ScanResult result : results) { if (result.SSID.equals(accessPoint.getSsidStr())) { @@ -480,7 +481,7 @@ public class WifiTracker { } } if (!apFound) { - accessPoint.setRssi(Integer.MAX_VALUE); + accessPoint.setUnreachable(); } accessPoints.add(accessPoint); apMap.put(accessPoint.getSsidStr(), accessPoint); diff --git a/packages/SettingsLib/tests/integ/Android.mk b/packages/SettingsLib/tests/integ/Android.mk index 60d1c770d0de..7ace048324c3 100644 --- a/packages/SettingsLib/tests/integ/Android.mk +++ b/packages/SettingsLib/tests/integ/Android.mk @@ -25,6 +25,7 @@ LOCAL_JAVA_LIBRARIES := android.test.runner telephony-common LOCAL_JACK_FLAGS := --multi-dex native LOCAL_PACKAGE_NAME := SettingsLibTests +LOCAL_COMPATIBILITY_SUITE := device-tests LOCAL_STATIC_JAVA_LIBRARIES := \ android-support-test \ diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java index ec0190cfa538..e8a58c13baf0 100644 --- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java +++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java @@ -89,21 +89,10 @@ public class AccessPointTest { @Test public void testThatCopyAccessPoint_scanCacheShouldMatch() { - Bundle bundle = new Bundle(); - ArrayList<ScanResult> scanResults = new ArrayList<>(); - for (int i = 0; i < 5; i++) { - ScanResult scanResult = new ScanResult(); - scanResult.level = i; - scanResult.BSSID = "bssid-" + i; - scanResult.timestamp = SystemClock.elapsedRealtime() * 1000; - scanResults.add(scanResult); - } - - bundle.putParcelableArrayList("key_scanresultcache", scanResults); - AccessPoint original = new AccessPoint(mContext, bundle); + AccessPoint original = createAccessPointWithScanResultCache(); assertThat(original.getRssi()).isEqualTo(4); AccessPoint copy = new AccessPoint(mContext, createWifiConfiguration()); - assertThat(copy.getRssi()).isEqualTo(Integer.MIN_VALUE); + assertThat(copy.getRssi()).isEqualTo(AccessPoint.UNREACHABLE_RSSI); copy.copyFrom(original); assertThat(original.getRssi()).isEqualTo(copy.getRssi()); } @@ -190,6 +179,37 @@ public class AccessPointTest { assertThat(points.indexOf(firstName)).isLessThan(points.indexOf(lastname)); } + @Test + public void testRssiIsSetFromScanResults() { + AccessPoint ap = createAccessPointWithScanResultCache(); + int originalRssi = ap.getRssi(); + assertThat(originalRssi).isNotEqualTo(AccessPoint.UNREACHABLE_RSSI); + } + + @Test + public void testGetRssiShouldReturnSetRssiValue() { + AccessPoint ap = createAccessPointWithScanResultCache(); + int originalRssi = ap.getRssi(); + int newRssi = originalRssi - 10; + ap.setRssi(newRssi); + assertThat(ap.getRssi()).isEqualTo(newRssi); + } + + private AccessPoint createAccessPointWithScanResultCache() { + Bundle bundle = new Bundle(); + ArrayList<ScanResult> scanResults = new ArrayList<>(); + for (int i = 0; i < 5; i++) { + ScanResult scanResult = new ScanResult(); + scanResult.level = i; + scanResult.BSSID = "bssid-" + i; + scanResult.timestamp = SystemClock.elapsedRealtime() * 1000; + scanResults.add(scanResult); + } + + bundle.putParcelableArrayList("key_scanresultcache", scanResults); + return new AccessPoint(mContext, bundle); + } + private WifiConfiguration createWifiConfiguration() { WifiConfiguration configuration = new WifiConfiguration(); configuration.BSSID = "bssid"; diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/CategoryManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/CategoryManagerTest.java index 434241d17f64..8d61338f86c3 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/CategoryManagerTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/CategoryManagerTest.java @@ -120,7 +120,7 @@ public class CategoryManagerTest { } @Test - public void normalizePriority_singlePackage_shouldReorderBasedOnPriority() { + public void sortCategories_singlePackage_shouldReorderBasedOnPriority() { // Create some fake tiles that are not sorted. final String testPackage = "com.android.test"; final DashboardCategory category = new DashboardCategory(); @@ -141,22 +141,18 @@ public class CategoryManagerTest { category.tiles.add(tile3); mCategoryByKeyMap.put(CategoryKey.CATEGORY_HOMEPAGE, category); - // Normalize their priorities - mCategoryManager.normalizePriority(ShadowApplication.getInstance().getApplicationContext(), + // Sort their priorities + mCategoryManager.sortCategories(ShadowApplication.getInstance().getApplicationContext(), mCategoryByKeyMap); // Verify they are now sorted. - assertThat(category.tiles.get(0)).isSameAs(tile2); + assertThat(category.tiles.get(0)).isSameAs(tile3); assertThat(category.tiles.get(1)).isSameAs(tile1); - assertThat(category.tiles.get(2)).isSameAs(tile3); - // Verify their priority is normalized - assertThat(category.tiles.get(0).priority).isEqualTo(0); - assertThat(category.tiles.get(1).priority).isEqualTo(1); - assertThat(category.tiles.get(2).priority).isEqualTo(2); + assertThat(category.tiles.get(2)).isSameAs(tile2); } @Test - public void normalizePriority_multiPackage_shouldReorderBasedOnPackageAndPriority() { + public void sortCategories_multiPackage_shouldReorderBasedOnPackageAndPriority() { // Create some fake tiles that are not sorted. final String testPackage1 = "com.android.test1"; final String testPackage2 = "com.android.test2"; @@ -178,22 +174,18 @@ public class CategoryManagerTest { category.tiles.add(tile3); mCategoryByKeyMap.put(CategoryKey.CATEGORY_HOMEPAGE, category); - // Normalize their priorities - mCategoryManager.normalizePriority(ShadowApplication.getInstance().getApplicationContext(), + // Sort their priorities + mCategoryManager.sortCategories(ShadowApplication.getInstance().getApplicationContext(), mCategoryByKeyMap); // Verify they are now sorted. - assertThat(category.tiles.get(0)).isSameAs(tile3); - assertThat(category.tiles.get(1)).isSameAs(tile2); - assertThat(category.tiles.get(2)).isSameAs(tile1); - // Verify their priority is normalized - assertThat(category.tiles.get(0).priority).isEqualTo(0); - assertThat(category.tiles.get(1).priority).isEqualTo(1); - assertThat(category.tiles.get(2).priority).isEqualTo(2); + assertThat(category.tiles.get(0)).isSameAs(tile2); + assertThat(category.tiles.get(1)).isSameAs(tile1); + assertThat(category.tiles.get(2)).isSameAs(tile3); } @Test - public void normalizePriority_internalPackageTiles_shouldSkipTileForInternalPackage() { + public void sortCategories_internalPackageTiles_shouldSkipTileForInternalPackage() { // Create some fake tiles that are not sorted. final String testPackage = ShadowApplication.getInstance().getApplicationContext().getPackageName(); @@ -215,18 +207,82 @@ public class CategoryManagerTest { category.tiles.add(tile3); mCategoryByKeyMap.put(CategoryKey.CATEGORY_HOMEPAGE, category); - // Normalize their priorities - mCategoryManager.normalizePriority(ShadowApplication.getInstance().getApplicationContext(), + // Sort their priorities + mCategoryManager.sortCategories(ShadowApplication.getInstance().getApplicationContext(), mCategoryByKeyMap); // Verify the sorting order is not changed assertThat(category.tiles.get(0)).isSameAs(tile1); assertThat(category.tiles.get(1)).isSameAs(tile2); assertThat(category.tiles.get(2)).isSameAs(tile3); - // Verify their priorities are not changed. - assertThat(category.tiles.get(0).priority).isEqualTo(100); - assertThat(category.tiles.get(1).priority).isEqualTo(100); - assertThat(category.tiles.get(2).priority).isEqualTo(50); + } + + @Test + public void sortCategories_internalAndExternalPackageTiles_shouldRetainPriorityOrdering() { + // Inject one external tile among internal tiles. + final String testPackage = + ShadowApplication.getInstance().getApplicationContext().getPackageName(); + final String testPackage2 = "com.google.test2"; + final DashboardCategory category = new DashboardCategory(); + final Tile tile1 = new Tile(); + tile1.intent = new Intent().setComponent(new ComponentName(testPackage, "class1")); + tile1.priority = 2; + final Tile tile2 = new Tile(); + tile2.intent = new Intent().setComponent(new ComponentName(testPackage, "class2")); + tile2.priority = 1; + final Tile tile3 = new Tile(); + tile3.intent = new Intent().setComponent(new ComponentName(testPackage2, "class0")); + tile3.priority = 0; + final Tile tile4 = new Tile(); + tile4.intent = new Intent().setComponent(new ComponentName(testPackage, "class3")); + tile4.priority = -1; + category.tiles.add(tile1); + category.tiles.add(tile2); + category.tiles.add(tile3); + category.tiles.add(tile4); + mCategoryByKeyMap.put(CategoryKey.CATEGORY_HOMEPAGE, category); + + // Sort their priorities + mCategoryManager.sortCategories(ShadowApplication.getInstance().getApplicationContext(), + mCategoryByKeyMap); + + // Verify the sorting order is not changed + assertThat(category.tiles.get(0)).isSameAs(tile1); + assertThat(category.tiles.get(1)).isSameAs(tile2); + assertThat(category.tiles.get(2)).isSameAs(tile3); + assertThat(category.tiles.get(3)).isSameAs(tile4); + } + + @Test + public void sortCategories_samePriority_internalPackageTileShouldTakePrecedence() { + // Inject one external tile among internal tiles with same priority. + final String testPackage = + ShadowApplication.getInstance().getApplicationContext().getPackageName(); + final String testPackage2 = "com.google.test2"; + final String testPackage3 = "com.abcde.test3"; + final DashboardCategory category = new DashboardCategory(); + final Tile tile1 = new Tile(); + tile1.intent = new Intent().setComponent(new ComponentName(testPackage2, "class1")); + tile1.priority = 1; + final Tile tile2 = new Tile(); + tile2.intent = new Intent().setComponent(new ComponentName(testPackage, "class2")); + tile2.priority = 1; + final Tile tile3 = new Tile(); + tile3.intent = new Intent().setComponent(new ComponentName(testPackage3, "class3")); + tile3.priority = 1; + category.tiles.add(tile1); + category.tiles.add(tile2); + category.tiles.add(tile3); + mCategoryByKeyMap.put(CategoryKey.CATEGORY_HOMEPAGE, category); + + // Sort their priorities + mCategoryManager.sortCategories(ShadowApplication.getInstance().getApplicationContext(), + mCategoryByKeyMap); + + // Verify the sorting order is internal first, follow by package name ordering + assertThat(category.tiles.get(0)).isSameAs(tile2); + assertThat(category.tiles.get(1)).isSameAs(tile3); + assertThat(category.tiles.get(2)).isSameAs(tile1); } @Test diff --git a/packages/SettingsProvider/AndroidManifest.xml b/packages/SettingsProvider/AndroidManifest.xml index 6c06d05e100b..9374d522707b 100644 --- a/packages/SettingsProvider/AndroidManifest.xml +++ b/packages/SettingsProvider/AndroidManifest.xml @@ -17,6 +17,7 @@ android:multiprocess="false" android:exported="true" android:singleUser="true" - android:initOrder="100" /> + android:initOrder="100" + android:visibleToInstantApps="true" /> </application> </manifest> diff --git a/packages/Shell/tests/Android.mk b/packages/Shell/tests/Android.mk index acd552da6081..48b757c30cd1 100644 --- a/packages/Shell/tests/Android.mk +++ b/packages/Shell/tests/Android.mk @@ -16,6 +16,7 @@ LOCAL_STATIC_JAVA_LIBRARIES := \ legacy-android-test \ LOCAL_PACKAGE_NAME := ShellTests +LOCAL_COMPATIBILITY_SUITE := device-tests LOCAL_INSTRUMENTATION_FOR := Shell LOCAL_CERTIFICATE := platform diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index 428b7b85fc86..4b932a3361e6 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -180,6 +180,9 @@ <!-- accessibility --> <uses-permission android:name="android.permission.MODIFY_ACCESSIBILITY_DATA" /> + <!-- to control accessibility volume --> + <uses-permission android:name="android.permission.BIND_ACCESSIBILITY_SERVICE" /> + <application android:name=".SystemUIApplication" android:persistent="true" diff --git a/packages/SystemUI/res/color/qs_user_detail_avatar_tint.xml b/packages/SystemUI/res/color/qs_user_detail_avatar_tint.xml index 2b75c369c2e0..696e9b121564 100644 --- a/packages/SystemUI/res/color/qs_user_detail_avatar_tint.xml +++ b/packages/SystemUI/res/color/qs_user_detail_avatar_tint.xml @@ -17,6 +17,6 @@ --> <selector xmlns:android="http://schemas.android.com/apk/res/android"> - <item android:state_enabled="false" android:color="@color/qs_tile_disabled_color" /> + <item android:state_enabled="false" android:color="?android:attr/textColorPrimary" /> <item android:color="@android:color/transparent" /> -</selector>
\ No newline at end of file +</selector> diff --git a/packages/SystemUI/res/drawable/ic_add_circle_qs.xml b/packages/SystemUI/res/drawable/ic_add_circle_qs.xml index f296076adb4c..6415ecb44c2e 100644 --- a/packages/SystemUI/res/drawable/ic_add_circle_qs.xml +++ b/packages/SystemUI/res/drawable/ic_add_circle_qs.xml @@ -17,14 +17,15 @@ Copyright (C) 2014 The Android Open Source Project android:width="48.0dp" android:height="48.0dp" android:viewportWidth="24.0" - android:viewportHeight="24.0"> + android:viewportHeight="24.0" + android:tint="?android:attr/colorControlNormal"> <group android:scaleX="1.2" android:scaleY="1.2" android:pivotX="12.0" android:pivotY="12.0"> <path - android:fillColor="@color/qs_user_detail_icon_muted" + android:fillColor="#FFFFFFFF" android:pathData="M12.000000,2.000000C6.500000,2.000000 2.000000,6.500000 2.000000,12.000000s4.500000,10.000000 10.000000,10.000000c5.500000,0.000000 10.000000,-4.500000 10.000000,-10.000000S17.500000,2.000000 12.000000,2.000000zM17.000000,13.000000l-4.000000,0.000000l0.000000,4.000000l-2.000000,0.000000l0.000000,-4.000000L7.000000,13.000000l0.000000,-2.000000l4.000000,0.000000L11.000000,7.000000l2.000000,0.000000l0.000000,4.000000l4.000000,0.000000L17.000000,13.000000z"/> </group> </vector> diff --git a/packages/SystemUI/res/layout/pip_menu_activity.xml b/packages/SystemUI/res/layout/pip_menu_activity.xml index 5e49d058cd84..c6837fa30925 100644 --- a/packages/SystemUI/res/layout/pip_menu_activity.xml +++ b/packages/SystemUI/res/layout/pip_menu_activity.xml @@ -15,50 +15,55 @@ --> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:id="@+id/menu" + android:id="@+id/background" android:layout_width="match_parent" - android:layout_height="match_parent" - android:background="#4D000000"> - <!-- The above background is only for the dismiss button ripple to show. --> + android:layout_height="match_parent"> - <ImageView - android:id="@+id/dismiss" - android:layout_width="@dimen/pip_action_size" - android:layout_height="@dimen/pip_action_size" - android:layout_gravity="top|end" - android:padding="@dimen/pip_action_padding" - android:contentDescription="@string/pip_phone_close" - android:src="@drawable/ic_close_white" - android:background="?android:selectableItemBackgroundBorderless" /> + <!-- Menu layout --> + <FrameLayout + android:id="@+id/menu_container" + android:layout_width="match_parent" + android:layout_height="match_parent"> - <!-- The margins for this container is calculated in the code depending on whether the - actions_container is visible. --> - <FrameLayout - android:id="@+id/expand_container" - android:layout_width="match_parent" - android:layout_height="match_parent"> - <ImageView - android:id="@+id/expand_button" - android:layout_width="60dp" - android:layout_height="60dp" - android:layout_gravity="center" - android:contentDescription="@string/pip_phone_expand" - android:background="?android:selectableItemBackgroundBorderless" /> - </FrameLayout> + <ImageView + android:id="@+id/dismiss" + android:layout_width="@dimen/pip_action_size" + android:layout_height="@dimen/pip_action_size" + android:layout_gravity="top|end" + android:padding="@dimen/pip_action_padding" + android:contentDescription="@string/pip_phone_close" + android:src="@drawable/ic_close_white" + android:background="?android:selectableItemBackgroundBorderless" /> - <FrameLayout - android:id="@+id/actions_container" - android:layout_width="match_parent" - android:layout_height="@dimen/pip_action_size" - android:layout_gravity="bottom" - android:visibility="invisible"> - <LinearLayout - android:id="@+id/actions_group" - android:layout_width="wrap_content" - android:layout_height="match_parent" - android:layout_gravity="center_horizontal" - android:orientation="horizontal" - android:divider="@android:color/transparent" - android:showDividers="middle" /> - </FrameLayout> + <!-- The margins for this container is calculated in the code depending on whether the + actions_container is visible. --> + <FrameLayout + android:id="@+id/expand_container" + android:layout_width="match_parent" + android:layout_height="match_parent"> + <ImageView + android:id="@+id/expand_button" + android:layout_width="60dp" + android:layout_height="60dp" + android:layout_gravity="center" + android:contentDescription="@string/pip_phone_expand" + android:background="?android:selectableItemBackgroundBorderless" /> + </FrameLayout> + + <FrameLayout + android:id="@+id/actions_container" + android:layout_width="match_parent" + android:layout_height="@dimen/pip_action_size" + android:layout_gravity="bottom" + android:visibility="invisible"> + <LinearLayout + android:id="@+id/actions_group" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:layout_gravity="center_horizontal" + android:orientation="horizontal" + android:divider="@android:color/transparent" + android:showDividers="middle" /> + </FrameLayout> + </FrameLayout> </FrameLayout> diff --git a/packages/SystemUI/res/layout/qs_tile_label.xml b/packages/SystemUI/res/layout/qs_tile_label.xml index 8d1f9e4131f9..9d1fb8fd4c6f 100644 --- a/packages/SystemUI/res/layout/qs_tile_label.xml +++ b/packages/SystemUI/res/layout/qs_tile_label.xml @@ -14,24 +14,39 @@ See the License for the specific language governing permissions and limitations under the License. --> -<LinearLayout - xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="match_parent" +<RelativeLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:minHeight="48dp" + android:paddingTop="8dp"> + <LinearLayout + android:id="@+id/label_group" + android:layout_width="wrap_content" android:layout_height="wrap_content" - android:orientation="horizontal" - android:gravity="center_horizontal" - android:paddingTop="8dp" - android:paddingBottom="8dp"> - <TextView android:id="@+id/tile_label" + android:layout_alignParentTop="true" + android:layout_centerHorizontal="true" + android:orientation="horizontal"> + + <TextView + android:id="@+id/tile_label" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:textColor="?android:attr/textColorPrimary" - android:gravity="center_horizontal" - android:minLines="2" + android:clickable="false" + android:maxLines="2" android:padding="0dp" android:textAppearance="@style/TextAppearance.QS.TileLabel" - android:clickable="false" /> - <ImageView android:id="@+id/restricted_padlock" + android:textColor="?android:attr/textColorPrimary"/> + + <ImageView + android:id="@+id/expand_indicator" + android:layout_marginStart="4dp" + android:layout_width="12dp" + android:layout_height="match_parent" + android:src="@drawable/qs_dual_tile_caret" + android:tint="?android:attr/textColorPrimary" /> + + <ImageView android:id="@+id/restricted_padlock" android:layout_width="@dimen/qs_tile_text_size" android:layout_height="match_parent" android:paddingBottom="@dimen/qs_tile_text_size" @@ -39,4 +54,32 @@ android:layout_marginLeft="@dimen/restricted_padlock_pading" android:scaleType="centerInside" android:visibility="gone" /> -</LinearLayout> + </LinearLayout> + + <TextView + android:id="@+id/app_label" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignStart="@id/label_group" + android:layout_alignEnd="@id/label_group" + android:layout_below="@id/label_group" + android:clickable="false" + android:maxLines="1" + android:padding="0dp" + android:visibility="gone" + android:gravity="center" + android:textAppearance="@style/TextAppearance.QS.TileLabel" + android:textColor="?android:attr/textColorPrimary"/> + + <View + android:id="@+id/underline" + android:layout_width="30dp" + android:layout_height="1dp" + android:layout_marginTop="2dp" + android:layout_alignStart="@id/label_group" + android:layout_alignEnd="@id/label_group" + android:layout_below="@id/label_group" + android:alpha="?android:attr/disabledAlpha" + android:background="?android:attr/colorForeground"/> + +</RelativeLayout> diff --git a/packages/SystemUI/res/layout/qs_user_detail_item.xml b/packages/SystemUI/res/layout/qs_user_detail_item.xml index 8c6c7cfc8048..c7bfaef8bbe8 100644 --- a/packages/SystemUI/res/layout/qs_user_detail_item.xml +++ b/packages/SystemUI/res/layout/qs_user_detail_item.xml @@ -53,7 +53,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="@dimen/qs_detail_item_secondary_text_size" - android:textColor="@color/qs_user_detail_name" + android:textColor="?android:attr/textColorSecondary" android:gravity="center_horizontal" /> <ImageView android:id="@+id/restricted_padlock" diff --git a/packages/SystemUI/res/layout/quick_settings_footer_dialog.xml b/packages/SystemUI/res/layout/quick_settings_footer_dialog.xml index 2ba04fd31c0f..397fbf10dc81 100644 --- a/packages/SystemUI/res/layout/quick_settings_footer_dialog.xml +++ b/packages/SystemUI/res/layout/quick_settings_footer_dialog.xml @@ -88,7 +88,6 @@ android:scaleType="fitCenter" android:src="@drawable/ic_qs_network_logging" android:tint="?android:attr/textColorPrimaryInverse" - android:alpha="@dimen/qs_footer_dialog_network_logging_icon_alpha" android:adjustViewBounds="true"/> <LinearLayout android:layout_width="match_parent" diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index ffaa7bae959c..72bdbf124c18 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -279,8 +279,6 @@ <dimen name="qs_footer_dialog_icon_size">24sp</dimen> <!-- Left and right margin of the icons --> <dimen name="qs_footer_dialog_icon_margin">8sp</dimen> - <!-- Alpha value of network logging icon --> - <dimen name="qs_footer_dialog_network_logging_icon_alpha">0.3</dimen> <!-- Zen mode panel: condition item button padding --> <dimen name="zen_mode_condition_detail_button_padding">8dp</dimen> diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java index bb7e19de0689..4dfaf452fd51 100644 --- a/packages/SystemUI/src/com/android/systemui/Dependency.java +++ b/packages/SystemUI/src/com/android/systemui/Dependency.java @@ -30,6 +30,7 @@ import com.android.systemui.fragments.FragmentService; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.PluginDependencyProvider; import com.android.systemui.plugins.PluginManager; +import com.android.systemui.plugins.PluginManagerImpl; import com.android.systemui.statusbar.phone.ConfigurationControllerImpl; import com.android.systemui.statusbar.phone.DarkIconDispatcherImpl; import com.android.systemui.statusbar.phone.ManagedProfileController; @@ -73,6 +74,7 @@ import com.android.systemui.statusbar.policy.UserSwitcherController; import com.android.systemui.statusbar.policy.ZenModeController; import com.android.systemui.statusbar.policy.ZenModeControllerImpl; import com.android.systemui.tuner.TunerService; +import com.android.systemui.tuner.TunerServiceImpl; import com.android.systemui.util.leak.GarbageMonitor; import com.android.systemui.util.leak.LeakDetector; import com.android.systemui.util.leak.LeakReporter; @@ -200,7 +202,7 @@ public class Dependency extends SystemUI { new DeviceProvisionedControllerImpl(mContext)); mProviders.put(PluginManager.class, () -> - new PluginManager(mContext)); + new PluginManagerImpl(mContext)); mProviders.put(AssistManager.class, () -> new AssistManager(getDependency(DeviceProvisionedController.class), mContext)); @@ -223,7 +225,7 @@ public class Dependency extends SystemUI { getDependency(LeakReporter.class))); mProviders.put(TunerService.class, () -> - new TunerService(mContext)); + new TunerServiceImpl(mContext)); mProviders.put(StatusBarWindowManager.class, () -> new StatusBarWindowManager(mContext)); diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index cbe822f2205d..a549c73c6527 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -1160,7 +1160,8 @@ public class KeyguardViewMediator extends SystemUI { if (mOccluded != isOccluded) { mOccluded = isOccluded; - mStatusBarKeyguardViewManager.setOccluded(isOccluded, animate); + mStatusBarKeyguardViewManager.setOccluded(isOccluded, animate + && mDeviceInteractive); adjustStatusBarLocked(); } } diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java index 86e2c4956070..0547cbb4dd92 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java @@ -20,10 +20,12 @@ import static com.android.systemui.pip.phone.PipMenuActivityController.EXTRA_ACT import static com.android.systemui.pip.phone.PipMenuActivityController.EXTRA_CONTROLLER_MESSENGER; import static com.android.systemui.pip.phone.PipMenuActivityController.EXTRA_MOVEMENT_BOUNDS; import static com.android.systemui.pip.phone.PipMenuActivityController.EXTRA_STACK_BOUNDS; +import static com.android.systemui.pip.phone.PipMenuActivityController.EXTRA_SHOW_MENU; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ObjectAnimator; +import android.animation.ValueAnimator; import android.annotation.Nullable; import android.app.Activity; import android.app.ActivityManager; @@ -35,6 +37,8 @@ import android.content.res.Resources; import android.graphics.Color; import android.graphics.PointF; import android.graphics.Rect; +import android.graphics.drawable.ColorDrawable; +import android.graphics.drawable.Drawable; import android.graphics.drawable.Icon; import android.os.Bundle; import android.os.Handler; @@ -71,13 +75,19 @@ public class PipMenuActivity extends Activity { public static final int MESSAGE_POKE_MENU = 2; public static final int MESSAGE_HIDE_MENU = 3; public static final int MESSAGE_UPDATE_ACTIONS = 4; + public static final int MESSAGE_UPDATE_DISMISS_FRACTION = 5; private static final long INITIAL_DISMISS_DELAY = 2000; private static final long POST_INTERACTION_DISMISS_DELAY = 1500; private static final long MENU_FADE_DURATION = 125; + private static final float MENU_BACKGROUND_ALPHA = 0.3f; + private static final float DISMISS_BACKGROUND_ALPHA = 0.8f; + private boolean mMenuVisible; private final List<RemoteAction> mActions = new ArrayList<>(); + private View mViewRoot; + private Drawable mBackgroundDrawable; private View mMenuContainer; private LinearLayout mActionsGroup; private View mDismissButton; @@ -85,6 +95,14 @@ public class PipMenuActivity extends Activity { private int mBetweenActionPaddingLand; private ObjectAnimator mMenuContainerAnimator; + private ValueAnimator.AnimatorUpdateListener mMenuBgUpdateListener = + new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + final float alpha = (float) animation.getAnimatedValue(); + mBackgroundDrawable.setAlpha((int) (MENU_BACKGROUND_ALPHA*alpha*255)); + } + }; private PointF mDownPosition = new PointF(); private PointF mDownDelta = new PointF(); @@ -109,6 +127,10 @@ public class PipMenuActivity extends Activity { Pair<Rect, ParceledListSlice> data = (Pair<Rect, ParceledListSlice>) msg.obj; setActions(data.first, data.second.getList()); break; + case MESSAGE_UPDATE_DISMISS_FRACTION: + float fraction = (float) msg.obj; + updateDismissFraction(fraction); + break; } } }); @@ -130,7 +152,12 @@ public class PipMenuActivity extends Activity { super.onCreate(savedInstanceState); setContentView(R.layout.pip_menu_activity); - mMenuContainer = findViewById(R.id.menu); + mBackgroundDrawable = new ColorDrawable(Color.BLACK); + mBackgroundDrawable.setAlpha(0); + mViewRoot = findViewById(R.id.background); + mViewRoot.setBackground(mBackgroundDrawable); + mMenuContainer = findViewById(R.id.menu_container); + mMenuContainer.setAlpha(0); mMenuContainer.setOnClickListener((v) -> { expandPip(); }); @@ -222,10 +249,11 @@ public class PipMenuActivity extends Activity { private void showMenu(Rect stackBounds, Rect movementBounds) { if (!mMenuVisible) { + setVisible(true); + updateActionViews(stackBounds); if (mMenuContainerAnimator != null) { mMenuContainerAnimator.cancel(); } - notifyMenuVisibility(true); updateExpandButtonFromBounds(stackBounds, movementBounds); mMenuContainerAnimator = ObjectAnimator.ofFloat(mMenuContainer, View.ALPHA, @@ -238,9 +266,13 @@ public class PipMenuActivity extends Activity { repostDelayedFinish(INITIAL_DISMISS_DELAY); } }); + mMenuContainerAnimator.addUpdateListener(mMenuBgUpdateListener); mMenuContainerAnimator.start(); } else { + // If we are already visible, then just start the delayed dismiss and unregister any + // existing input consumers from the previous drag repostDelayedFinish(POST_INTERACTION_DISMISS_DELAY); + notifyUnregisterInputConsumer(); } } @@ -264,25 +296,27 @@ public class PipMenuActivity extends Activity { if (animationFinishedRunnable != null) { animationFinishedRunnable.run(); } - if (getSystemService(AccessibilityManager.class).isEnabled()) { - finish(); - } + setVisible(false); } }); + mMenuContainerAnimator.addUpdateListener(mMenuBgUpdateListener); mMenuContainerAnimator.start(); } } private void updateFromIntent(Intent intent) { - Rect stackBounds = Rect.unflattenFromString(intent.getStringExtra(EXTRA_STACK_BOUNDS)); - Rect movementBounds = Rect.unflattenFromString(intent.getStringExtra( - EXTRA_MOVEMENT_BOUNDS)); mToControllerMessenger = intent.getParcelableExtra(EXTRA_CONTROLLER_MESSENGER); ParceledListSlice actions = intent.getParcelableExtra(EXTRA_ACTIONS); if (actions != null) { - setActions(stackBounds, actions.getList()); + mActions.clear(); + mActions.addAll(actions.getList()); + } + if (intent.getBooleanExtra(EXTRA_SHOW_MENU, false)) { + Rect stackBounds = Rect.unflattenFromString(intent.getStringExtra(EXTRA_STACK_BOUNDS)); + Rect movementBounds = Rect.unflattenFromString(intent.getStringExtra( + EXTRA_MOVEMENT_BOUNDS)); + showMenu(stackBounds, movementBounds); } - showMenu(stackBounds, movementBounds); } private void updateExpandButtonFromBounds(Rect stackBounds, Rect movementBounds) { @@ -365,12 +399,32 @@ public class PipMenuActivity extends Activity { } } + private void updateDismissFraction(float fraction) { + setVisible(true); + int alpha; + if (mMenuVisible) { + mMenuContainer.setAlpha(1-fraction); + final float interpolatedAlpha = + MENU_BACKGROUND_ALPHA * (1.0f - fraction) + DISMISS_BACKGROUND_ALPHA * fraction; + alpha = (int) (interpolatedAlpha*255); + } else { + alpha = (int) (fraction*DISMISS_BACKGROUND_ALPHA*255); + } + mBackgroundDrawable.setAlpha(alpha); + } + private void notifyRegisterInputConsumer() { Message m = Message.obtain(); m.what = PipMenuActivityController.MESSAGE_REGISTER_INPUT_CONSUMER; sendMessage(m, "Could not notify controller to register input consumer"); } + private void notifyUnregisterInputConsumer() { + Message m = Message.obtain(); + m.what = PipMenuActivityController.MESSAGE_UNREGISTER_INPUT_CONSUMER; + sendMessage(m, "Could not notify controller to unregister input consumer"); + } + private void notifyMenuVisibility(boolean visible) { mMenuVisible = visible; Message m = Message.obtain(); diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java index badf64b90b8f..724f45347f5d 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java @@ -42,7 +42,7 @@ import java.util.ArrayList; import java.util.List; /** - * Manages the PiP menu activity. + * Manages the PiP menu activity which can show menu options or a scrim. * * The current media session provides actions whenever there are no valid actions provided by the * current PiP activity. Otherwise, those actions always take precedence. @@ -55,6 +55,7 @@ public class PipMenuActivityController { public static final String EXTRA_ACTIONS = "actions"; public static final String EXTRA_STACK_BOUNDS = "stack_bounds"; public static final String EXTRA_MOVEMENT_BOUNDS = "movement_bounds"; + public static final String EXTRA_SHOW_MENU = "show_menu"; public static final int MESSAGE_MENU_VISIBILITY_CHANGED = 100; public static final int MESSAGE_EXPAND_PIP = 101; @@ -62,6 +63,7 @@ public class PipMenuActivityController { public static final int MESSAGE_DISMISS_PIP = 103; public static final int MESSAGE_UPDATE_ACTIVITY_CALLBACK = 104; public static final int MESSAGE_REGISTER_INPUT_CONSUMER = 105; + public static final int MESSAGE_UNREGISTER_INPUT_CONSUMER = 106; /** * A listener interface to receive notification on changes in PIP. @@ -101,6 +103,7 @@ public class PipMenuActivityController { private ParceledListSlice mMediaActions; private boolean mMenuVisible; + private boolean mStartActivityRequested; private Messenger mToActivityMessenger; private Messenger mMessenger = new Messenger(new Handler() { @Override @@ -133,8 +136,13 @@ public class PipMenuActivityController { mInputConsumerController.registerInputConsumer(); break; } + case MESSAGE_UNREGISTER_INPUT_CONSUMER: { + mInputConsumerController.unregisterInputConsumer(); + break; + } case MESSAGE_UPDATE_ACTIVITY_CALLBACK: { mToActivityMessenger = msg.replyTo; + mStartActivityRequested = false; // Mark the menu as invisible once the activity finishes as well if (mToActivityMessenger == null) { onMenuVisibilityChanged(false, true /* resize */); @@ -179,6 +187,25 @@ public class PipMenuActivityController { } /** + * Updates the appearance of the menu and scrim on top of the PiP while dismissing. + */ + public void setDismissFraction(float fraction) { + if (mToActivityMessenger != null) { + Message m = Message.obtain(); + m.what = PipMenuActivity.MESSAGE_UPDATE_DISMISS_FRACTION; + m.obj = fraction; + try { + mToActivityMessenger.send(m); + } catch (RemoteException e) { + Log.e(TAG, "Could not notify menu to show", e); + } + } else if (!mStartActivityRequested) { + startMenuActivity(null /* stackBounds */, null /* movementBounds */, + false /* showMenu */); + } + } + + /** * Shows the menu activity. */ public void showMenu(Rect stackBounds, Rect movementBounds) { @@ -191,28 +218,8 @@ public class PipMenuActivityController { } catch (RemoteException e) { Log.e(TAG, "Could not notify menu to show", e); } - } else { - // Start the menu activity on the top task of the pinned stack - try { - StackInfo pinnedStackInfo = mActivityManager.getStackInfo(PINNED_STACK_ID); - if (pinnedStackInfo != null && pinnedStackInfo.taskIds != null && - pinnedStackInfo.taskIds.length > 0) { - Intent intent = new Intent(mContext, PipMenuActivity.class); - intent.putExtra(EXTRA_CONTROLLER_MESSENGER, mMessenger); - intent.putExtra(EXTRA_ACTIONS, resolveMenuActions()); - intent.putExtra(EXTRA_STACK_BOUNDS, stackBounds.flattenToString()); - intent.putExtra(EXTRA_MOVEMENT_BOUNDS, movementBounds.flattenToString()); - ActivityOptions options = ActivityOptions.makeCustomAnimation(mContext, 0, 0); - options.setLaunchTaskId( - pinnedStackInfo.taskIds[pinnedStackInfo.taskIds.length - 1]); - options.setTaskOverlay(true, true /* canResume */); - mContext.startActivityAsUser(intent, options.toBundle(), UserHandle.CURRENT); - } else { - Log.e(TAG, "No PIP tasks found"); - } - } catch (RemoteException e) { - Log.e(TAG, "Error showing PIP menu activity", e); - } + } else if (!mStartActivityRequested) { + startMenuActivity(stackBounds, movementBounds, true /* showMenu */); } } @@ -272,6 +279,39 @@ public class PipMenuActivityController { } /** + * Starts the menu activity on the top task of the pinned stack. + */ + private void startMenuActivity(Rect stackBounds, Rect movementBounds, boolean showMenu) { + try { + StackInfo pinnedStackInfo = mActivityManager.getStackInfo(PINNED_STACK_ID); + if (pinnedStackInfo != null && pinnedStackInfo.taskIds != null && + pinnedStackInfo.taskIds.length > 0) { + Intent intent = new Intent(mContext, PipMenuActivity.class); + intent.putExtra(EXTRA_CONTROLLER_MESSENGER, mMessenger); + intent.putExtra(EXTRA_ACTIONS, resolveMenuActions()); + if (stackBounds != null) { + intent.putExtra(EXTRA_STACK_BOUNDS, stackBounds.flattenToString()); + } + if (movementBounds != null) { + intent.putExtra(EXTRA_MOVEMENT_BOUNDS, movementBounds.flattenToString()); + } + intent.putExtra(EXTRA_SHOW_MENU, showMenu); + ActivityOptions options = ActivityOptions.makeCustomAnimation(mContext, 0, 0); + options.setLaunchTaskId( + pinnedStackInfo.taskIds[pinnedStackInfo.taskIds.length - 1]); + options.setTaskOverlay(true, true /* canResume */); + mContext.startActivityAsUser(intent, options.toBundle(), UserHandle.CURRENT); + mStartActivityRequested = true; + } else { + Log.e(TAG, "No PIP tasks found"); + } + } catch (RemoteException e) { + mStartActivityRequested = false; + Log.e(TAG, "Error showing PIP menu activity", e); + } + } + + /** * Updates the PiP menu activity with the best set of actions provided. */ private void updateMenuActions() { diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java index 49d89a2bdd68..c4cf28c9a320 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java @@ -65,9 +65,9 @@ public class PipMotionHelper { private static final int IME_SHIFT_DURATION = 300; // The fraction of the stack width that the user has to drag offscreen to minimize the PiP - private static final float MINIMIZE_OFFSCREEN_FRACTION = 0.2f; - // The fraction of the stack height that the user has to drag offscreen to minimize the PiP - private static final float DISMISS_OFFSCREEN_FRACTION = 0.35f; + private static final float MINIMIZE_OFFSCREEN_FRACTION = 0.3f; + // The fraction of the stack height that the user has to drag offscreen to dismiss the PiP + private static final float DISMISS_OFFSCREEN_FRACTION = 0.15f; private Context mContext; private IActivityManager mActivityManager; @@ -234,12 +234,16 @@ public class PipMotionHelper { /** * Animates the PiP to the minimized state, slightly offscreen. */ - Rect animateToClosestMinimizedState(Rect movementBounds) { + Rect animateToClosestMinimizedState(Rect movementBounds, + AnimatorUpdateListener updateListener) { cancelAnimations(); Rect toBounds = getClosestMinimizedBounds(mBounds, movementBounds); if (!mBounds.equals(toBounds)) { mBoundsAnimator = createAnimationToBounds(mBounds, toBounds, MINIMIZE_STACK_MAX_DURATION, LINEAR_OUT_SLOW_IN, mUpdateBoundsListener); + if (updateListener != null) { + mBoundsAnimator.addUpdateListener(updateListener); + } mBoundsAnimator.start(); } return toBounds; @@ -248,7 +252,8 @@ public class PipMotionHelper { /** * Flings the PiP to the closest snap target. */ - Rect flingToSnapTarget(float velocity, float velocityX, float velocityY, Rect movementBounds) { + Rect flingToSnapTarget(float velocity, float velocityX, float velocityY, Rect movementBounds, + AnimatorUpdateListener listener) { cancelAnimations(); Rect toBounds = mSnapAlgorithm.findClosestSnapBounds(movementBounds, mBounds, velocityX, velocityY); @@ -258,6 +263,9 @@ public class PipMotionHelper { mFlingAnimationUtils.apply(mBoundsAnimator, 0, distanceBetweenRectOffsets(mBounds, toBounds), velocity); + if (listener != null) { + mBoundsAnimator.addUpdateListener(listener); + } mBoundsAnimator.start(); } return toBounds; @@ -266,12 +274,15 @@ public class PipMotionHelper { /** * Animates the PiP to the closest snap target. */ - Rect animateToClosestSnapTarget(Rect movementBounds) { + Rect animateToClosestSnapTarget(Rect movementBounds, AnimatorUpdateListener listener) { cancelAnimations(); Rect toBounds = mSnapAlgorithm.findClosestSnapBounds(movementBounds, mBounds); if (!mBounds.equals(toBounds)) { mBoundsAnimator = createAnimationToBounds(mBounds, toBounds, SNAP_STACK_DURATION, FAST_OUT_SLOW_IN, mUpdateBoundsListener); + if (listener != null) { + mBoundsAnimator.addUpdateListener(listener); + } mBoundsAnimator.start(); } return toBounds; @@ -316,7 +327,7 @@ public class PipMotionHelper { /** * Animates the dismissal of the PiP off the edge of the screen. */ - Rect animateDragToEdgeDismiss(Rect pipBounds) { + Rect animateDragToEdgeDismiss(Rect pipBounds, AnimatorUpdateListener listener) { cancelAnimations(); Point displaySize = new Point(); mContext.getDisplay().getRealSize(displaySize); @@ -330,6 +341,9 @@ public class PipMotionHelper { dismissPip(); } }); + if (listener != null) { + mBoundsAnimator.addUpdateListener(listener); + } mBoundsAnimator.start(); return toBounds; } diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java index 80231a954e1a..f70d5b41ac3e 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java @@ -16,6 +16,8 @@ package com.android.systemui.pip.phone; +import android.animation.ValueAnimator; +import android.animation.ValueAnimator.AnimatorUpdateListener; import android.app.IActivityManager; import android.content.Context; import android.graphics.Point; @@ -55,7 +57,7 @@ public class PipTouchHandler { // Allow dragging the PIP to a location to close it private static final boolean ENABLE_DISMISS_DRAG_TO_TARGET = false; - private static final boolean ENABLE_DISMISS_DRAG_TO_EDGE = false; + private static final boolean ENABLE_DISMISS_DRAG_TO_EDGE = true; private final Context mContext; private final IActivityManager mActivityManager; @@ -87,6 +89,13 @@ public class PipTouchHandler { } } }; + private ValueAnimator.AnimatorUpdateListener mUpdateScrimListener = + new AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + updateDismissFraction(); + } + }; // Behaviour states private boolean mIsMenuVisible; @@ -124,7 +133,7 @@ public class PipTouchHandler { @Override public void onPipMinimize() { setMinimizedStateInternal(true); - mMotionHelper.animateToClosestMinimizedState(mMovementBounds); + mMotionHelper.animateToClosestMinimizedState(mMovementBounds, null /* updateListener */); } @Override @@ -331,6 +340,22 @@ public class PipTouchHandler { } /** + * Updates the appearance of the menu and scrim on top of the PiP while dismissing. + */ + void updateDismissFraction() { + if (mMenuController != null) { + Rect bounds = mMotionHelper.getBounds(); + final float target = mMovementBounds.bottom + bounds.height(); + float fraction = 0f; + if (bounds.bottom > target) { + final float distance = bounds.bottom - target; + fraction = Math.min(distance / bounds.height(), 1f); + } + mMenuController.setDismissFraction(fraction); + } + } + + /** * Sets the controller to update the system of changes from user interaction. */ void setPinnedStackController(IPinnedStackController controller) { @@ -465,6 +490,9 @@ public class PipTouchHandler { if (ENABLE_DISMISS_DRAG_TO_TARGET) { mDismissViewController.updateDismissTarget(mTmpBounds); } + if (ENABLE_DISMISS_DRAG_TO_EDGE) { + updateDismissFraction(); + } return true; } return false; @@ -476,8 +504,8 @@ public class PipTouchHandler { return false; } - try { - if (ENABLE_DISMISS_DRAG_TO_TARGET) { + if (ENABLE_DISMISS_DRAG_TO_TARGET) { + try { mHandler.removeCallbacks(mShowDismissAffordance); PointF vel = mTouchState.getVelocity(); final float velocity = PointF.length(vel.x, vel.y); @@ -492,9 +520,9 @@ public class PipTouchHandler { return true; } } + } finally { + mDismissViewController.destroyDismissTarget(); } - } finally { - mDismissViewController.destroyDismissTarget(); } if (touchState.isDragging()) { @@ -502,7 +530,8 @@ public class PipTouchHandler { boolean isFlingToBot = isFlingTowardsEdge(touchState, 4 /* bottom */); if (ENABLE_DISMISS_DRAG_TO_EDGE && (mMotionHelper.shouldDismissPip() || isFlingToBot)) { - mMotionHelper.animateDragToEdgeDismiss(mMotionHelper.getBounds()); + mMotionHelper.animateDragToEdgeDismiss(mMotionHelper.getBounds(), + mUpdateScrimListener); MetricsLogger.action(mContext, MetricsEvent.ACTION_PICTURE_IN_PICTURE_DISMISSED, METRIC_VALUE_DISMISSED_BY_DRAG); @@ -517,7 +546,8 @@ public class PipTouchHandler { // minimize offset adjusted mMenuController.hideMenu(); } else { - mMotionHelper.animateToClosestMinimizedState(mMovementBounds); + mMotionHelper.animateToClosestMinimizedState(mMovementBounds, + mUpdateScrimListener); } return true; } @@ -536,13 +566,14 @@ public class PipTouchHandler { final PointF vel = mTouchState.getVelocity(); final float velocity = PointF.length(vel.x, vel.y); if (velocity > mFlingAnimationUtils.getMinVelocityPxPerSecond()) { - mMotionHelper.flingToSnapTarget(velocity, vel.x, vel.y, mMovementBounds); + mMotionHelper.flingToSnapTarget(velocity, vel.x, vel.y, mMovementBounds, + mUpdateScrimListener); } else { - mMotionHelper.animateToClosestSnapTarget(mMovementBounds); + mMotionHelper.animateToClosestSnapTarget(mMovementBounds, mUpdateScrimListener); } } else if (mIsMinimized) { // This was a tap, so no longer minimized - mMotionHelper.animateToClosestSnapTarget(mMovementBounds); + mMotionHelper.animateToClosestSnapTarget(mMovementBounds, null /* listener */); setMinimizedStateInternal(false); } else if (!mIsMenuVisible) { mMenuController.showMenu(mMotionHelper.getBounds(), mMovementBounds); diff --git a/packages/SystemUI/src/com/android/systemui/plugins/PluginInstanceManager.java b/packages/SystemUI/src/com/android/systemui/plugins/PluginInstanceManager.java index 79f78c92f8b9..07ac52d718aa 100644 --- a/packages/SystemUI/src/com/android/systemui/plugins/PluginInstanceManager.java +++ b/packages/SystemUI/src/com/android/systemui/plugins/PluginInstanceManager.java @@ -62,10 +62,10 @@ public class PluginInstanceManager<T extends Plugin> { final PluginHandler mPluginHandler; private final boolean isDebuggable; private final PackageManager mPm; - private final PluginManager mManager; + private final PluginManagerImpl mManager; PluginInstanceManager(Context context, String action, PluginListener<T> listener, - boolean allowMultiple, Looper looper, VersionInfo version, PluginManager manager) { + boolean allowMultiple, Looper looper, VersionInfo version, PluginManagerImpl manager) { this(context, context.getPackageManager(), action, listener, allowMultiple, looper, version, manager, Build.IS_DEBUGGABLE); } @@ -73,7 +73,7 @@ public class PluginInstanceManager<T extends Plugin> { @VisibleForTesting PluginInstanceManager(Context context, PackageManager pm, String action, PluginListener<T> listener, boolean allowMultiple, Looper looper, VersionInfo version, - PluginManager manager, boolean debuggable) { + PluginManagerImpl manager, boolean debuggable) { mMainHandler = new MainHandler(Looper.getMainLooper()); mPluginHandler = new PluginHandler(looper); mManager = manager; @@ -346,7 +346,7 @@ public class PluginInstanceManager<T extends Plugin> { .setContentText("Check to see if an OTA is available.\n" + e.getMessage()); } - Intent i = new Intent(PluginManager.DISABLE_PLUGIN).setData( + Intent i = new Intent(PluginManagerImpl.DISABLE_PLUGIN).setData( Uri.parse("package://" + component.flattenToString())); PendingIntent pi = PendingIntent.getBroadcast(mContext, 0, i, 0); nb.addAction(new Action.Builder(null, "Disable plugin", pi).build()); diff --git a/packages/SystemUI/src/com/android/systemui/plugins/PluginManager.java b/packages/SystemUI/src/com/android/systemui/plugins/PluginManager.java index 9ad862d4c33a..298eaf18dce5 100644 --- a/packages/SystemUI/src/com/android/systemui/plugins/PluginManager.java +++ b/packages/SystemUI/src/com/android/systemui/plugins/PluginManager.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016 The Android Open Source Project + * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of the License at @@ -14,276 +14,33 @@ package com.android.systemui.plugins; -import android.app.Notification; -import android.app.Notification.Action; -import android.app.NotificationManager; -import android.app.PendingIntent; -import android.content.BroadcastReceiver; -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageManager; -import android.content.pm.PackageManager.NameNotFoundException; -import android.content.res.Resources; -import android.net.Uri; -import android.os.Build; -import android.os.Handler; -import android.os.HandlerThread; -import android.os.Looper; -import android.os.SystemProperties; -import android.os.UserHandle; import android.text.TextUtils; -import android.util.ArrayMap; -import android.util.ArraySet; -import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; -import com.android.systemui.Dependency; -import com.android.systemui.plugins.PluginInstanceManager.PluginContextWrapper; -import com.android.systemui.plugins.PluginInstanceManager.PluginInfo; import com.android.systemui.plugins.annotations.ProvidesInterface; -import dalvik.system.PathClassLoader; +public interface PluginManager { -import java.lang.Thread.UncaughtExceptionHandler; -import java.util.Map; - -/** - * @see Plugin - */ -public class PluginManager extends BroadcastReceiver { - - public static final String PLUGIN_CHANGED = "com.android.systemui.action.PLUGIN_CHANGED"; - - static final String DISABLE_PLUGIN = "com.android.systemui.action.DISABLE_PLUGIN"; + String PLUGIN_CHANGED = "com.android.systemui.action.PLUGIN_CHANGED"; // must be one of the channels created in NotificationChannels.java - static final String NOTIFICATION_CHANNEL_ID = "ALR"; - - private static PluginManager sInstance; - - private final HandlerThread mBackgroundThread; - private final ArrayMap<PluginListener<?>, PluginInstanceManager> mPluginMap - = new ArrayMap<>(); - private final Map<String, ClassLoader> mClassLoaders = new ArrayMap<>(); - private final ArraySet<String> mOneShotPackages = new ArraySet<>(); - private final Context mContext; - private final PluginInstanceManagerFactory mFactory; - private final boolean isDebuggable; - private final PluginPrefs mPluginPrefs; - private ClassLoaderFilter mParentClassLoader; - private boolean mListening; - private boolean mHasOneShot; - - public PluginManager(Context context) { - this(context, new PluginInstanceManagerFactory(), - Build.IS_DEBUGGABLE, Thread.getDefaultUncaughtExceptionHandler()); - } - - @VisibleForTesting - PluginManager(Context context, PluginInstanceManagerFactory factory, boolean debuggable, - UncaughtExceptionHandler defaultHandler) { - mContext = context; - mFactory = factory; - mBackgroundThread = new HandlerThread("Plugins"); - mBackgroundThread.start(); - isDebuggable = debuggable; - mPluginPrefs = new PluginPrefs(mContext); - - PluginExceptionHandler uncaughtExceptionHandler = new PluginExceptionHandler( - defaultHandler); - Thread.setDefaultUncaughtExceptionHandler(uncaughtExceptionHandler); - if (isDebuggable) { - new Handler(mBackgroundThread.getLooper()).post(() -> { - // Plugin dependencies that don't have another good home can go here, but - // dependencies that have better places to init can happen elsewhere. - Dependency.get(PluginDependencyProvider.class) - .allowPluginDependency(ActivityStarter.class); - }); - } - } - - public <T extends Plugin> T getOneShotPlugin(Class<T> cls) { - ProvidesInterface info = cls.getDeclaredAnnotation(ProvidesInterface.class); - if (info == null) { - throw new RuntimeException(cls + " doesn't provide an interface"); - } - if (TextUtils.isEmpty(info.action())) { - throw new RuntimeException(cls + " doesn't provide an action"); - } - return getOneShotPlugin(info.action(), cls); - } - - public <T extends Plugin> T getOneShotPlugin(String action, Class<?> cls) { - if (!isDebuggable) { - // Never ever ever allow these on production builds, they are only for prototyping. - return null; - } - if (Looper.myLooper() != Looper.getMainLooper()) { - throw new RuntimeException("Must be called from UI thread"); - } - PluginInstanceManager<T> p = mFactory.createPluginInstanceManager(mContext, action, null, - false, mBackgroundThread.getLooper(), cls, this); - mPluginPrefs.addAction(action); - PluginInfo<T> info = p.getPlugin(); - if (info != null) { - mOneShotPackages.add(info.mPackage); - mHasOneShot = true; - startListening(); - return info.mPlugin; - } - return null; - } - - public <T extends Plugin> void addPluginListener(PluginListener<T> listener, Class<?> cls) { - addPluginListener(listener, cls, false); - } - - public <T extends Plugin> void addPluginListener(PluginListener<T> listener, Class<?> cls, - boolean allowMultiple) { - addPluginListener(getAction(cls), listener, cls, allowMultiple); - } - - public <T extends Plugin> void addPluginListener(String action, PluginListener<T> listener, - Class<?> cls) { - addPluginListener(action, listener, cls, false); - } - - public <T extends Plugin> void addPluginListener(String action, PluginListener<T> listener, - Class cls, boolean allowMultiple) { - if (!isDebuggable) { - // Never ever ever allow these on production builds, they are only for prototyping. - return; - } - mPluginPrefs.addAction(action); - PluginInstanceManager p = mFactory.createPluginInstanceManager(mContext, action, listener, - allowMultiple, mBackgroundThread.getLooper(), cls, this); - p.loadAll(); - mPluginMap.put(listener, p); - startListening(); - } - - public void removePluginListener(PluginListener<?> listener) { - if (!isDebuggable) { - // Never ever ever allow these on production builds, they are only for prototyping. - return; - } - if (!mPluginMap.containsKey(listener)) return; - mPluginMap.remove(listener).destroy(); - stopListening(); - } - - private void startListening() { - if (mListening) return; - mListening = true; - IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED); - filter.addAction(Intent.ACTION_PACKAGE_CHANGED); - filter.addAction(Intent.ACTION_PACKAGE_REMOVED); - filter.addAction(PLUGIN_CHANGED); - filter.addAction(DISABLE_PLUGIN); - filter.addDataScheme("package"); - mContext.registerReceiver(this, filter); - filter = new IntentFilter(Intent.ACTION_USER_UNLOCKED); - mContext.registerReceiver(this, filter); - } - - private void stopListening() { - // Never stop listening if a one-shot is present. - if (!mListening || mHasOneShot) return; - mListening = false; - mContext.unregisterReceiver(this); - } - - @Override - public void onReceive(Context context, Intent intent) { - if (Intent.ACTION_USER_UNLOCKED.equals(intent.getAction())) { - for (PluginInstanceManager manager : mPluginMap.values()) { - manager.loadAll(); - } - } else if (DISABLE_PLUGIN.equals(intent.getAction())) { - Uri uri = intent.getData(); - ComponentName component = ComponentName.unflattenFromString( - uri.toString().substring(10)); - mContext.getPackageManager().setComponentEnabledSetting(component, - PackageManager.COMPONENT_ENABLED_STATE_DISABLED, - PackageManager.DONT_KILL_APP); - mContext.getSystemService(NotificationManager.class).cancel(component.getClassName(), - SystemMessage.NOTE_PLUGIN); - } else { - Uri data = intent.getData(); - String pkg = data.getEncodedSchemeSpecificPart(); - if (mOneShotPackages.contains(pkg)) { - int icon = mContext.getResources().getIdentifier("tuner", "drawable", - mContext.getPackageName()); - int color = Resources.getSystem().getIdentifier( - "system_notification_accent_color", "color", "android"); - String label = pkg; - try { - PackageManager pm = mContext.getPackageManager(); - label = pm.getApplicationInfo(pkg, 0).loadLabel(pm).toString(); - } catch (NameNotFoundException e) { - } - // Localization not required as this will never ever appear in a user build. - final Notification.Builder nb = - new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID) - .setSmallIcon(icon) - .setWhen(0) - .setShowWhen(false) - .setPriority(Notification.PRIORITY_MAX) - .setVisibility(Notification.VISIBILITY_PUBLIC) - .setColor(mContext.getColor(color)) - .setContentTitle("Plugin \"" + label + "\" has updated") - .setContentText("Restart SysUI for changes to take effect."); - Intent i = new Intent("com.android.systemui.action.RESTART").setData( - Uri.parse("package://" + pkg)); - PendingIntent pi = PendingIntent.getBroadcast(mContext, 0, i, 0); - nb.addAction(new Action.Builder(null, "Restart SysUI", pi).build()); - mContext.getSystemService(NotificationManager.class).notifyAsUser(pkg, - SystemMessage.NOTE_PLUGIN, nb.build(), UserHandle.ALL); - } - clearClassLoader(pkg); - if (!Intent.ACTION_PACKAGE_REMOVED.equals(intent.getAction())) { - for (PluginInstanceManager manager : mPluginMap.values()) { - manager.onPackageChange(pkg); - } - } else { - for (PluginInstanceManager manager : mPluginMap.values()) { - manager.onPackageRemoved(pkg); - } - } - } - } + String NOTIFICATION_CHANNEL_ID = "ALR"; - public ClassLoader getClassLoader(String sourceDir, String pkg) { - if (mClassLoaders.containsKey(pkg)) { - return mClassLoaders.get(pkg); - } - ClassLoader classLoader = new PathClassLoader(sourceDir, getParentClassLoader()); - mClassLoaders.put(pkg, classLoader); - return classLoader; - } + <T extends Plugin> T getOneShotPlugin(Class<T> cls); + <T extends Plugin> T getOneShotPlugin(String action, Class<?> cls); - private void clearClassLoader(String pkg) { - mClassLoaders.remove(pkg); - } + <T extends Plugin> void addPluginListener(PluginListener<T> listener, Class<?> cls); + <T extends Plugin> void addPluginListener(PluginListener<T> listener, Class<?> cls, + boolean allowMultiple); + <T extends Plugin> void addPluginListener(String action, PluginListener<T> listener, + Class<?> cls); + <T extends Plugin> void addPluginListener(String action, PluginListener<T> listener, + Class cls, boolean allowMultiple); - ClassLoader getParentClassLoader() { - if (mParentClassLoader == null) { - // Lazily load this so it doesn't have any effect on devices without plugins. - mParentClassLoader = new ClassLoaderFilter(getClass().getClassLoader(), - "com.android.systemui.plugin"); - } - return mParentClassLoader; - } + void removePluginListener(PluginListener<?> listener); - public Context getContext(ApplicationInfo info, String pkg) throws NameNotFoundException { - ClassLoader classLoader = getClassLoader(info.sourceDir, pkg); - return new PluginContextWrapper(mContext.createApplicationContext(info, 0), classLoader); - } + <T> boolean dependsOn(Plugin p, Class<T> cls); - public static <P> String getAction(Class<P> cls) { + static <P> String getAction(Class<P> cls) { ProvidesInterface info = cls.getDeclaredAnnotation(ProvidesInterface.class); if (info == null) { throw new RuntimeException(cls + " doesn't provide an interface"); @@ -293,82 +50,4 @@ public class PluginManager extends BroadcastReceiver { } return info.action(); } - - public <T> boolean dependsOn(Plugin p, Class<T> cls) { - for (int i = 0; i < mPluginMap.size(); i++) { - if (mPluginMap.valueAt(i).dependsOn(p, cls)) { - return true; - } - } - return false; - } - - @VisibleForTesting - public static class PluginInstanceManagerFactory { - public <T extends Plugin> PluginInstanceManager createPluginInstanceManager(Context context, - String action, PluginListener<T> listener, boolean allowMultiple, Looper looper, - Class<?> cls, PluginManager manager) { - return new PluginInstanceManager(context, action, listener, allowMultiple, looper, - new VersionInfo().addClass(cls), manager); - } - } - - // This allows plugins to include any libraries or copied code they want by only including - // classes from the plugin library. - private static class ClassLoaderFilter extends ClassLoader { - private final String mPackage; - private final ClassLoader mBase; - - public ClassLoaderFilter(ClassLoader base, String pkg) { - super(ClassLoader.getSystemClassLoader()); - mBase = base; - mPackage = pkg; - } - - @Override - protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { - if (!name.startsWith(mPackage)) super.loadClass(name, resolve); - return mBase.loadClass(name); - } - } - - private class PluginExceptionHandler implements UncaughtExceptionHandler { - private final UncaughtExceptionHandler mHandler; - - private PluginExceptionHandler(UncaughtExceptionHandler handler) { - mHandler = handler; - } - - @Override - public void uncaughtException(Thread thread, Throwable throwable) { - if (SystemProperties.getBoolean("plugin.debugging", false)) { - mHandler.uncaughtException(thread, throwable); - return; - } - // Search for and disable plugins that may have been involved in this crash. - boolean disabledAny = checkStack(throwable); - if (!disabledAny) { - // We couldn't find any plugins involved in this crash, just to be safe - // disable all the plugins, so we can be sure that SysUI is running as - // best as possible. - for (PluginInstanceManager manager : mPluginMap.values()) { - manager.disableAll(); - } - } - - // Run the normal exception handler so we can crash and cleanup our state. - mHandler.uncaughtException(thread, throwable); - } - - private boolean checkStack(Throwable throwable) { - if (throwable == null) return false; - boolean disabledAny = false; - for (StackTraceElement element : throwable.getStackTrace()) { - for (PluginInstanceManager manager : mPluginMap.values()) { - disabledAny |= manager.checkAndDisable(element.getClassName()); - } - } - return disabledAny | checkStack(throwable.getCause()); - } - } } diff --git a/packages/SystemUI/src/com/android/systemui/plugins/PluginManagerImpl.java b/packages/SystemUI/src/com/android/systemui/plugins/PluginManagerImpl.java new file mode 100644 index 000000000000..1fb6c8704587 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/plugins/PluginManagerImpl.java @@ -0,0 +1,358 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package com.android.systemui.plugins; + +import android.app.Notification; +import android.app.Notification.Action; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.content.res.Resources; +import android.net.Uri; +import android.os.Build; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Looper; +import android.os.SystemProperties; +import android.os.UserHandle; +import android.text.TextUtils; +import android.util.ArrayMap; +import android.util.ArraySet; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; +import com.android.systemui.Dependency; +import com.android.systemui.plugins.PluginInstanceManager.PluginContextWrapper; +import com.android.systemui.plugins.PluginInstanceManager.PluginInfo; +import com.android.systemui.plugins.annotations.ProvidesInterface; + +import dalvik.system.PathClassLoader; + +import java.lang.Thread.UncaughtExceptionHandler; +import java.util.Map; + +/** + * @see Plugin + */ +public class PluginManagerImpl extends BroadcastReceiver implements PluginManager { + + static final String DISABLE_PLUGIN = "com.android.systemui.action.DISABLE_PLUGIN"; + + private static PluginManager sInstance; + + private final HandlerThread mBackgroundThread; + private final ArrayMap<PluginListener<?>, PluginInstanceManager> mPluginMap + = new ArrayMap<>(); + private final Map<String, ClassLoader> mClassLoaders = new ArrayMap<>(); + private final ArraySet<String> mOneShotPackages = new ArraySet<>(); + private final Context mContext; + private final PluginInstanceManagerFactory mFactory; + private final boolean isDebuggable; + private final PluginPrefs mPluginPrefs; + private ClassLoaderFilter mParentClassLoader; + private boolean mListening; + private boolean mHasOneShot; + + public PluginManagerImpl(Context context) { + this(context, new PluginInstanceManagerFactory(), + Build.IS_DEBUGGABLE, Thread.getDefaultUncaughtExceptionHandler()); + } + + @VisibleForTesting + PluginManagerImpl(Context context, PluginInstanceManagerFactory factory, boolean debuggable, + UncaughtExceptionHandler defaultHandler) { + mContext = context; + mFactory = factory; + mBackgroundThread = new HandlerThread("Plugins"); + mBackgroundThread.start(); + isDebuggable = debuggable; + mPluginPrefs = new PluginPrefs(mContext); + + PluginExceptionHandler uncaughtExceptionHandler = new PluginExceptionHandler( + defaultHandler); + Thread.setDefaultUncaughtExceptionHandler(uncaughtExceptionHandler); + if (isDebuggable) { + new Handler(mBackgroundThread.getLooper()).post(() -> { + // Plugin dependencies that don't have another good home can go here, but + // dependencies that have better places to init can happen elsewhere. + Dependency.get(PluginDependencyProvider.class) + .allowPluginDependency(ActivityStarter.class); + }); + } + } + + public <T extends Plugin> T getOneShotPlugin(Class<T> cls) { + ProvidesInterface info = cls.getDeclaredAnnotation(ProvidesInterface.class); + if (info == null) { + throw new RuntimeException(cls + " doesn't provide an interface"); + } + if (TextUtils.isEmpty(info.action())) { + throw new RuntimeException(cls + " doesn't provide an action"); + } + return getOneShotPlugin(info.action(), cls); + } + + public <T extends Plugin> T getOneShotPlugin(String action, Class<?> cls) { + if (!isDebuggable) { + // Never ever ever allow these on production builds, they are only for prototyping. + return null; + } + if (Looper.myLooper() != Looper.getMainLooper()) { + throw new RuntimeException("Must be called from UI thread"); + } + PluginInstanceManager<T> p = mFactory.createPluginInstanceManager(mContext, action, null, + false, mBackgroundThread.getLooper(), cls, this); + mPluginPrefs.addAction(action); + PluginInfo<T> info = p.getPlugin(); + if (info != null) { + mOneShotPackages.add(info.mPackage); + mHasOneShot = true; + startListening(); + return info.mPlugin; + } + return null; + } + + public <T extends Plugin> void addPluginListener(PluginListener<T> listener, Class<?> cls) { + addPluginListener(listener, cls, false); + } + + public <T extends Plugin> void addPluginListener(PluginListener<T> listener, Class<?> cls, + boolean allowMultiple) { + addPluginListener(PluginManager.getAction(cls), listener, cls, allowMultiple); + } + + public <T extends Plugin> void addPluginListener(String action, PluginListener<T> listener, + Class<?> cls) { + addPluginListener(action, listener, cls, false); + } + + public <T extends Plugin> void addPluginListener(String action, PluginListener<T> listener, + Class cls, boolean allowMultiple) { + if (!isDebuggable) { + // Never ever ever allow these on production builds, they are only for prototyping. + return; + } + mPluginPrefs.addAction(action); + PluginInstanceManager p = mFactory.createPluginInstanceManager(mContext, action, listener, + allowMultiple, mBackgroundThread.getLooper(), cls, this); + p.loadAll(); + mPluginMap.put(listener, p); + startListening(); + } + + public void removePluginListener(PluginListener<?> listener) { + if (!isDebuggable) { + // Never ever ever allow these on production builds, they are only for prototyping. + return; + } + if (!mPluginMap.containsKey(listener)) return; + mPluginMap.remove(listener).destroy(); + stopListening(); + } + + private void startListening() { + if (mListening) return; + mListening = true; + IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED); + filter.addAction(Intent.ACTION_PACKAGE_CHANGED); + filter.addAction(Intent.ACTION_PACKAGE_REMOVED); + filter.addAction(PLUGIN_CHANGED); + filter.addAction(DISABLE_PLUGIN); + filter.addDataScheme("package"); + mContext.registerReceiver(this, filter); + filter = new IntentFilter(Intent.ACTION_USER_UNLOCKED); + mContext.registerReceiver(this, filter); + } + + private void stopListening() { + // Never stop listening if a one-shot is present. + if (!mListening || mHasOneShot) return; + mListening = false; + mContext.unregisterReceiver(this); + } + + @Override + public void onReceive(Context context, Intent intent) { + if (Intent.ACTION_USER_UNLOCKED.equals(intent.getAction())) { + for (PluginInstanceManager manager : mPluginMap.values()) { + manager.loadAll(); + } + } else if (DISABLE_PLUGIN.equals(intent.getAction())) { + Uri uri = intent.getData(); + ComponentName component = ComponentName.unflattenFromString( + uri.toString().substring(10)); + mContext.getPackageManager().setComponentEnabledSetting(component, + PackageManager.COMPONENT_ENABLED_STATE_DISABLED, + PackageManager.DONT_KILL_APP); + mContext.getSystemService(NotificationManager.class).cancel(component.getClassName(), + SystemMessage.NOTE_PLUGIN); + } else { + Uri data = intent.getData(); + String pkg = data.getEncodedSchemeSpecificPart(); + if (mOneShotPackages.contains(pkg)) { + int icon = mContext.getResources().getIdentifier("tuner", "drawable", + mContext.getPackageName()); + int color = Resources.getSystem().getIdentifier( + "system_notification_accent_color", "color", "android"); + String label = pkg; + try { + PackageManager pm = mContext.getPackageManager(); + label = pm.getApplicationInfo(pkg, 0).loadLabel(pm).toString(); + } catch (NameNotFoundException e) { + } + // Localization not required as this will never ever appear in a user build. + final Notification.Builder nb = + new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID) + .setSmallIcon(icon) + .setWhen(0) + .setShowWhen(false) + .setPriority(Notification.PRIORITY_MAX) + .setVisibility(Notification.VISIBILITY_PUBLIC) + .setColor(mContext.getColor(color)) + .setContentTitle("Plugin \"" + label + "\" has updated") + .setContentText("Restart SysUI for changes to take effect."); + Intent i = new Intent("com.android.systemui.action.RESTART").setData( + Uri.parse("package://" + pkg)); + PendingIntent pi = PendingIntent.getBroadcast(mContext, 0, i, 0); + nb.addAction(new Action.Builder(null, "Restart SysUI", pi).build()); + mContext.getSystemService(NotificationManager.class).notifyAsUser(pkg, + SystemMessage.NOTE_PLUGIN, nb.build(), UserHandle.ALL); + } + clearClassLoader(pkg); + if (!Intent.ACTION_PACKAGE_REMOVED.equals(intent.getAction())) { + for (PluginInstanceManager manager : mPluginMap.values()) { + manager.onPackageChange(pkg); + } + } else { + for (PluginInstanceManager manager : mPluginMap.values()) { + manager.onPackageRemoved(pkg); + } + } + } + } + + public ClassLoader getClassLoader(String sourceDir, String pkg) { + if (mClassLoaders.containsKey(pkg)) { + return mClassLoaders.get(pkg); + } + ClassLoader classLoader = new PathClassLoader(sourceDir, getParentClassLoader()); + mClassLoaders.put(pkg, classLoader); + return classLoader; + } + + private void clearClassLoader(String pkg) { + mClassLoaders.remove(pkg); + } + + ClassLoader getParentClassLoader() { + if (mParentClassLoader == null) { + // Lazily load this so it doesn't have any effect on devices without plugins. + mParentClassLoader = new ClassLoaderFilter(getClass().getClassLoader(), + "com.android.systemui.plugin"); + } + return mParentClassLoader; + } + + public Context getContext(ApplicationInfo info, String pkg) throws NameNotFoundException { + ClassLoader classLoader = getClassLoader(info.sourceDir, pkg); + return new PluginContextWrapper(mContext.createApplicationContext(info, 0), classLoader); + } + + public <T> boolean dependsOn(Plugin p, Class<T> cls) { + for (int i = 0; i < mPluginMap.size(); i++) { + if (mPluginMap.valueAt(i).dependsOn(p, cls)) { + return true; + } + } + return false; + } + + @VisibleForTesting + public static class PluginInstanceManagerFactory { + public <T extends Plugin> PluginInstanceManager createPluginInstanceManager(Context context, + String action, PluginListener<T> listener, boolean allowMultiple, Looper looper, + Class<?> cls, PluginManagerImpl manager) { + return new PluginInstanceManager(context, action, listener, allowMultiple, looper, + new VersionInfo().addClass(cls), manager); + } + } + + // This allows plugins to include any libraries or copied code they want by only including + // classes from the plugin library. + private static class ClassLoaderFilter extends ClassLoader { + private final String mPackage; + private final ClassLoader mBase; + + public ClassLoaderFilter(ClassLoader base, String pkg) { + super(ClassLoader.getSystemClassLoader()); + mBase = base; + mPackage = pkg; + } + + @Override + protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { + if (!name.startsWith(mPackage)) super.loadClass(name, resolve); + return mBase.loadClass(name); + } + } + + private class PluginExceptionHandler implements UncaughtExceptionHandler { + private final UncaughtExceptionHandler mHandler; + + private PluginExceptionHandler(UncaughtExceptionHandler handler) { + mHandler = handler; + } + + @Override + public void uncaughtException(Thread thread, Throwable throwable) { + if (SystemProperties.getBoolean("plugin.debugging", false)) { + mHandler.uncaughtException(thread, throwable); + return; + } + // Search for and disable plugins that may have been involved in this crash. + boolean disabledAny = checkStack(throwable); + if (!disabledAny) { + // We couldn't find any plugins involved in this crash, just to be safe + // disable all the plugins, so we can be sure that SysUI is running as + // best as possible. + for (PluginInstanceManager manager : mPluginMap.values()) { + manager.disableAll(); + } + } + + // Run the normal exception handler so we can crash and cleanup our state. + mHandler.uncaughtException(thread, throwable); + } + + private boolean checkStack(Throwable throwable) { + if (throwable == null) return false; + boolean disabledAny = false; + for (StackTraceElement element : throwable.getStackTrace()) { + for (PluginInstanceManager manager : mPluginMap.values()) { + disabledAny |= manager.checkAndDisable(element.getClassName()); + } + } + return disabledAny | checkStack(throwable.getCause()); + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/CustomizeTileView.java b/packages/SystemUI/src/com/android/systemui/qs/customize/CustomizeTileView.java index 0629d66767d0..b0806429df28 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/customize/CustomizeTileView.java +++ b/packages/SystemUI/src/com/android/systemui/qs/customize/CustomizeTileView.java @@ -35,19 +35,13 @@ public class CustomizeTileView extends QSTileView { protected void createLabel() { super.createLabel(); mLabelMinLines = mLabel.getMinLines(); - View view = LayoutInflater.from(mContext).inflate(R.layout.qs_tile_label, null); - mAppLabel = (TextView) view.findViewById(R.id.tile_label); + mAppLabel = findViewById(R.id.app_label); mAppLabel.setAlpha(.6f); - mAppLabel.setSingleLine(true); - addView(view); } public void setShowAppLabel(boolean showAppLabel) { mAppLabel.setVisibility(showAppLabel ? View.VISIBLE : View.GONE); mLabel.setSingleLine(showAppLabel); - if (!showAppLabel) { - mLabel.setMinLines(mLabelMinLines); - } } public void setAppLabel(CharSequence label) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java index c0fb4d5e956c..d8e554254076 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java +++ b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java @@ -41,10 +41,10 @@ import com.android.internal.logging.nano.MetricsProto; import com.android.systemui.Dependency; import com.android.systemui.R; import com.android.systemui.plugins.qs.QS; -import com.android.systemui.qs.QSDetailClipper; import com.android.systemui.plugins.qs.QSTile; -import com.android.systemui.statusbar.phone.NotificationsQuickSettingsContainer; +import com.android.systemui.qs.QSDetailClipper; import com.android.systemui.qs.QSTileHost; +import com.android.systemui.statusbar.phone.NotificationsQuickSettingsContainer; import com.android.systemui.statusbar.policy.KeyguardMonitor; import com.android.systemui.statusbar.policy.KeyguardMonitor.Callback; @@ -74,6 +74,7 @@ public class QSCustomizer extends LinearLayout implements OnMenuItemClickListene private boolean mFinishedFetchingTiles = false; private int mX; private int mY; + private boolean mOpening; public QSCustomizer(Context context, AttributeSet attrs) { super(new ContextThemeWrapper(context, R.style.edit_theme), attrs); @@ -140,6 +141,7 @@ public class QSCustomizer extends LinearLayout implements OnMenuItemClickListene mY = y; MetricsLogger.visible(getContext(), MetricsProto.MetricsEvent.QS_EDIT); isShown = true; + mOpening = true; setTileSpecs(); setVisibility(View.VISIBLE); mClipper.animateCircularClip(x, y, true, mExpandAnimationListener); @@ -226,7 +228,8 @@ public class QSCustomizer extends LinearLayout implements OnMenuItemClickListene } private final Callback mKeyguardCallback = () -> { - if (Dependency.get(KeyguardMonitor.class).isShowing()) { + if (!isAttachedToWindow()) return; + if (Dependency.get(KeyguardMonitor.class).isShowing() && !mOpening) { hide(0, 0); } }; @@ -237,11 +240,13 @@ public class QSCustomizer extends LinearLayout implements OnMenuItemClickListene if (isShown) { setCustomizing(true); } + mOpening = false; mNotifQsContainer.setCustomizerAnimating(false); } @Override public void onAnimationCancel(Animator animation) { + mOpening = false; mNotifQsContainer.setCustomizerAnimating(false); } }; diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java b/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java index 547bef72863f..2ac592ffe5bd 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java +++ b/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java @@ -165,6 +165,7 @@ public class TileQueryHelper { } TileInfo info = new TileInfo(); info.state = state; + info.state.dualTarget = false; // No dual targets in edit. info.state.expandedAccessibilityClassName = Button.class.getName(); info.spec = spec; diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java index 5ac78919f006..948954c2bd99 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java @@ -369,7 +369,6 @@ public abstract class QSTileImpl<TState extends State> implements QSTile { Dependency.get(ActivityStarter.class).postStartActivityDismissingKeyguard( intent, 0); } else { - mAnnounceNextStateChange = true; handleClick(); } } else if (msg.what == SECONDARY_CLICK) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileView.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileView.java index 2c04e82bad72..d2ae6e9f5354 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileView.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileView.java @@ -36,11 +36,12 @@ import libcore.util.Objects; /** View that represents a standard quick settings tile. **/ public class QSTileView extends QSTileBaseView { - private final View mDivider; + private View mDivider; protected TextView mLabel; private ImageView mPadLock; private int mState; private ViewGroup mLabelContainer; + private View mExpandIndicator; public QSTileView(Context context, QSIconView icon) { this(context, icon, false); @@ -54,8 +55,6 @@ public class QSTileView extends QSTileBaseView { setClickable(true); setId(View.generateViewId()); - mDivider = LayoutInflater.from(context).inflate(R.layout.divider, this, false); - addView(mDivider); createLabel(); setOrientation(VERTICAL); setGravity(Gravity.CENTER); @@ -81,8 +80,10 @@ public class QSTileView extends QSTileBaseView { .inflate(R.layout.qs_tile_label, this, false); mLabelContainer.setClipChildren(false); mLabelContainer.setClipToPadding(false); - mLabel = (TextView) mLabelContainer.findViewById(R.id.tile_label); - mPadLock = (ImageView) mLabelContainer.findViewById(R.id.restricted_padlock); + mLabel = mLabelContainer.findViewById(R.id.tile_label); + mPadLock = mLabelContainer.findViewById(R.id.restricted_padlock); + mDivider = mLabelContainer.findViewById(R.id.underline); + mExpandIndicator = mLabelContainer.findViewById(R.id.expand_indicator); addView(mLabelContainer); } @@ -101,6 +102,7 @@ public class QSTileView extends QSTileBaseView { mLabel.setText(state.label); } mDivider.setVisibility(state.dualTarget ? View.VISIBLE : View.INVISIBLE); + mExpandIndicator.setVisibility(state.dualTarget ? View.VISIBLE : View.GONE); if (state.dualTarget != mLabelContainer.isClickable()) { mLabelContainer.setClickable(state.dualTarget); mLabelContainer.setLongClickable(state.dualTarget); diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java index 47468aeaeb97..ac24e2e715df 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java +++ b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java @@ -67,6 +67,7 @@ import android.os.UserHandle; import android.os.UserManager; import android.provider.Settings; import android.util.ArraySet; +import android.util.LauncherIcons; import android.util.Log; import android.util.MutableBoolean; import android.view.Display; @@ -142,6 +143,7 @@ public class SystemServicesProxy { int mDummyThumbnailHeight; Paint mBgProtectionPaint; Canvas mBgProtectionCanvas; + LauncherIcons mLauncherIcons; private final Handler mHandler = new H(); @@ -299,6 +301,7 @@ public class SystemServicesProxy { Collections.addAll(sRecentsBlacklist, res.getStringArray(R.array.recents_blacklist_array)); } + mLauncherIcons = new LauncherIcons(context); } /** @@ -834,7 +837,7 @@ public class SystemServicesProxy { return new ColorDrawable(0xFF666666); } - Drawable icon = info.loadIcon(mPm); + Drawable icon = mLauncherIcons.wrapIconDrawableWithShadow(info.loadIcon(mPm)); return getBadgedIcon(icon, userId); } @@ -850,7 +853,7 @@ public class SystemServicesProxy { return new ColorDrawable(0xFF666666); } - Drawable icon = appInfo.loadIcon(mPm); + Drawable icon = mLauncherIcons.wrapIconDrawableWithShadow(appInfo.loadIcon(mPm)); return getBadgedIcon(icon, userId); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java index dceeb7415c20..092408961eab 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java @@ -29,7 +29,6 @@ import android.os.BatteryManager; import android.os.BatteryStats; import android.os.Handler; import android.os.Message; -import android.os.PowerManager; import android.os.RemoteException; import android.os.ServiceManager; import android.os.UserHandle; @@ -95,12 +94,20 @@ public class KeyguardIndicationController { private final DevicePolicyManager mDevicePolicyManager; private boolean mDozing; + /** + * Creates a new KeyguardIndicationController and registers callbacks. + */ public KeyguardIndicationController(Context context, ViewGroup indicationArea, LockIcon lockIcon) { this(context, indicationArea, lockIcon, WakeLock.createPartial(context, "Doze:KeyguardIndication")); + + registerCallbacks(KeyguardUpdateMonitor.getInstance(context)); } + /** + * Creates a new KeyguardIndicationController for testing. Does *not* register callbacks. + */ @VisibleForTesting KeyguardIndicationController(Context context, ViewGroup indicationArea, LockIcon lockIcon, WakeLock wakeLock) { @@ -124,12 +131,15 @@ public class KeyguardIndicationController { mDevicePolicyManager = (DevicePolicyManager) context.getSystemService( Context.DEVICE_POLICY_SERVICE); - KeyguardUpdateMonitor.getInstance(context).registerCallback(getKeyguardCallback()); - context.registerReceiverAsUser(mTickReceiver, UserHandle.SYSTEM, + updateDisclosure(); + } + + private void registerCallbacks(KeyguardUpdateMonitor monitor) { + monitor.registerCallback(getKeyguardCallback()); + + mContext.registerReceiverAsUser(mTickReceiver, UserHandle.SYSTEM, new IntentFilter(Intent.ACTION_TIME_TICK), null, Dependency.get(Dependency.TIME_TICK_HANDLER)); - - updateDisclosure(); } /** @@ -331,7 +341,7 @@ public class KeyguardIndicationController { mStatusBarKeyguardViewManager = statusBarKeyguardViewManager; } - BroadcastReceiver mTickReceiver = new BroadcastReceiver() { + private final BroadcastReceiver mTickReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { mHandler.post(() -> { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInfo.java index af464c6d138f..5db5498e9bb6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInfo.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInfo.java @@ -136,12 +136,7 @@ public class NotificationInfo extends LinearLayout implements GutsContent { if (channel.getId().equals(NotificationChannel.DEFAULT_CHANNEL_ID)) { channelNameText = mContext.getString(R.string.notification_header_default_channel); } else { - if (info != null && channel.getNameResId() != 0) { - channelNameText = pm.getText(pkg, channel.getNameResId(), info); - } - if (channel.getName() != null) { - channelNameText = channel.getName(); - } + channelNameText = channel.getName(); } ((TextView) findViewById(R.id.pkgname)).setText(appName); ((TextView) findViewById(R.id.channel_name)).setText(channelNameText); @@ -154,12 +149,7 @@ public class NotificationInfo extends LinearLayout implements GutsContent { iNotificationManager.getNotificationChannelGroupForPackage( channel.getGroup(), pkg, appUid); if (notificationChannelGroup != null) { - if (info != null && notificationChannelGroup.getNameResId() != 0) { - groupName = pm.getText(pkg, notificationChannelGroup.getNameResId(), info); - } - if (notificationChannelGroup.getName() != null) { - groupName = notificationChannelGroup.getName(); - } + groupName = notificationChannelGroup.getName(); } } catch (RemoteException e) { Log.e(TAG, e.toString()); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java index 6cd3eae366e8..aa0fcbd28650 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java @@ -45,6 +45,7 @@ import com.android.systemui.statusbar.policy.NetworkController; public class CollapsedStatusBarFragment extends Fragment implements CommandQueue.Callbacks { public static final String TAG = "CollapsedStatusBarFragment"; + private static final String EXTRA_PANEL_STATE = "panel_state"; private PhoneStatusBarView mStatusBar; private KeyguardMonitor mKeyguardMonitor; private NetworkController mNetworkController; @@ -73,14 +74,23 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); mStatusBar = (PhoneStatusBarView) view; - mDarkIconManager = new DarkIconManager((LinearLayout) view.findViewById(R.id.statusIcons)); + if (savedInstanceState != null && savedInstanceState.containsKey(EXTRA_PANEL_STATE)) { + mStatusBar.go(savedInstanceState.getInt(EXTRA_PANEL_STATE)); + } + mDarkIconManager = new DarkIconManager(view.findViewById(R.id.statusIcons)); Dependency.get(StatusBarIconController.class).addIconGroup(mDarkIconManager); - mSystemIconArea = (LinearLayout) mStatusBar.findViewById(R.id.system_icon_area); + mSystemIconArea = mStatusBar.findViewById(R.id.system_icon_area); mSignalClusterView = reinflateSignalCluster(mStatusBar); Dependency.get(DarkIconDispatcher.class).addDarkReceiver(mSignalClusterView); } @Override + public void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + outState.putInt(EXTRA_PANEL_STATE, mStatusBar.getState()); + } + + @Override public void onResume() { super.onResume(); SysUiServiceProvider.getComponent(getContext(), CommandQueue.class).addCallbacks(this); @@ -101,8 +111,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue public void initNotificationIconArea(NotificationIconAreaController notificationIconAreaController) { - ViewGroup notificationIconArea = (ViewGroup) mStatusBar - .findViewById(R.id.notification_icon_area); + ViewGroup notificationIconArea = mStatusBar.findViewById(R.id.notification_icon_area); mNotificationIconAreaInner = notificationIconAreaController.getNotificationInnerAreaView(); if (mNotificationIconAreaInner.getParent() != null) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java index 8a3c4e3b7d76..2b52b48a6819 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java @@ -35,6 +35,7 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.res.Configuration; +import android.database.ContentObserver; import android.graphics.PixelFormat; import android.graphics.Rect; import android.inputmethodservice.InputMethodService; @@ -46,6 +47,7 @@ import android.os.Message; import android.os.PowerManager; import android.os.RemoteException; import android.os.UserHandle; +import android.provider.Settings; import android.support.annotation.VisibleForTesting; import android.telecom.TelecomManager; import android.text.TextUtils; @@ -104,6 +106,7 @@ public class NavigationBarFragment extends Fragment implements Callbacks { private int mNavigationIconHints = 0; private int mNavigationBarMode; private AccessibilityManager mAccessibilityManager; + private MagnificationContentObserver mMagnificationObserver; private int mDisabledFlags1; private StatusBar mStatusBar; @@ -135,6 +138,12 @@ public class NavigationBarFragment extends Fragment implements Callbacks { mAccessibilityManager = getContext().getSystemService(AccessibilityManager.class); mAccessibilityManager.addAccessibilityServicesStateChangeListener( this::updateAccessibilityServicesState); + mMagnificationObserver = new MagnificationContentObserver( + getContext().getMainThreadHandler()); + getContext().getContentResolver().registerContentObserver(Settings.Secure.getUriFor( + Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED), false, + mMagnificationObserver); + if (savedInstanceState != null) { mDisabledFlags1 = savedInstanceState.getInt(EXTRA_DISABLE_STATE, 0); } @@ -154,6 +163,7 @@ public class NavigationBarFragment extends Fragment implements Callbacks { mCommandQueue.removeCallbacks(this); mAccessibilityManager.removeAccessibilityServicesStateChangeListener( this::updateAccessibilityServicesState); + getContext().getContentResolver().unregisterContentObserver(mMagnificationObserver); try { WindowManagerGlobal.getWindowManagerService() .removeRotationWatcher(mRotationWatcher); @@ -387,6 +397,7 @@ public class NavigationBarFragment extends Fragment implements Callbacks { ButtonDispatcher accessibilityButton = mNavigationBarView.getAccessibilityButton(); accessibilityButton.setOnClickListener(this::onAccessibilityClick); accessibilityButton.setOnLongClickListener(this::onAccessibilityLongClick); + updateAccessibilityServicesState(); } private boolean onHomeTouch(View v, MotionEvent event) { @@ -550,10 +561,18 @@ public class NavigationBarFragment extends Fragment implements Callbacks { } private void updateAccessibilityServicesState() { + int requestingServices = 0; + try { + if (Settings.Secure.getInt(getContext().getContentResolver(), + Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED) == 1) { + requestingServices++; + } + } catch (Settings.SettingNotFoundException e) { + } + final List<AccessibilityServiceInfo> services = mAccessibilityManager.getEnabledAccessibilityServiceList( AccessibilityServiceInfo.FEEDBACK_ALL_MASK); - int requestingServices = 0; for (int i = services.size() - 1; i >= 0; --i) { AccessibilityServiceInfo info = services.get(i); if ((info.flags & AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON) != 0) { @@ -600,6 +619,18 @@ public class NavigationBarFragment extends Fragment implements Callbacks { mNavigationBarView.getBarTransitions().finishAnimations(); } + private class MagnificationContentObserver extends ContentObserver { + + public MagnificationContentObserver(Handler handler) { + super(handler); + } + + @Override + public void onChange(boolean selfChange) { + NavigationBarFragment.this.updateAccessibilityServicesState(); + } + } + private final Stub mRotationWatcher = new Stub() { @Override public void onRotationChanged(int rotation) throws RemoteException { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java index 23d3816b3ffe..cefe97200ca9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java @@ -46,6 +46,10 @@ public abstract class PanelBar extends FrameLayout { mState = state; } + public int getState() { + return mState; + } + public PanelBar(Context context, AttributeSet attrs) { super(context, attrs); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardMonitorImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardMonitorImpl.java index a87b50a4a8df..d6c080ad1f44 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardMonitorImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardMonitorImpl.java @@ -129,6 +129,7 @@ public class KeyguardMonitorImpl extends KeyguardUpdateMonitorCallback public void notifyKeyguardDoneFading() { mKeyguardFadingAway = false; + mKeyguardGoingAway = false; notifyKeyguardChanged(); } diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java index 7c4f2eec8b11..369ce6939cdf 100644 --- a/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java +++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java @@ -1,228 +1,96 @@ /* - * Copyright (C) 2015 The Android Open Source Project + * Copyright (C) 2017 The Android Open Source Project * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * 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 + * 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.tuner; import android.app.ActivityManager; import android.content.BroadcastReceiver; import android.content.ComponentName; -import android.content.ContentResolver; import android.content.Context; import android.content.DialogInterface; import android.content.DialogInterface.OnClickListener; import android.content.Intent; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; -import android.content.pm.UserInfo; -import android.database.ContentObserver; -import android.net.Uri; -import android.os.Handler; -import android.os.Looper; import android.os.UserHandle; -import android.os.UserManager; import android.provider.Settings; -import android.provider.Settings.Secure; -import android.text.TextUtils; -import android.util.ArrayMap; -import android.util.ArraySet; import static android.provider.Settings.System.SHOW_BATTERY_PERCENT; import com.android.systemui.DemoMode; import com.android.systemui.Dependency; import com.android.systemui.R; -import com.android.systemui.SysUiServiceProvider; -import com.android.systemui.SystemUI; -import com.android.systemui.SystemUIApplication; -import com.android.systemui.settings.CurrentUserTracker; -import com.android.systemui.statusbar.phone.StatusBarIconController; import com.android.systemui.statusbar.phone.SystemUIDialog; -import com.android.systemui.util.leak.LeakDetector; - -import java.util.HashMap; -import java.util.HashSet; -import java.util.Set; - -public class TunerService { +public abstract class TunerService { public static final String ACTION_CLEAR = "com.android.systemui.action.CLEAR_TUNER"; - private static final String TUNER_VERSION = "sysui_tuner_version"; - - private static final int CURRENT_TUNER_VERSION = 1; - - private final Observer mObserver = new Observer(); - // Map of Uris we listen on to their settings keys. - private final ArrayMap<Uri, String> mListeningUris = new ArrayMap<>(); - // Map of settings keys to the listener. - private final HashMap<String, Set<Tunable>> mTunableLookup = new HashMap<>(); - // Set of all tunables, used for leak detection. - private final HashSet<Tunable> mTunables = LeakDetector.ENABLED ? new HashSet<>() : null; - private final Context mContext; - - private ContentResolver mContentResolver; - private int mCurrentUser; - private CurrentUserTracker mUserTracker; - - public TunerService(Context context) { - mContext = context; - mContentResolver = mContext.getContentResolver(); - - for (UserInfo user : UserManager.get(mContext).getUsers()) { - mCurrentUser = user.getUserHandle().getIdentifier(); - if (getValue(TUNER_VERSION, 0) != CURRENT_TUNER_VERSION) { - upgradeTuner(getValue(TUNER_VERSION, 0), CURRENT_TUNER_VERSION); - } - } - - mCurrentUser = ActivityManager.getCurrentUser(); - mUserTracker = new CurrentUserTracker(mContext) { - @Override - public void onUserSwitched(int newUserId) { - mCurrentUser = newUserId; - reloadAll(); - reregisterAll(); - } - }; - mUserTracker.startTracking(); - } - - public void destroy() { - mUserTracker.stopTracking(); - } - - private void upgradeTuner(int oldVersion, int newVersion) { - if (oldVersion < 1) { - String blacklistStr = getValue(StatusBarIconController.ICON_BLACKLIST); - if (blacklistStr != null) { - ArraySet<String> iconBlacklist = - StatusBarIconController.getIconBlacklist(blacklistStr); + public abstract void clearAll(); + public abstract void destroy(); - iconBlacklist.add("rotate"); - iconBlacklist.add("headset"); - - Settings.Secure.putStringForUser(mContentResolver, - StatusBarIconController.ICON_BLACKLIST, - TextUtils.join(",", iconBlacklist), mCurrentUser); - } - } - setValue(TUNER_VERSION, newVersion); - } + public abstract String getValue(String setting); + public abstract int getValue(String setting, int def); + public abstract String getValue(String setting, String def); - public String getValue(String setting) { - return Settings.Secure.getStringForUser(mContentResolver, setting, mCurrentUser); - } + public abstract void setValue(String setting, String value); + public abstract void setValue(String setting, int value); - public void setValue(String setting, String value) { - Settings.Secure.putStringForUser(mContentResolver, setting, value, mCurrentUser); - } + public abstract void addTunable(Tunable tunable, String... keys); + public abstract void removeTunable(Tunable tunable); - public int getValue(String setting, int def) { - return Settings.Secure.getIntForUser(mContentResolver, setting, def, mCurrentUser); - } - - public String getValue(String setting, String def) { - String ret = Secure.getStringForUser(mContentResolver, setting, mCurrentUser); - if (ret == null) return def; - return ret; - } - - public void setValue(String setting, int value) { - Settings.Secure.putIntForUser(mContentResolver, setting, value, mCurrentUser); - } - - public void addTunable(Tunable tunable, String... keys) { - for (String key : keys) { - addTunable(tunable, key); - } + public interface Tunable { + void onTuningChanged(String key, String newValue); } - private void addTunable(Tunable tunable, String key) { - if (!mTunableLookup.containsKey(key)) { - mTunableLookup.put(key, new ArraySet<Tunable>()); - } - mTunableLookup.get(key).add(tunable); - if (LeakDetector.ENABLED) { - mTunables.add(tunable); - Dependency.get(LeakDetector.class).trackCollection(mTunables, "TunerService.mTunables"); - } - Uri uri = Settings.Secure.getUriFor(key); - if (!mListeningUris.containsKey(uri)) { - mListeningUris.put(uri, key); - mContentResolver.registerContentObserver(uri, false, mObserver, mCurrentUser); + private static Context userContext(Context context) { + try { + return context.createPackageContextAsUser(context.getPackageName(), 0, + new UserHandle(ActivityManager.getCurrentUser())); + } catch (NameNotFoundException e) { + return context; } - // Send the first state. - String value = Settings.Secure.getStringForUser(mContentResolver, key, mCurrentUser); - tunable.onTuningChanged(key, value); } - public void removeTunable(Tunable tunable) { - for (Set<Tunable> list : mTunableLookup.values()) { - list.remove(tunable); - } - if (LeakDetector.ENABLED) { - mTunables.remove(tunable); - } - } + public static final void setTunerEnabled(Context context, boolean enabled) { + userContext(context).getPackageManager().setComponentEnabledSetting( + new ComponentName(context, TunerActivity.class), + enabled ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED + : PackageManager.COMPONENT_ENABLED_STATE_DISABLED, + PackageManager.DONT_KILL_APP); - protected void reregisterAll() { - if (mListeningUris.size() == 0) { - return; - } - mContentResolver.unregisterContentObserver(mObserver); - for (Uri uri : mListeningUris.keySet()) { - mContentResolver.registerContentObserver(uri, false, mObserver, mCurrentUser); - } + userContext(context).getPackageManager().setComponentEnabledSetting( + new ComponentName(context, TunerActivity.ACTIVITY_ALIAS_NAME), + enabled ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED + : PackageManager.COMPONENT_ENABLED_STATE_DISABLED, + PackageManager.DONT_KILL_APP); } - public void reloadSetting(Uri uri) { - String key = mListeningUris.get(uri); - Set<Tunable> tunables = mTunableLookup.get(key); - if (tunables == null) { - return; - } - String value = Settings.Secure.getStringForUser(mContentResolver, key, mCurrentUser); - for (Tunable tunable : tunables) { - tunable.onTuningChanged(key, value); - } + public static final boolean isTunerEnabled(Context context) { + return userContext(context).getPackageManager().getComponentEnabledSetting( + new ComponentName(context, TunerActivity.class)) + == PackageManager.COMPONENT_ENABLED_STATE_ENABLED; } - private void reloadAll() { - for (String key : mTunableLookup.keySet()) { - String value = Settings.Secure.getStringForUser(mContentResolver, key, - mCurrentUser); - for (Tunable tunable : mTunableLookup.get(key)) { - tunable.onTuningChanged(key, value); + public static class ClearReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + if (ACTION_CLEAR.equals(intent.getAction())) { + Dependency.get(TunerService.class).clearAll(); } } } - public void clearAll() { - // A couple special cases. - Settings.Global.putString(mContentResolver, DemoMode.DEMO_MODE_ALLOWED, null); - Settings.System.putString(mContentResolver, - SHOW_BATTERY_PERCENT, null); - Intent intent = new Intent(DemoMode.ACTION_DEMO); - intent.putExtra(DemoMode.EXTRA_COMMAND, DemoMode.COMMAND_EXIT); - mContext.sendBroadcast(intent); - - for (String key : mTunableLookup.keySet()) { - Settings.Secure.putString(mContentResolver, key, null); - } - } - public static final void showResetRequest(final Context context, final Runnable onDisabled) { SystemUIDialog dialog = new SystemUIDialog(context); dialog.setShowForAllUsers(true); @@ -247,59 +115,4 @@ public class TunerService { }); dialog.show(); } - - public static final void setTunerEnabled(Context context, boolean enabled) { - userContext(context).getPackageManager().setComponentEnabledSetting( - new ComponentName(context, TunerActivity.class), - enabled ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED - : PackageManager.COMPONENT_ENABLED_STATE_DISABLED, - PackageManager.DONT_KILL_APP); - - userContext(context).getPackageManager().setComponentEnabledSetting( - new ComponentName(context, TunerActivity.ACTIVITY_ALIAS_NAME), - enabled ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED - : PackageManager.COMPONENT_ENABLED_STATE_DISABLED, - PackageManager.DONT_KILL_APP); - } - - public static final boolean isTunerEnabled(Context context) { - return userContext(context).getPackageManager().getComponentEnabledSetting( - new ComponentName(context, TunerActivity.class)) - == PackageManager.COMPONENT_ENABLED_STATE_ENABLED; - } - - private static Context userContext(Context context) { - try { - return context.createPackageContextAsUser(context.getPackageName(), 0, - new UserHandle(ActivityManager.getCurrentUser())); - } catch (NameNotFoundException e) { - return context; - } - } - - private class Observer extends ContentObserver { - public Observer() { - super(new Handler(Looper.getMainLooper())); - } - - @Override - public void onChange(boolean selfChange, Uri uri, int userId) { - if (userId == ActivityManager.getCurrentUser()) { - reloadSetting(uri); - } - } - } - - public interface Tunable { - void onTuningChanged(String key, String newValue); - } - - public static class ClearReceiver extends BroadcastReceiver { - @Override - public void onReceive(Context context, Intent intent) { - if (ACTION_CLEAR.equals(intent.getAction())) { - Dependency.get(TunerService.class).clearAll(); - } - } - } } diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java new file mode 100644 index 000000000000..8e584bc362eb --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java @@ -0,0 +1,242 @@ +/* + * Copyright (C) 2015 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.tuner; + +import android.app.ActivityManager; +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.content.ContentResolver; +import android.content.Context; +import android.content.DialogInterface; +import android.content.DialogInterface.OnClickListener; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.content.pm.UserInfo; +import android.database.ContentObserver; +import android.net.Uri; +import android.os.Handler; +import android.os.Looper; +import android.os.UserHandle; +import android.os.UserManager; +import android.provider.Settings; +import android.provider.Settings.Secure; +import android.text.TextUtils; +import android.util.ArrayMap; +import android.util.ArraySet; + +import com.android.systemui.DemoMode; +import com.android.systemui.Dependency; +import com.android.systemui.R; +import com.android.systemui.SysUiServiceProvider; +import com.android.systemui.SystemUI; +import com.android.systemui.SystemUIApplication; +import com.android.systemui.settings.CurrentUserTracker; +import com.android.systemui.statusbar.phone.StatusBarIconController; +import com.android.systemui.statusbar.phone.SystemUIDialog; +import com.android.systemui.util.leak.LeakDetector; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Set; + + +public class TunerServiceImpl extends TunerService { + + private static final String TUNER_VERSION = "sysui_tuner_version"; + + private static final int CURRENT_TUNER_VERSION = 1; + + private final Observer mObserver = new Observer(); + // Map of Uris we listen on to their settings keys. + private final ArrayMap<Uri, String> mListeningUris = new ArrayMap<>(); + // Map of settings keys to the listener. + private final HashMap<String, Set<Tunable>> mTunableLookup = new HashMap<>(); + // Set of all tunables, used for leak detection. + private final HashSet<Tunable> mTunables = LeakDetector.ENABLED ? new HashSet<>() : null; + private final Context mContext; + + private ContentResolver mContentResolver; + private int mCurrentUser; + private CurrentUserTracker mUserTracker; + + public TunerServiceImpl(Context context) { + mContext = context; + mContentResolver = mContext.getContentResolver(); + + for (UserInfo user : UserManager.get(mContext).getUsers()) { + mCurrentUser = user.getUserHandle().getIdentifier(); + if (getValue(TUNER_VERSION, 0) != CURRENT_TUNER_VERSION) { + upgradeTuner(getValue(TUNER_VERSION, 0), CURRENT_TUNER_VERSION); + } + } + + mCurrentUser = ActivityManager.getCurrentUser(); + mUserTracker = new CurrentUserTracker(mContext) { + @Override + public void onUserSwitched(int newUserId) { + mCurrentUser = newUserId; + reloadAll(); + reregisterAll(); + } + }; + mUserTracker.startTracking(); + } + + @Override + public void destroy() { + mUserTracker.stopTracking(); + } + + private void upgradeTuner(int oldVersion, int newVersion) { + if (oldVersion < 1) { + String blacklistStr = getValue(StatusBarIconController.ICON_BLACKLIST); + if (blacklistStr != null) { + ArraySet<String> iconBlacklist = + StatusBarIconController.getIconBlacklist(blacklistStr); + + iconBlacklist.add("rotate"); + iconBlacklist.add("headset"); + + Settings.Secure.putStringForUser(mContentResolver, + StatusBarIconController.ICON_BLACKLIST, + TextUtils.join(",", iconBlacklist), mCurrentUser); + } + } + setValue(TUNER_VERSION, newVersion); + } + + @Override + public String getValue(String setting) { + return Settings.Secure.getStringForUser(mContentResolver, setting, mCurrentUser); + } + + @Override + public void setValue(String setting, String value) { + Settings.Secure.putStringForUser(mContentResolver, setting, value, mCurrentUser); + } + + @Override + public int getValue(String setting, int def) { + return Settings.Secure.getIntForUser(mContentResolver, setting, def, mCurrentUser); + } + + @Override + public String getValue(String setting, String def) { + String ret = Secure.getStringForUser(mContentResolver, setting, mCurrentUser); + if (ret == null) return def; + return ret; + } + + @Override + public void setValue(String setting, int value) { + Settings.Secure.putIntForUser(mContentResolver, setting, value, mCurrentUser); + } + + @Override + public void addTunable(Tunable tunable, String... keys) { + for (String key : keys) { + addTunable(tunable, key); + } + } + + private void addTunable(Tunable tunable, String key) { + if (!mTunableLookup.containsKey(key)) { + mTunableLookup.put(key, new ArraySet<Tunable>()); + } + mTunableLookup.get(key).add(tunable); + if (LeakDetector.ENABLED) { + mTunables.add(tunable); + Dependency.get(LeakDetector.class).trackCollection(mTunables, "TunerService.mTunables"); + } + Uri uri = Settings.Secure.getUriFor(key); + if (!mListeningUris.containsKey(uri)) { + mListeningUris.put(uri, key); + mContentResolver.registerContentObserver(uri, false, mObserver, mCurrentUser); + } + // Send the first state. + String value = Settings.Secure.getStringForUser(mContentResolver, key, mCurrentUser); + tunable.onTuningChanged(key, value); + } + + @Override + public void removeTunable(Tunable tunable) { + for (Set<Tunable> list : mTunableLookup.values()) { + list.remove(tunable); + } + if (LeakDetector.ENABLED) { + mTunables.remove(tunable); + } + } + + protected void reregisterAll() { + if (mListeningUris.size() == 0) { + return; + } + mContentResolver.unregisterContentObserver(mObserver); + for (Uri uri : mListeningUris.keySet()) { + mContentResolver.registerContentObserver(uri, false, mObserver, mCurrentUser); + } + } + + private void reloadSetting(Uri uri) { + String key = mListeningUris.get(uri); + Set<Tunable> tunables = mTunableLookup.get(key); + if (tunables == null) { + return; + } + String value = Settings.Secure.getStringForUser(mContentResolver, key, mCurrentUser); + for (Tunable tunable : tunables) { + tunable.onTuningChanged(key, value); + } + } + + private void reloadAll() { + for (String key : mTunableLookup.keySet()) { + String value = Settings.Secure.getStringForUser(mContentResolver, key, + mCurrentUser); + for (Tunable tunable : mTunableLookup.get(key)) { + tunable.onTuningChanged(key, value); + } + } + } + + @Override + public void clearAll() { + // A couple special cases. + Settings.Global.putString(mContentResolver, DemoMode.DEMO_MODE_ALLOWED, null); + Intent intent = new Intent(DemoMode.ACTION_DEMO); + intent.putExtra(DemoMode.EXTRA_COMMAND, DemoMode.COMMAND_EXIT); + mContext.sendBroadcast(intent); + + for (String key : mTunableLookup.keySet()) { + Settings.Secure.putString(mContentResolver, key, null); + } + } + + private class Observer extends ContentObserver { + public Observer() { + super(new Handler(Looper.getMainLooper())); + } + + @Override + public void onChange(boolean selfChange, Uri uri, int userId) { + if (userId == ActivityManager.getCurrentUser()) { + reloadSetting(uri); + } + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/util/NotificationChannels.java b/packages/SystemUI/src/com/android/systemui/util/NotificationChannels.java index 5df3beb471b5..cd85a760159b 100644 --- a/packages/SystemUI/src/com/android/systemui/util/NotificationChannels.java +++ b/packages/SystemUI/src/com/android/systemui/util/NotificationChannels.java @@ -38,19 +38,19 @@ public class NotificationChannels extends SystemUI { nm.createNotificationChannels(Arrays.asList( new NotificationChannel( ALERTS, - R.string.notification_channel_alerts, + context.getString(R.string.notification_channel_alerts), NotificationManager.IMPORTANCE_HIGH), new NotificationChannel( SCREENSHOTS, - R.string.notification_channel_screenshot, + context.getString(R.string.notification_channel_screenshot), NotificationManager.IMPORTANCE_LOW), new NotificationChannel( GENERAL, - R.string.notification_channel_general, + context.getString(R.string.notification_channel_general), NotificationManager.IMPORTANCE_MIN), new NotificationChannel( STORAGE, - R.string.notification_channel_storage, + context.getString(R.string.notification_channel_storage), isTv(context) ? NotificationManager.IMPORTANCE_DEFAULT : NotificationManager.IMPORTANCE_LOW) diff --git a/packages/SystemUI/tests/Android.mk b/packages/SystemUI/tests/Android.mk index 760d875143c0..ddd8d7b3002f 100644 --- a/packages/SystemUI/tests/Android.mk +++ b/packages/SystemUI/tests/Android.mk @@ -26,6 +26,7 @@ LOCAL_PROTOC_FLAGS := -I$(LOCAL_PATH)/.. LOCAL_PROTO_JAVA_OUTPUT_PARAMS := optional_field_style=accessors LOCAL_PACKAGE_NAME := SystemUITests +LOCAL_COMPATIBILITY_SUITE := device-tests LOCAL_SRC_FILES := $(call all-java-files-under, src) \ $(call all-Iaidl-files-under, src) \ @@ -46,10 +47,11 @@ LOCAL_STATIC_ANDROID_LIBRARIES := \ LOCAL_STATIC_JAVA_LIBRARIES := \ metrics-helper-lib \ android-support-test \ - mockito-updated-target-minus-junit4 \ + mockito-target-minus-junit4 \ SystemUI-proto \ SystemUI-tags \ - legacy-android-test + legacy-android-test \ + testables LOCAL_JAVA_LIBRARIES := android.test.runner telephony-common android.car diff --git a/packages/SystemUI/tests/src/com/android/systemui/DependencyTest.java b/packages/SystemUI/tests/src/com/android/systemui/DependencyTest.java index 27955ecbca72..c297ae840bb9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/DependencyTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/DependencyTest.java @@ -39,21 +39,21 @@ public class DependencyTest extends SysuiTestCase { @Test public void testClassDependency() { FlashlightController f = mock(FlashlightController.class); - injectTestDependency(FlashlightController.class, f); + mDependency.injectTestDependency(FlashlightController.class, f); Assert.assertEquals(f, Dependency.get(FlashlightController.class)); } @Test public void testStringDependency() { Looper l = Looper.getMainLooper(); - injectTestDependency(Dependency.BG_LOOPER, l); + mDependency.injectTestDependency(Dependency.BG_LOOPER, l); assertEquals(l, Dependency.get(Dependency.BG_LOOPER)); } @Test public void testDump() { Dumpable d = mock(Dumpable.class); - injectTestDependency(DUMPABLE, d); + mDependency.injectTestDependency(DUMPABLE, d); Dependency.get(DUMPABLE); mDependency.dump(null, mock(PrintWriter.class), null); verify(d).dump(eq(null), any(), eq(null)); @@ -62,7 +62,7 @@ public class DependencyTest extends SysuiTestCase { @Test public void testConfigurationChanged() { ConfigurationChangedReceiver d = mock(ConfigurationChangedReceiver.class); - injectTestDependency(CONFIGURATION_CHANGED_RECEIVER, d); + mDependency.injectTestDependency(CONFIGURATION_CHANGED_RECEIVER, d); Dependency.get(CONFIGURATION_CHANGED_RECEIVER); mDependency.onConfigurationChanged(null); verify(d).onConfigurationChanged(eq(null)); diff --git a/packages/SystemUI/tests/src/com/android/systemui/SysuiBaseFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/SysuiBaseFragmentTest.java new file mode 100644 index 000000000000..15cebc70e8bc --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/SysuiBaseFragmentTest.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package com.android.systemui; + +import android.app.Fragment; +import android.support.test.InstrumentationRegistry; +import android.testing.BaseFragmentTest; + +import com.android.systemui.utils.leaks.LeakCheckedTest; +import com.android.systemui.utils.leaks.LeakCheckedTest.SysuiLeakCheck; + +import org.junit.Before; +import org.junit.Rule; + +public abstract class SysuiBaseFragmentTest extends BaseFragmentTest { + + public static final Class<?>[] ALL_SUPPORTED_CLASSES = LeakCheckedTest.ALL_SUPPORTED_CLASSES; + + @Rule + public final SysuiLeakCheck mLeakCheck = new SysuiLeakCheck(); + + protected final TestableDependency mDependency = new TestableDependency(mContext); + protected SysuiTestableContext mSysuiContext; + + public SysuiBaseFragmentTest(Class<? extends Fragment> cls) { + super(cls); + } + + @Before + public void SysuiSetup() { + System.setProperty("dexmaker.share_classloader", "true"); + SystemUIFactory.createFromConfig(mContext); + // TODO: Figure out another way to give reference to a SysuiTestableContext. + mSysuiContext = (SysuiTestableContext) mContext; + } + + @Override + protected SysuiTestableContext getContext() { + return new SysuiTestableContext(InstrumentationRegistry.getContext(), mLeakCheck); + } + + public void injectLeakCheckedDependencies(Class<?>... cls) { + for (Class<?> c : cls) { + injectLeakCheckedDependency(c); + } + } + + public <T> void injectLeakCheckedDependency(Class<T> c) { + mDependency.injectTestDependency(c, mLeakCheck.getLeakChecker(c)); + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java b/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java index c0e7e8061407..aadae0f22c17 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java +++ b/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java @@ -15,49 +15,38 @@ */ package com.android.systemui; -import static org.mockito.Mockito.mock; - import android.content.Context; import android.os.Handler; import android.os.Looper; import android.os.MessageQueue; import android.support.test.InstrumentationRegistry; -import android.util.ArrayMap; - -import com.android.systemui.Dependency.DependencyKey; -import com.android.systemui.utils.TestableContext; -import com.android.systemui.utils.leaks.Tracker; +import android.testing.LeakCheck; -import org.junit.After; import org.junit.Before; +import org.junit.Rule; /** * Base class that does System UI specific setup. */ public abstract class SysuiTestCase { - private Throwable mException; private Handler mHandler; - protected TestableContext mContext; - protected TestDependency mDependency; + @Rule + public SysuiTestableContext mContext = new SysuiTestableContext( + InstrumentationRegistry.getContext(), getLeakCheck()); + public TestableDependency mDependency = new TestableDependency(mContext); @Before public void SysuiSetup() throws Exception { - mException = null; System.setProperty("dexmaker.share_classloader", "true"); - mContext = new TestableContext(InstrumentationRegistry.getTargetContext(), this); SystemUIFactory.createFromConfig(mContext); - mDependency = new TestDependency(); - mDependency.mContext = mContext; - mDependency.start(); } - @After - public void cleanup() throws Exception { - mContext.getSettingsProvider().clearOverrides(this); + protected LeakCheck getLeakCheck() { + return null; } - protected Context getContext() { + public Context getContext() { return mContext; } @@ -84,48 +73,11 @@ public abstract class SysuiTestCase { } } - // Used for leak tracking, returns null to indicate no leak tracking by default. - public Tracker getTracker(String tag) { - return null; - } - - public <T> T injectMockDependency(Class<T> cls) { - final T mock = mock(cls); - mDependency.injectTestDependency(cls, mock); - return mock; - } - - public <T> void injectTestDependency(Class<T> cls, T obj) { - mDependency.injectTestDependency(cls, obj); - } - - public <T> void injectTestDependency(DependencyKey<T> key, T obj) { - mDependency.injectTestDependency(key, obj); - } - public static final class EmptyRunnable implements Runnable { public void run() { } } - public static class TestDependency extends Dependency { - private final ArrayMap<Object, Object> mObjs = new ArrayMap<>(); - - private <T> void injectTestDependency(DependencyKey<T> key, T obj) { - mObjs.put(key, obj); - } - - private <T> void injectTestDependency(Class<T> key, T obj) { - mObjs.put(key, obj); - } - - @Override - protected <T> T createDependency(Object key) { - if (mObjs.containsKey(key)) return (T) mObjs.get(key); - return super.createDependency(key); - } - } - public static final class Idler implements MessageQueue.IdleHandler { private final Runnable mCallback; private boolean mIdle; diff --git a/packages/SystemUI/tests/src/com/android/systemui/SysuiTestableContext.java b/packages/SystemUI/tests/src/com/android/systemui/SysuiTestableContext.java new file mode 100644 index 000000000000..b94a2fbb8f81 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/SysuiTestableContext.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package com.android.systemui; + +import android.content.Context; +import android.testing.LeakCheck; +import android.testing.TestableContext; +import android.util.ArrayMap; + +public class SysuiTestableContext extends TestableContext implements SysUiServiceProvider { + + private ArrayMap<Class<?>, Object> mComponents; + + public SysuiTestableContext(Context base) { + super(base); + } + + public SysuiTestableContext(Context base, LeakCheck check) { + super(base, check); + } + + @SuppressWarnings("unchecked") + public <T> T getComponent(Class<T> interfaceType) { + return (T) (mComponents != null ? mComponents.get(interfaceType) : null); + } + + public <T, C extends T> void putComponent(Class<T> interfaceType, C component) { + if (mComponents == null) mComponents = new ArrayMap<>(); + mComponents.put(interfaceType, component); + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/TestableDependency.java b/packages/SystemUI/tests/src/com/android/systemui/TestableDependency.java new file mode 100644 index 000000000000..53a799497fd8 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/TestableDependency.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package com.android.systemui; + +import static org.mockito.Mockito.mock; + +import android.content.Context; +import android.util.ArrayMap; + +public class TestableDependency extends Dependency { + private final ArrayMap<Object, Object> mObjs = new ArrayMap<>(); + + public TestableDependency(Context context) { + mContext = context; + if (SystemUIFactory.getInstance() == null) { + SystemUIFactory.createFromConfig(context); + } + start(); + } + + public <T> T injectMockDependency(Class<T> cls) { + final T mock = mock(cls); + injectTestDependency(cls, mock); + return mock; + } + + public <T> void injectTestDependency(DependencyKey<T> key, T obj) { + mObjs.put(key, obj); + } + + public <T> void injectTestDependency(Class<T> key, T obj) { + mObjs.put(key, obj); + } + + @Override + protected <T> T createDependency(Object key) { + if (mObjs.containsKey(key)) return (T) mObjs.get(key); + return super.createDependency(key); + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeConfigurationTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeConfigurationTest.java index 5477afa86ffe..048936b3f5f0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeConfigurationTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeConfigurationTest.java @@ -47,7 +47,7 @@ public class DozeConfigurationTest extends SysuiTestCase { return; } - mContext.getSettingsProvider().acquireOverridesBuilder(this) + mContext.getSettingsProvider().acquireOverridesBuilder() .addSetting("secure", Settings.Secure.DOZE_ALWAYS_ON, null) .build(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java index ba396718fc78..cdbde5e8b0e1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java @@ -39,19 +39,20 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.support.test.filters.SmallTest; +import android.testing.AndroidTestingRunner; import android.view.Display; -import com.android.systemui.SysUIRunner; -import com.android.systemui.UiThreadTest; import com.android.internal.hardware.AmbientDisplayConfiguration; import com.android.systemui.util.wakelock.WakeLockFake; +import android.testing.UiThreadTest; + import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @SmallTest -@RunWith(SysUIRunner.class) +@RunWith(AndroidTestingRunner.class) @UiThreadTest public class DozeMachineTest { diff --git a/packages/SystemUI/tests/src/com/android/systemui/notification/PropertyAnimatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/notification/PropertyAnimatorTest.java index 53053fa6e2b2..8484beda5fc7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/notification/PropertyAnimatorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/notification/PropertyAnimatorTest.java @@ -14,22 +14,25 @@ package com.android.systemui.notification; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; + import android.animation.AnimatorListenerAdapter; import android.animation.ValueAnimator; -import android.support.test.runner.AndroidJUnit4; import android.test.suitebuilder.annotation.SmallTest; - -import com.android.systemui.Interpolators; -import com.android.systemui.R; - +import android.testing.AndroidTestingRunner; +import android.testing.UiThreadTest; import android.util.FloatProperty; import android.util.Property; import android.view.View; import android.view.animation.Interpolator; -import com.android.systemui.SysUIRunner; +import com.android.systemui.Interpolators; +import com.android.systemui.R; import com.android.systemui.SysuiTestCase; -import com.android.systemui.UiThreadTest; import com.android.systemui.statusbar.notification.PropertyAnimator; import com.android.systemui.statusbar.stack.AnimationFilter; import com.android.systemui.statusbar.stack.AnimationProperties; @@ -39,18 +42,8 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; -import static org.mockito.Matchers.anyObject; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - @SmallTest -@RunWith(SysUIRunner.class) +@RunWith(AndroidTestingRunner.class) @UiThreadTest public class PropertyAnimatorTest extends SysuiTestCase { diff --git a/packages/SystemUI/tests/src/com/android/systemui/plugins/PluginInstanceManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/plugins/PluginInstanceManagerTest.java index 658966cdde17..4f0815d0f283 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/plugins/PluginInstanceManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/plugins/PluginInstanceManagerTest.java @@ -72,7 +72,7 @@ public class PluginInstanceManagerTest extends SysuiTestCase { private PackageManager mMockPm; private PluginListener mMockListener; private PluginInstanceManager mPluginInstanceManager; - private PluginManager mMockManager; + private PluginManagerImpl mMockManager; private VersionInfo mMockVersionInfo; @Before @@ -82,7 +82,7 @@ public class PluginInstanceManagerTest extends SysuiTestCase { mContextWrapper = new MyContextWrapper(getContext()); mMockPm = mock(PackageManager.class); mMockListener = mock(PluginListener.class); - mMockManager = mock(PluginManager.class); + mMockManager = mock(PluginManagerImpl.class); when(mMockManager.getClassLoader(any(), any())) .thenReturn(getClass().getClassLoader()); mMockVersionInfo = mock(VersionInfo.class); diff --git a/packages/SystemUI/tests/src/com/android/systemui/plugins/PluginManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/plugins/PluginManagerTest.java index 053e5cf2d4c0..a3d5d5fc3871 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/plugins/PluginManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/plugins/PluginManagerTest.java @@ -34,7 +34,7 @@ import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; import com.android.systemui.SysuiTestCase; import com.android.systemui.plugins.annotations.ProvidesInterface; import com.android.systemui.plugins.PluginInstanceManager.PluginInfo; -import com.android.systemui.plugins.PluginManager.PluginInstanceManagerFactory; +import com.android.systemui.plugins.PluginManagerImpl.PluginInstanceManagerFactory; import org.junit.Before; import org.junit.Test; @@ -50,7 +50,7 @@ public class PluginManagerTest extends SysuiTestCase { private PluginInstanceManagerFactory mMockFactory; private PluginInstanceManager mMockPluginInstance; - private PluginManager mPluginManager; + private PluginManagerImpl mPluginManager; private PluginListener mMockListener; private UncaughtExceptionHandler mRealExceptionHandler; @@ -66,7 +66,8 @@ public class PluginManagerTest extends SysuiTestCase { when(mMockFactory.createPluginInstanceManager(Mockito.any(), Mockito.any(), Mockito.any(), Mockito.anyBoolean(), Mockito.any(), Mockito.any(), Mockito.any())) .thenReturn(mMockPluginInstance); - mPluginManager = new PluginManager(getContext(), mMockFactory, true, mMockExceptionHandler); + mPluginManager = new PluginManagerImpl(getContext(), mMockFactory, true, + mMockExceptionHandler); resetExceptionHandler(); mMockListener = mock(PluginListener.class); } @@ -98,7 +99,7 @@ public class PluginManagerTest extends SysuiTestCase { @Test public void testNonDebuggable() { - mPluginManager = new PluginManager(getContext(), mMockFactory, false, + mPluginManager = new PluginManagerImpl(getContext(), mMockFactory, false, mMockExceptionHandler); resetExceptionHandler(); @@ -148,7 +149,7 @@ public class PluginManagerTest extends SysuiTestCase { ComponentName testComponent = new ComponentName(getContext().getPackageName(), PluginManagerTest.class.getName()); - Intent intent = new Intent(PluginManager.DISABLE_PLUGIN); + Intent intent = new Intent(PluginManagerImpl.DISABLE_PLUGIN); intent.setData(Uri.parse("package://" + testComponent.flattenToString())); mPluginManager.onReceive(mContext, intent); verify(nm).cancel(eq(testComponent.getClassName()), eq(SystemMessage.NOTE_PLUGIN)); diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java index 7153340920d1..deb31dae6840 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java @@ -21,14 +21,16 @@ import android.os.Looper; import com.android.keyguard.CarrierText; import com.android.systemui.Dependency; -import com.android.systemui.FragmentTestCase; import com.android.systemui.R; -import com.android.systemui.SysUIRunner; + +import android.testing.AndroidTestingRunner; + +import com.android.systemui.SysuiBaseFragmentTest; import com.android.systemui.statusbar.phone.StatusBarIconController; import com.android.systemui.statusbar.policy.UserSwitcherController; -import com.android.systemui.util.LayoutInflaterBuilder; -import com.android.systemui.utils.TestableLooper; -import com.android.systemui.utils.TestableLooper.RunWithLooper; +import android.testing.LayoutInflaterBuilder; +import android.testing.TestableLooper; +import android.testing.TestableLooper.RunWithLooper; import org.junit.Before; import org.junit.Test; @@ -38,9 +40,9 @@ import android.content.Context; import android.view.View; import android.widget.FrameLayout; -@RunWith(SysUIRunner.class) +@RunWith(AndroidTestingRunner.class) @RunWithLooper(setAsMainLooper = true) -public class QSFragmentTest extends FragmentTestCase { +public class QSFragmentTest extends SysuiBaseFragmentTest { public QSFragmentTest() { super(QSFragment.class); @@ -56,8 +58,9 @@ public class QSFragmentTest extends FragmentTestCase { .replace(CarrierText.class, View.class) .build()); - injectTestDependency(Dependency.BG_LOOPER, TestableLooper.get(this).getLooper()); - injectMockDependency(UserSwitcherController.class); + mDependency.injectTestDependency(Dependency.BG_LOOPER, + TestableLooper.get(this).getLooper()); + mDependency.injectMockDependency(UserSwitcherController.class); injectLeakCheckedDependencies(ALL_SUPPORTED_CLASSES); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java index e38c30f5b020..1ff373c95c14 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java @@ -33,8 +33,8 @@ import com.android.systemui.Dependency; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.statusbar.policy.SecurityController; -import com.android.systemui.util.LayoutInflaterBuilder; -import com.android.systemui.utils.TestableImageView; +import android.testing.LayoutInflaterBuilder; +import android.testing.TestableImageView; import org.junit.Before; import org.junit.Test; @@ -57,8 +57,8 @@ public class QSSecurityFooterTest extends SysuiTestCase { @Before public void setUp() { - injectTestDependency(SecurityController.class, mSecurityController); - injectTestDependency(Dependency.BG_LOOPER, Looper.getMainLooper()); + mDependency.injectTestDependency(SecurityController.class, mSecurityController); + mDependency.injectTestDependency(Dependency.BG_LOOPER, Looper.getMainLooper()); mContext.addMockSystemService(Context.LAYOUT_INFLATER_SERVICE, new LayoutInflaterBuilder(mContext) .replace("ImageView", TestableImageView.class) diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java index d1e17f4b5499..5ae107a6a431 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java @@ -21,13 +21,13 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import com.android.systemui.Dependency; -import com.android.systemui.SysUIRunner; +import android.testing.AndroidTestingRunner; import com.android.systemui.SysuiTestCase; import com.android.systemui.plugins.qs.QSTile; import com.android.systemui.plugins.qs.QSTile.State; import com.android.systemui.qs.QSTileHost; -import com.android.systemui.utils.TestableLooper; -import com.android.systemui.utils.TestableLooper.RunWithLooper; +import android.testing.TestableLooper; +import android.testing.TestableLooper.RunWithLooper; import org.junit.Before; import org.junit.Test; @@ -37,7 +37,7 @@ import android.os.Message; import android.test.suitebuilder.annotation.SmallTest; @SmallTest -@RunWith(SysUIRunner.class) +@RunWith(AndroidTestingRunner.class) @RunWithLooper public class TileQueryHelperTest extends SysuiTestCase { private TestableLooper mBGLooper; @@ -46,7 +46,7 @@ public class TileQueryHelperTest extends SysuiTestCase { @Before public void setup() { mBGLooper = TestableLooper.get(this); - injectTestDependency(Dependency.BG_LOOPER, mBGLooper.getLooper()); + mDependency.injectTestDependency(Dependency.BG_LOOPER, mBGLooper.getLooper()); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java index 6d7b50fc1df9..ddd661558be5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java @@ -25,12 +25,12 @@ import android.os.Looper; import android.service.quicksettings.Tile; import android.test.suitebuilder.annotation.SmallTest; -import com.android.systemui.SysUIRunner; +import android.testing.AndroidTestingRunner; import com.android.systemui.SysuiTestCase; import com.android.systemui.qs.QSTileHost; import com.android.systemui.statusbar.phone.StatusBarIconController; -import com.android.systemui.utils.TestableLooper; -import com.android.systemui.utils.TestableLooper.RunWithLooper; +import android.testing.TestableLooper; +import android.testing.TestableLooper.RunWithLooper; import org.junit.After; import org.junit.Before; @@ -42,7 +42,7 @@ import org.mockito.Mockito; import java.util.ArrayList; @SmallTest -@RunWith(SysUIRunner.class) +@RunWith(AndroidTestingRunner.class) @RunWithLooper(setAsMainLooper = true) public class TileServicesTest extends SysuiTestCase { private static int NUM_FAKES = TileServices.DEFAULT_MAX_BOUND * 2; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java index 425096242287..bdd05c7e6384 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java @@ -16,8 +16,6 @@ package com.android.systemui.statusbar; -import static android.support.test.internal.runner.junit4.statement.UiThreadStatement.runOnUiThread; - import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.mock; @@ -34,19 +32,17 @@ import android.hardware.fingerprint.FingerprintManager; import android.os.Looper; import android.support.test.InstrumentationRegistry; import android.support.test.filters.SmallTest; -import android.support.test.internal.runner.junit4.statement.UiThreadStatement; import android.support.test.runner.AndroidJUnit4; import android.view.View; import android.view.ViewGroup; -import com.android.keyguard.KeyguardUpdateMonitor; +import com.android.keyguard.KeyguardUpdateMonitorCallback; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.statusbar.phone.KeyguardIndicationTextView; import com.android.systemui.util.wakelock.WakeLockFake; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; @@ -125,7 +121,7 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { when(mDevicePolicyManager.isDeviceManaged()).thenReturn(false); createController(); - final KeyguardUpdateMonitor monitor = KeyguardUpdateMonitor.getInstance(mContext); + final KeyguardUpdateMonitorCallback monitor = mController.getKeyguardCallback(); reset(mDisclosure); when(mDevicePolicyManager.isDeviceManaged()).thenReturn(true); @@ -176,7 +172,6 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { assertFalse(mWakeLock.isHeld()); } - @Ignore("Flaky") @Test public void transientIndication_releasesWakeLock_afterHidingDelayed() throws Throwable { mInstrumentation.runOnMainSync(() -> { @@ -188,12 +183,10 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { }); mInstrumentation.waitForIdleSync(); - boolean[] held = new boolean[2]; + Boolean[] held = new Boolean[1]; mInstrumentation.runOnMainSync(() -> { held[0] = mWakeLock.isHeld(); - held[1] = true; }); - assertFalse("wake lock still held", held[0]); - assertTrue("held was not written yet", held[1]); + assertFalse("WakeLock expected: RELEASED, was: HELD", held[0]); } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInfoTest.java index 8520bdb114aa..726300f79fc1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInfoTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInfoTest.java @@ -33,41 +33,35 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.app.INotificationManager; -import android.app.Notification; import android.app.NotificationChannel; import android.app.NotificationChannelGroup; import android.app.NotificationManager; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; -import android.content.pm.ParceledListSlice; import android.graphics.drawable.Drawable; import android.service.notification.StatusBarNotification; -import android.support.test.InstrumentationRegistry; -import android.support.test.runner.AndroidJUnit4; import android.test.suitebuilder.annotation.SmallTest; -import android.util.Log; +import android.testing.AndroidTestingRunner; +import android.testing.UiThreadTest; import android.view.LayoutInflater; import android.view.View; import android.widget.ImageView; import android.widget.Switch; import android.widget.TextView; -import com.android.internal.util.CharSequences; + import com.android.systemui.R; -import com.android.systemui.SysUIRunner; import com.android.systemui.SysuiTestCase; -import com.android.systemui.UiThreadTest; import org.junit.Before; -import org.junit.runner.RunWith; import org.junit.Test; -import org.mockito.Mockito; -import java.util.Arrays; +import org.junit.runner.RunWith; + import java.util.Collections; import java.util.concurrent.CountDownLatch; @SmallTest -@RunWith(SysUIRunner.class) +@RunWith(AndroidTestingRunner.class) @UiThreadTest public class NotificationInfoTest extends SysuiTestCase { private static final String TEST_PACKAGE_NAME = "test_package"; @@ -167,27 +161,6 @@ public class NotificationInfoTest extends SysuiTestCase { } @Test - public void testBindNotification_SetsGroupName_resId() throws Exception { - when(mMockPackageManager.getText(eq(TEST_PACKAGE_NAME), - eq(R.string.legacy_vpn_name), anyObject())).thenReturn( - getContext().getString(R.string.legacy_vpn_name)); - mNotificationChannel.setGroup("test_group_id"); - final NotificationChannelGroup notificationChannelGroup = - new NotificationChannelGroup("test_group_id", R.string.legacy_vpn_name); - when(mMockINotificationManager.getNotificationChannelGroupForPackage( - eq("test_group_id"), eq(TEST_PACKAGE_NAME), anyInt())) - .thenReturn(notificationChannelGroup); - mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, - mMockStatusBarNotification, mNotificationChannel, null, null, null); - final TextView groupNameView = (TextView) mNotificationInfo.findViewById(R.id.group_name); - assertEquals(View.VISIBLE, groupNameView.getVisibility()); - assertEquals(mContext.getString(R.string.legacy_vpn_name), groupNameView.getText()); - final TextView groupDividerView = - (TextView) mNotificationInfo.findViewById(R.id.pkg_group_divider); - assertEquals(View.VISIBLE, groupDividerView.getVisibility()); - } - - @Test public void testBindNotification_SetsTextChannelName() throws Exception { mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, mMockStatusBarNotification, mNotificationChannel, null, null, null); @@ -196,21 +169,6 @@ public class NotificationInfoTest extends SysuiTestCase { } @Test - public void testBindNotification_SetsTextChannelName_resId() throws Exception { - when(mMockPackageManager.getText(eq(TEST_PACKAGE_NAME), - eq(R.string.notification_menu_accessibility), anyObject())).thenReturn( - getContext().getString(R.string.notification_menu_accessibility)); - NotificationChannel notificationChannelResId = new NotificationChannel( - TEST_CHANNEL, R.string.notification_menu_accessibility, - NotificationManager.IMPORTANCE_LOW); - mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, - mMockStatusBarNotification, notificationChannelResId, null, null, null); - final TextView textView = mNotificationInfo.findViewById(R.id.channel_name); - assertEquals(getContext().getString(R.string.notification_menu_accessibility), - textView.getText()); - } - - @Test public void testBindNotification_SetsOnClickListenerForSettings() throws Exception { final CountDownLatch latch = new CountDownLatch(1); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationMenuRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationMenuRowTest.java index b8be4fa57c0f..c2c633639e81 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationMenuRowTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationMenuRowTest.java @@ -14,17 +14,17 @@ package com.android.systemui.statusbar; -import com.android.systemui.SysUIRunner; -import com.android.systemui.utils.TestableLooper; -import com.android.systemui.utils.TestableLooper.RunWithLooper; -import com.android.systemui.utils.ViewUtils; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; +import android.testing.TestableLooper.RunWithLooper; +import android.testing.ViewUtils; import com.android.systemui.utils.leaks.LeakCheckedTest; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -@RunWith(SysUIRunner.class) +@RunWith(AndroidTestingRunner.class) @RunWithLooper(setAsMainLooper = true) public class NotificationMenuRowTest extends LeakCheckedTest { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java index 3ccb1606876a..e41a827098c6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java @@ -22,7 +22,7 @@ import static org.mockito.Mockito.verify; import com.android.internal.app.NightDisplayController; import com.android.systemui.Prefs; import com.android.systemui.Prefs.Key; -import com.android.systemui.SysUIRunner; +import android.testing.AndroidTestingRunner; import com.android.systemui.SysuiTestCase; import com.android.systemui.qs.QSTileHost; @@ -32,7 +32,7 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mockito; -@RunWith(SysUIRunner.class) +@RunWith(AndroidTestingRunner.class) public class AutoTileManagerTest extends SysuiTestCase { private QSTileHost mQsTileHost; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragmentTest.java index f55115e005b2..a9acda305663 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragmentTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragmentTest.java @@ -23,24 +23,22 @@ import android.app.StatusBarManager; import android.view.View; import android.view.ViewPropertyAnimator; -import com.android.systemui.FragmentTestCase; import com.android.systemui.R; -import com.android.systemui.SysUIRunner; +import android.testing.AndroidTestingRunner; + +import com.android.systemui.SysuiBaseFragmentTest; import com.android.systemui.statusbar.CommandQueue; -import com.android.systemui.statusbar.policy.KeyguardMonitor; -import com.android.systemui.statusbar.policy.NetworkController; -import com.android.systemui.statusbar.policy.SecurityController; import com.android.systemui.tuner.TunerService; -import com.android.systemui.utils.TestableLooper.RunWithLooper; +import android.testing.TestableLooper.RunWithLooper; import org.junit.Before; import org.junit.runner.RunWith; import org.junit.Test; import org.mockito.Mockito; -@RunWith(SysUIRunner.class) +@RunWith(AndroidTestingRunner.class) @RunWithLooper(setAsMainLooper = true) -public class CollapsedStatusBarFragmentTest extends FragmentTestCase { +public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { private NotificationIconAreaController mMockNotificiationAreaController; private View mNotificationAreaInner; @@ -51,9 +49,9 @@ public class CollapsedStatusBarFragmentTest extends FragmentTestCase { @Before public void setup() { - mContext.putComponent(CommandQueue.class, mock(CommandQueue.class)); - mContext.putComponent(StatusBar.class, mock(StatusBar.class)); - mContext.putComponent(TunerService.class, mock(TunerService.class)); + mSysuiContext.putComponent(CommandQueue.class, mock(CommandQueue.class)); + mSysuiContext.putComponent(StatusBar.class, mock(StatusBar.class)); + mSysuiContext.putComponent(TunerService.class, mock(TunerService.class)); injectLeakCheckedDependencies(ALL_SUPPORTED_CLASSES); mMockNotificiationAreaController = mock(NotificationIconAreaController.class); mNotificationAreaInner = mock(View.class); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarFragmentTest.java index 1fa98461d02d..e7cdcb541ee4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarFragmentTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarFragmentTest.java @@ -19,24 +19,25 @@ import static org.mockito.Mockito.when; import android.content.Context; import android.os.Looper; +import android.testing.AndroidTestingRunner; import android.view.Display; import android.view.WindowManager; import com.android.systemui.Dependency; -import com.android.systemui.FragmentTestCase; -import com.android.systemui.SysUIRunner; + +import com.android.systemui.SysuiBaseFragmentTest; import com.android.systemui.recents.Recents; import com.android.systemui.stackdivider.Divider; import com.android.systemui.statusbar.CommandQueue; -import com.android.systemui.utils.TestableLooper.RunWithLooper; +import android.testing.TestableLooper.RunWithLooper; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -@RunWith(SysUIRunner.class) +@RunWith(AndroidTestingRunner.class) @RunWithLooper(setAsMainLooper = true) -public class NavigationBarFragmentTest extends FragmentTestCase { +public class NavigationBarFragmentTest extends SysuiBaseFragmentTest { public NavigationBarFragmentTest() { super(NavigationBarFragment.class); @@ -44,11 +45,11 @@ public class NavigationBarFragmentTest extends FragmentTestCase { @Before public void setup() { - injectTestDependency(Dependency.BG_LOOPER, Looper.getMainLooper()); - mContext.putComponent(CommandQueue.class, mock(CommandQueue.class)); - mContext.putComponent(StatusBar.class, mock(StatusBar.class)); - mContext.putComponent(Recents.class, mock(Recents.class)); - mContext.putComponent(Divider.class, mock(Divider.class)); + mDependency.injectTestDependency(Dependency.BG_LOOPER, Looper.getMainLooper()); + mSysuiContext.putComponent(CommandQueue.class, mock(CommandQueue.class)); + mSysuiContext.putComponent(StatusBar.class, mock(StatusBar.class)); + mSysuiContext.putComponent(Recents.class, mock(Recents.class)); + mSysuiContext.putComponent(Divider.class, mock(Divider.class)); injectLeakCheckedDependencies(ALL_SUPPORTED_CLASSES); WindowManager windowManager = mock(WindowManager.class); Display defaultDisplay = mContext.getSystemService(WindowManager.class).getDefaultDisplay(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ExtensionControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ExtensionControllerTest.java index e3a5ef04bcc8..3e79a0404026 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ExtensionControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ExtensionControllerTest.java @@ -42,8 +42,8 @@ public class ExtensionControllerTest extends SysuiTestCase { @Before public void setup() { - mPluginManager = injectMockDependency(PluginManager.class); - mTunerService = injectMockDependency(TunerService.class); + mPluginManager = mDependency.injectMockDependency(PluginManager.class); + mTunerService = mDependency.injectMockDependency(TunerService.class); mExtensionController = Dependency.get(ExtensionController.class); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java index 8cbf95b548eb..efa232bb33af 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java @@ -18,7 +18,7 @@ import android.test.suitebuilder.annotation.SmallTest; import com.android.settingslib.Utils; import com.android.systemui.statusbar.policy.NetworkController.IconState; -import com.android.systemui.utils.FakeSettingsProvider.SettingOverrider; +import android.testing.TestableSettings.SettingOverrider; import org.junit.Test; import org.junit.runner.RunWith; @@ -92,9 +92,9 @@ public class NetworkControllerWifiTest extends NetworkControllerBaseTest { attr); // Must set the Settings value before instantiating the NetworkControllerImpl due to bugs in - // FakeSettingsProvider. + // TestableSettings. SettingOverrider settingsOverrider = - mContext.getSettingsProvider().acquireOverridesBuilder(this) + mContext.getSettingsProvider().acquireOverridesBuilder() .addSetting("global", Settings.Global.NETWORK_SCORING_UI_ENABLED, "1") .build(); super.setUp(); // re-instantiate NetworkControllImpl now that setting has been updated diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/FakeSettingsProvider.java b/packages/SystemUI/tests/src/com/android/systemui/utils/FakeSettingsProvider.java deleted file mode 100644 index f40fe4cb2450..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/utils/FakeSettingsProvider.java +++ /dev/null @@ -1,298 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file - * except in compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the - * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ - -package com.android.systemui.utils; - -import android.content.ContentProviderClient; -import android.content.ContentResolver; -import android.os.Bundle; -import android.os.RemoteException; -import android.provider.Settings; -import android.support.annotation.VisibleForTesting; -import android.test.mock.MockContentProvider; -import android.util.ArrayMap; -import android.util.ArraySet; -import android.util.Log; - -import com.android.systemui.SysuiTestCase; -import com.android.systemui.utils.FakeSettingsProvider.SettingOverrider.Builder; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.Set; - -/** - * Allows calls to android.provider.Settings to be tested easier. A SettingOverride - * can be acquired and a set of specific settings can be set to a value (and not changed - * in the system when set), so that they can be tested without breaking the test device. - * <p> - * To use, in the before method acquire the override add all settings that will affect if - * your test passes or not. - * - * <pre class="prettyprint"> - * {@literal - * mSettingOverride = mTestableContext.getSettingsProvider().acquireOverridesBuilder() - * .addSetting("secure", Secure.USER_SETUP_COMPLETE, "0") - * .build(); - * } - * </pre> - * - * Then in the after free up the settings. - * - * <pre class="prettyprint"> - * {@literal - * mSettingOverride.release(); - * } - * </pre> - */ -public class FakeSettingsProvider extends MockContentProvider { - - private static final String TAG = "FakeSettingsProvider"; - private static final boolean DEBUG = false; - - // Number of times to try to acquire a setting if in use. - private static final int MAX_TRIES = 10; - // Time to wait for each setting. WAIT_TIMEOUT * MAX_TRIES will be the maximum wait time - // for a setting. - private static final long WAIT_TIMEOUT = 1000; - - private final Map<String, SettingOverrider> mOverrideMap = new ArrayMap<>(); - private final Map<SysuiTestCase, List<SettingOverrider>> mOwners = new ArrayMap<>(); - - private static FakeSettingsProvider sInstance; - private final ContentProviderClient mSettings; - private final ContentResolver mResolver; - - private FakeSettingsProvider(ContentProviderClient settings, ContentResolver resolver) { - mSettings = settings; - mResolver = resolver; - } - - public Builder acquireOverridesBuilder(SysuiTestCase test) { - return new Builder(this, test); - } - - public void clearOverrides(SysuiTestCase test) { - List<SettingOverrider> overrides = mOwners.remove(test); - if (overrides != null) { - overrides.forEach(override -> override.ensureReleased()); - } - } - - public Bundle call(String method, String arg, Bundle extras) { - // Methods are "GET_system", "GET_global", "PUT_secure", etc. - final String[] commands = method.split("_", 2); - final String op = commands[0]; - final String table = commands[1]; - - synchronized (mOverrideMap) { - SettingOverrider overrider = mOverrideMap.get(key(table, arg)); - if (overrider == null) { - // Fall through to real settings. - try { - if (DEBUG) Log.d(TAG, "Falling through to real settings " + method); - // TODO: Add our own version of caching to handle this. - Bundle call = mSettings.call(method, arg, extras); - call.remove(Settings.CALL_METHOD_TRACK_GENERATION_KEY); - return call; - } catch (RemoteException e) { - throw new RuntimeException(e); - } - } - String value; - Bundle out = new Bundle(); - switch (op) { - case "GET": - value = overrider.get(table, arg); - if (value != null) { - out.putString(Settings.NameValueTable.VALUE, value); - } - break; - case "PUT": - value = extras.getString(Settings.NameValueTable.VALUE, null); - if (value != null) { - overrider.put(table, arg, value); - } else { - overrider.remove(table, arg); - } - break; - default: - throw new UnsupportedOperationException("Unknown command " + method); - } - return out; - } - } - - private void acquireSettings(SettingOverrider overridder, Set<String> keys, - SysuiTestCase owner) throws AcquireTimeoutException { - synchronized (mOwners) { - List<SettingOverrider> list = mOwners.get(owner); - if (list == null) { - list = new ArrayList<>(); - mOwners.put(owner, list); - } - list.add(overridder); - } - synchronized (mOverrideMap) { - for (int i = 0; i < MAX_TRIES; i++) { - if (checkKeys(keys, false)) break; - try { - if (DEBUG) Log.d(TAG, "Waiting for contention to finish"); - mOverrideMap.wait(WAIT_TIMEOUT); - } catch (InterruptedException e) { - } - } - checkKeys(keys, true); - for (String key : keys) { - if (DEBUG) Log.d(TAG, "Acquiring " + key); - mOverrideMap.put(key, overridder); - } - } - } - - private void releaseSettings(Set<String> keys) { - synchronized (mOverrideMap) { - for (String key : keys) { - if (DEBUG) Log.d(TAG, "Releasing " + key); - mOverrideMap.remove(key); - } - if (DEBUG) Log.d(TAG, "Notifying"); - mOverrideMap.notify(); - } - } - - @VisibleForTesting - public Object getLock() { - return mOverrideMap; - } - - private boolean checkKeys(Set<String> keys, boolean shouldThrow) - throws AcquireTimeoutException { - for (String key : keys) { - if (mOverrideMap.containsKey(key)) { - if (shouldThrow) { - throw new AcquireTimeoutException("Could not acquire " + key); - } - return false; - } - } - return true; - } - - public static class SettingOverrider { - private final Set<String> mValidKeys; - private final Map<String, String> mValueMap = new ArrayMap<>(); - private final FakeSettingsProvider mProvider; - private boolean mReleased; - - private SettingOverrider(Set<String> keys, FakeSettingsProvider provider) { - mValidKeys = new ArraySet<>(keys); - mProvider = provider; - } - - private void ensureReleased() { - if (!mReleased) { - release(); - } - } - - public void release() { - mProvider.releaseSettings(mValidKeys); - mReleased = true; - } - - private void putDirect(String key, String value) { - mValueMap.put(key, value); - } - - public void put(String table, String key, String value) { - if (!mValidKeys.contains(key(table, key))) { - throw new IllegalArgumentException("Key " + table + " " + key - + " not acquired for this overrider"); - } - mValueMap.put(key(table, key), value); - } - - public void remove(String table, String key) { - if (!mValidKeys.contains(key(table, key))) { - throw new IllegalArgumentException("Key " + table + " " + key - + " not acquired for this overrider"); - } - mValueMap.remove(key(table, key)); - } - - public String get(String table, String key) { - if (!mValidKeys.contains(key(table, key))) { - throw new IllegalArgumentException("Key " + table + " " + key - + " not acquired for this overrider"); - } - Log.d(TAG, "Get " + table + " " + key + " " + mValueMap.get(key(table, key))); - return mValueMap.get(key(table, key)); - } - - public static class Builder { - private final FakeSettingsProvider mProvider; - private final SysuiTestCase mOwner; - private Set<String> mKeys = new ArraySet<>(); - private Map<String, String> mValues = new ArrayMap<>(); - - private Builder(FakeSettingsProvider provider, SysuiTestCase test) { - mProvider = provider; - mOwner = test; - } - - public Builder addSetting(String table, String key) { - mKeys.add(key(table, key)); - return this; - } - - public Builder addSetting(String table, String key, String value) { - addSetting(table, key); - mValues.put(key(table, key), value); - return this; - } - - public SettingOverrider build() throws AcquireTimeoutException { - SettingOverrider overrider = new SettingOverrider(mKeys, mProvider); - mProvider.acquireSettings(overrider, mKeys, mOwner); - mValues.forEach((key, value) -> overrider.putDirect(key, value)); - return overrider; - } - } - } - - public static class AcquireTimeoutException extends Exception { - public AcquireTimeoutException(String str) { - super(str); - } - } - - private static String key(String table, String key) { - return table + "_" + key; - } - - /** - * Since the settings provider is cached inside android.provider.Settings, this must - * be gotten statically to ensure there is only one instance referenced. - * @param settings - */ - public static FakeSettingsProvider getFakeSettingsProvider(ContentProviderClient settings, - ContentResolver resolver) { - if (sInstance == null) { - sInstance = new FakeSettingsProvider(settings, resolver); - } - return sInstance; - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/BaseLeakChecker.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/BaseLeakChecker.java index b118fdcb64ac..d94ecc09f263 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/BaseLeakChecker.java +++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/BaseLeakChecker.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016 The Android Open Source Project + * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of the License at @@ -14,6 +14,9 @@ package com.android.systemui.utils.leaks; +import android.testing.LeakCheck; +import android.testing.LeakCheck.Tracker; + import com.android.systemui.Dumpable; import com.android.systemui.statusbar.policy.CallbackController; @@ -24,7 +27,7 @@ public class BaseLeakChecker<T> implements CallbackController<T>, Dumpable { private final Tracker mTracker; - public BaseLeakChecker(LeakCheckedTest test, String tag) { + public BaseLeakChecker(LeakCheck test, String tag) { mTracker = test.getTracker(tag); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeBatteryController.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeBatteryController.java index fa07d337cd3d..a843cca498a0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeBatteryController.java +++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeBatteryController.java @@ -15,6 +15,7 @@ package com.android.systemui.utils.leaks; import android.os.Bundle; +import android.testing.LeakCheck; import com.android.systemui.statusbar.policy.BatteryController; import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback; @@ -24,7 +25,7 @@ import java.io.PrintWriter; public class FakeBatteryController extends BaseLeakChecker<BatteryStateChangeCallback> implements BatteryController { - public FakeBatteryController(LeakCheckedTest test) { + public FakeBatteryController(LeakCheck test) { super(test, "battery"); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeBluetoothController.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeBluetoothController.java index 6074a011fd20..0ba031908d90 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeBluetoothController.java +++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeBluetoothController.java @@ -14,6 +14,8 @@ package com.android.systemui.utils.leaks; +import android.testing.LeakCheck; + import com.android.settingslib.bluetooth.CachedBluetoothDevice; import com.android.systemui.statusbar.policy.BluetoothController; import com.android.systemui.statusbar.policy.BluetoothController.Callback; @@ -23,7 +25,7 @@ import java.util.Collection; public class FakeBluetoothController extends BaseLeakChecker<Callback> implements BluetoothController { - public FakeBluetoothController(LeakCheckedTest test) { + public FakeBluetoothController(LeakCheck test) { super(test, "bluetooth"); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeCastController.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeCastController.java index 08211f857cb8..51149abe792d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeCastController.java +++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeCastController.java @@ -14,13 +14,15 @@ package com.android.systemui.utils.leaks; +import android.testing.LeakCheck; + import com.android.systemui.statusbar.policy.CastController; import com.android.systemui.statusbar.policy.CastController.Callback; import java.util.Set; public class FakeCastController extends BaseLeakChecker<Callback> implements CastController { - public FakeCastController(LeakCheckedTest test) { + public FakeCastController(LeakCheck test) { super(test, "cast"); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeDataSaverController.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeDataSaverController.java index 857a78510c1e..886722e46376 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeDataSaverController.java +++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeDataSaverController.java @@ -14,12 +14,14 @@ package com.android.systemui.utils.leaks; +import android.testing.LeakCheck; + import com.android.systemui.statusbar.policy.DataSaverController; import com.android.systemui.statusbar.policy.DataSaverController.Listener; public class FakeDataSaverController extends BaseLeakChecker<Listener> implements DataSaverController { - public FakeDataSaverController(LeakCheckedTest test) { + public FakeDataSaverController(LeakCheck test) { super(test, "datasaver"); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeExtensionController.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeExtensionController.java index c0f578373fe4..b9d188a3871a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeExtensionController.java +++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeExtensionController.java @@ -14,7 +14,8 @@ package com.android.systemui.utils.leaks; -import static org.mockito.Mockito.mock; +import android.testing.LeakCheck; +import android.testing.LeakCheck.Tracker; import com.android.systemui.statusbar.policy.ExtensionController; @@ -25,7 +26,7 @@ public class FakeExtensionController implements ExtensionController { private final Tracker mTracker; - public FakeExtensionController(LeakCheckedTest test) { + public FakeExtensionController(LeakCheck test) { mTracker = test.getTracker("extension"); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeFlashlightController.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeFlashlightController.java index 630abd75af97..f6fd2cb8f3b1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeFlashlightController.java +++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeFlashlightController.java @@ -14,12 +14,14 @@ package com.android.systemui.utils.leaks; +import android.testing.LeakCheck; + import com.android.systemui.statusbar.policy.FlashlightController; import com.android.systemui.statusbar.policy.FlashlightController.FlashlightListener; public class FakeFlashlightController extends BaseLeakChecker<FlashlightListener> implements FlashlightController { - public FakeFlashlightController(LeakCheckedTest test) { + public FakeFlashlightController(LeakCheck test) { super(test, "flashlight"); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeHotspotController.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeHotspotController.java index 781960d82e59..69e236172fef 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeHotspotController.java +++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeHotspotController.java @@ -14,12 +14,14 @@ package com.android.systemui.utils.leaks; +import android.testing.LeakCheck; + import com.android.systemui.statusbar.policy.HotspotController; import com.android.systemui.statusbar.policy.HotspotController.Callback; public class FakeHotspotController extends BaseLeakChecker<Callback> implements HotspotController { - public FakeHotspotController(LeakCheckedTest test) { + public FakeHotspotController(LeakCheck test) { super(test, "hotspot"); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeKeyguardMonitor.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeKeyguardMonitor.java index 21871fc4cb75..51e35cc3915e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeKeyguardMonitor.java +++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeKeyguardMonitor.java @@ -14,13 +14,15 @@ package com.android.systemui.utils.leaks; +import android.testing.LeakCheck; + import com.android.systemui.statusbar.policy.KeyguardMonitor; public class FakeKeyguardMonitor implements KeyguardMonitor { private final BaseLeakChecker<Callback> mCallbackController; - public FakeKeyguardMonitor(LeakCheckedTest test) { + public FakeKeyguardMonitor(LeakCheck test) { mCallbackController = new BaseLeakChecker<Callback>(test, "keyguard"); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeLocationController.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeLocationController.java index eab436cd8750..29d7f1ca85bc 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeLocationController.java +++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeLocationController.java @@ -14,12 +14,14 @@ package com.android.systemui.utils.leaks; +import android.testing.LeakCheck; + import com.android.systemui.statusbar.policy.LocationController; import com.android.systemui.statusbar.policy.LocationController.LocationSettingsChangeCallback; public class FakeLocationController extends BaseLeakChecker<LocationSettingsChangeCallback> implements LocationController { - public FakeLocationController(LeakCheckedTest test) { + public FakeLocationController(LeakCheck test) { super(test, "location"); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeManagedProfileController.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeManagedProfileController.java index 0ec0d778a472..18b07cf25fbc 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeManagedProfileController.java +++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeManagedProfileController.java @@ -14,12 +14,14 @@ package com.android.systemui.utils.leaks; +import android.testing.LeakCheck; + import com.android.systemui.statusbar.phone.ManagedProfileController; import com.android.systemui.statusbar.phone.ManagedProfileController.Callback; public class FakeManagedProfileController extends BaseLeakChecker<Callback> implements ManagedProfileController { - public FakeManagedProfileController(LeakCheckedTest test) { + public FakeManagedProfileController(LeakCheck test) { super(test, "profile"); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeNetworkController.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeNetworkController.java index 47ed5ca05312..64fe8dd4a98f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeNetworkController.java +++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeNetworkController.java @@ -15,25 +15,23 @@ package com.android.systemui.utils.leaks; import android.os.Bundle; +import android.testing.LeakCheck; import com.android.settingslib.net.DataUsageController; import com.android.systemui.statusbar.policy.DataSaverController; import com.android.systemui.statusbar.policy.NetworkController; import com.android.systemui.statusbar.policy.NetworkController.SignalCallback; -import java.io.FileDescriptor; -import java.io.PrintWriter; - public class FakeNetworkController extends BaseLeakChecker<SignalCallback> implements NetworkController { private final FakeDataSaverController mDataSaverController; private final BaseLeakChecker<EmergencyListener> mEmergencyChecker; - public FakeNetworkController(LeakCheckedTest test) { + public FakeNetworkController(LeakCheck test) { super(test, "network"); mDataSaverController = new FakeDataSaverController(test); - mEmergencyChecker = new BaseLeakChecker<EmergencyListener>(test, "emergency"); + mEmergencyChecker = new BaseLeakChecker<>(test, "emergency"); } @Override diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeNextAlarmController.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeNextAlarmController.java index 707fc4b4880d..5ae8e22c06ee 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeNextAlarmController.java +++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeNextAlarmController.java @@ -14,13 +14,15 @@ package com.android.systemui.utils.leaks; +import android.testing.LeakCheck; + import com.android.systemui.statusbar.policy.NextAlarmController; import com.android.systemui.statusbar.policy.NextAlarmController.NextAlarmChangeCallback; public class FakeNextAlarmController extends BaseLeakChecker<NextAlarmChangeCallback> implements NextAlarmController { - public FakeNextAlarmController(LeakCheckedTest test) { + public FakeNextAlarmController(LeakCheck test) { super(test, "alarm"); } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakePluginManager.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakePluginManager.java index 59a93618cdb1..0a83a896dfaf 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakePluginManager.java +++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakePluginManager.java @@ -15,17 +15,17 @@ package com.android.systemui.utils.leaks; import android.content.Context; +import android.testing.LeakCheck; import com.android.systemui.plugins.Plugin; import com.android.systemui.plugins.PluginListener; import com.android.systemui.plugins.PluginManager; -public class FakePluginManager extends PluginManager { +public class FakePluginManager implements PluginManager { private final BaseLeakChecker<PluginListener> mLeakChecker; - public FakePluginManager(Context context, LeakCheckedTest test) { - super(context); + public FakePluginManager(LeakCheck test) { mLeakChecker = new BaseLeakChecker<>(test, "Plugin"); } @@ -36,11 +36,38 @@ public class FakePluginManager extends PluginManager { } @Override + public <T extends Plugin> void addPluginListener(PluginListener<T> listener, Class<?> cls) { + mLeakChecker.addCallback(listener); + } + + @Override + public <T extends Plugin> void addPluginListener(PluginListener<T> listener, Class<?> cls, + boolean allowMultiple) { + mLeakChecker.addCallback(listener); + } + + @Override + public <T extends Plugin> void addPluginListener(String action, PluginListener<T> listener, + Class<?> cls) { + mLeakChecker.addCallback(listener); + } + + @Override public void removePluginListener(PluginListener<?> listener) { mLeakChecker.removeCallback(listener); } @Override + public <T> boolean dependsOn(Plugin p, Class<T> cls) { + return false; + } + + @Override + public <T extends Plugin> T getOneShotPlugin(Class<T> cls) { + return null; + } + + @Override public <T extends Plugin> T getOneShotPlugin(String action, Class<?> cls) { return null; } diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeRotationLockController.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeRotationLockController.java index 00e2404e931f..d60fe78e9d6e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeRotationLockController.java +++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeRotationLockController.java @@ -14,12 +14,14 @@ package com.android.systemui.utils.leaks; +import android.testing.LeakCheck; + import com.android.systemui.statusbar.policy.RotationLockController; import com.android.systemui.statusbar.policy.RotationLockController.RotationLockControllerCallback; public class FakeRotationLockController extends BaseLeakChecker<RotationLockControllerCallback> implements RotationLockController { - public FakeRotationLockController(LeakCheckedTest test) { + public FakeRotationLockController(LeakCheck test) { super(test, "rotation"); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeSecurityController.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeSecurityController.java index 2d53c77cd163..157b8a04720b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeSecurityController.java +++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeSecurityController.java @@ -14,12 +14,14 @@ package com.android.systemui.utils.leaks; +import android.testing.LeakCheck; + import com.android.systemui.statusbar.policy.SecurityController; import com.android.systemui.statusbar.policy.SecurityController.SecurityControllerCallback; public class FakeSecurityController extends BaseLeakChecker<SecurityControllerCallback> implements SecurityController { - public FakeSecurityController(LeakCheckedTest test) { + public FakeSecurityController(LeakCheck test) { super(test, "security"); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java index b13535f75aee..6b501af95097 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java +++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java @@ -14,6 +14,8 @@ package com.android.systemui.utils.leaks; +import android.testing.LeakCheck; + import com.android.internal.statusbar.StatusBarIcon; import com.android.systemui.statusbar.phone.StatusBarIconController; import com.android.systemui.statusbar.phone.StatusBarIconController.IconManager; @@ -21,7 +23,7 @@ import com.android.systemui.statusbar.phone.StatusBarIconController.IconManager; public class FakeStatusBarIconController extends BaseLeakChecker<IconManager> implements StatusBarIconController { - public FakeStatusBarIconController(LeakCheckedTest test) { + public FakeStatusBarIconController(LeakCheck test) { super(test, "StatusBarGroup"); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeTunerService.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeTunerService.java index b841ce908ca2..8db82e2a52fc 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeTunerService.java +++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeTunerService.java @@ -15,6 +15,7 @@ package com.android.systemui.utils.leaks; import android.content.Context; +import android.testing.LeakCheck; import com.android.systemui.tuner.TunerService; @@ -22,10 +23,8 @@ public class FakeTunerService extends TunerService { private final BaseLeakChecker<Tunable> mBaseLeakChecker; - public FakeTunerService(Context context, LeakCheckedTest test) { - super(context); + public FakeTunerService(LeakCheck test) { mBaseLeakChecker = new BaseLeakChecker<>(test, "tunable"); - destroy(); } @Override @@ -40,4 +39,39 @@ public class FakeTunerService extends TunerService { public void removeTunable(Tunable tunable) { mBaseLeakChecker.removeCallback(tunable); } + + @Override + public void clearAll() { + + } + + @Override + public void destroy() { + + } + + @Override + public String getValue(String setting) { + return null; + } + + @Override + public int getValue(String setting, int def) { + return def; + } + + @Override + public String getValue(String setting, String def) { + return def; + } + + @Override + public void setValue(String setting, String value) { + + } + + @Override + public void setValue(String setting, int value) { + + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeUserInfoController.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeUserInfoController.java index 578b31089d75..f7ef653aebf2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeUserInfoController.java +++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeUserInfoController.java @@ -14,12 +14,14 @@ package com.android.systemui.utils.leaks; +import android.testing.LeakCheck; + import com.android.systemui.statusbar.policy.UserInfoController; import com.android.systemui.statusbar.policy.UserInfoController.OnUserInfoChangedListener; public class FakeUserInfoController extends BaseLeakChecker<OnUserInfoChangedListener> implements UserInfoController { - public FakeUserInfoController(LeakCheckedTest test) { + public FakeUserInfoController(LeakCheck test) { super(test, "user_info"); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeZenModeController.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeZenModeController.java index 7581363c78f0..fb9bf7a15f60 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeZenModeController.java +++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeZenModeController.java @@ -18,12 +18,13 @@ import android.content.ComponentName; import android.net.Uri; import android.service.notification.ZenModeConfig; import android.service.notification.ZenModeConfig.ZenRule; +import android.testing.LeakCheck; import com.android.systemui.statusbar.policy.ZenModeController; import com.android.systemui.statusbar.policy.ZenModeController.Callback; public class FakeZenModeController extends BaseLeakChecker<Callback> implements ZenModeController { - public FakeZenModeController(LeakCheckedTest test) { + public FakeZenModeController(LeakCheck test) { super(test, "zen"); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/LeakCheckedTest.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/LeakCheckedTest.java index 6c5152496692..94af77332f46 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/LeakCheckedTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/LeakCheckedTest.java @@ -15,8 +15,8 @@ package com.android.systemui.utils.leaks; import static org.mockito.Matchers.any; -import static org.mockito.Mockito.doAnswer; +import android.testing.LeakCheck; import android.util.ArrayMap; import com.android.systemui.SysuiTestCase; @@ -25,7 +25,6 @@ import com.android.systemui.statusbar.phone.ManagedProfileController; import com.android.systemui.statusbar.phone.StatusBarIconController; import com.android.systemui.statusbar.policy.BatteryController; import com.android.systemui.statusbar.policy.BluetoothController; -import com.android.systemui.statusbar.policy.CallbackController; import com.android.systemui.statusbar.policy.CastController; import com.android.systemui.statusbar.policy.FlashlightController; import com.android.systemui.statusbar.policy.HotspotController; @@ -41,12 +40,7 @@ import com.android.systemui.tuner.TunerService; import org.junit.Assert; import org.junit.Rule; -import org.junit.rules.TestWatcher; -import org.junit.runner.Description; -import org.mockito.invocation.InvocationOnMock; -import org.mockito.stubbing.Answer; -import java.util.HashMap; import java.util.Map; /** @@ -56,10 +50,7 @@ import java.util.Map; public abstract class LeakCheckedTest extends SysuiTestCase { private static final String TAG = "LeakCheckedTest"; - private final Map<String, Tracker> mTrackers = new HashMap<>(); - private final Map<Class, Object> mLeakCheckers = new ArrayMap<>(); - - public static final Class<?>[] ALL_SUPPORTED_CLASSES = new Class[] { + public static final Class<?>[] ALL_SUPPORTED_CLASSES = new Class[]{ BluetoothController.class, LocationController.class, RotationLockController.class, @@ -80,71 +71,11 @@ public abstract class LeakCheckedTest extends SysuiTestCase { }; @Rule - public TestWatcher successWatcher = new TestWatcher() { - @Override - protected void succeeded(Description description) { - verify(); - } - }; - - public <T> T getLeakChecker(Class<T> cls) { - Object obj = mLeakCheckers.get(cls); - if (obj == null) { - // Lazy create checkers so we only have the ones we need. - if (cls == BluetoothController.class) { - obj = new FakeBluetoothController(this); - } else if (cls == LocationController.class) { - obj = new FakeLocationController(this); - } else if (cls == RotationLockController.class) { - obj = new FakeRotationLockController(this); - } else if (cls == ZenModeController.class) { - obj = new FakeZenModeController(this); - } else if (cls == CastController.class) { - obj = new FakeCastController(this); - } else if (cls == HotspotController.class) { - obj = new FakeHotspotController(this); - } else if (cls == FlashlightController.class) { - obj = new FakeFlashlightController(this); - } else if (cls == UserInfoController.class) { - obj = new FakeUserInfoController(this); - } else if (cls == KeyguardMonitor.class) { - obj = new FakeKeyguardMonitor(this); - } else if (cls == BatteryController.class) { - obj = new FakeBatteryController(this); - } else if (cls == SecurityController.class) { - obj = new FakeSecurityController(this); - } else if (cls == ManagedProfileController.class) { - obj = new FakeManagedProfileController(this); - } else if (cls == NextAlarmController.class) { - obj = new FakeNextAlarmController(this); - } else if (cls == NetworkController.class) { - obj = new FakeNetworkController(this); - } else if (cls == PluginManager.class) { - obj = new FakePluginManager(mContext, this); - } else if (cls == TunerService.class) { - obj = new FakeTunerService(mContext, this); - } else if (cls == StatusBarIconController.class) { - obj = new FakeStatusBarIconController(this); - } else { - Assert.fail(cls.getName() + " is not supported by LeakCheckedTest yet"); - } - mLeakCheckers.put(cls, obj); - } - return (T) obj; - } + public SysuiLeakCheck mLeakCheck = new SysuiLeakCheck(); @Override - public Tracker getTracker(String tag) { - Tracker t = mTrackers.get(tag); - if (t == null) { - t = new Tracker(); - mTrackers.put(tag, t); - } - return t; - } - - public void verify() { - mTrackers.values().forEach(Tracker::verify); + public LeakCheck getLeakCheck() { + return mLeakCheck; } public void injectLeakCheckedDependencies(Class<?>... cls) { @@ -154,26 +85,61 @@ public abstract class LeakCheckedTest extends SysuiTestCase { } public <T> void injectLeakCheckedDependency(Class<T> c) { - injectTestDependency(c, getLeakChecker(c)); + mDependency.injectTestDependency(c, mLeakCheck.getLeakChecker(c)); } - public <T extends CallbackController> T addListening(T mock, Class<T> cls, String tag) { - doAnswer(new Answer<Void>() { - @Override - public Void answer(InvocationOnMock invocation) throws Throwable { - getTracker(tag).getLeakInfo(invocation.getArguments()[0]) - .addAllocation(new Throwable()); - return null; - } - }).when(mock).addCallback(any()); - doAnswer(new Answer<Void>() { - @Override - public Void answer(InvocationOnMock invocation) throws Throwable { - getTracker(tag).getLeakInfo(invocation.getArguments()[0]).clearAllocations(); - return null; + public static class SysuiLeakCheck extends LeakCheck { + + private final Map<Class, Object> mLeakCheckers = new ArrayMap<>(); + + public SysuiLeakCheck() { + super(); + } + + public <T> T getLeakChecker(Class<T> cls) { + Object obj = mLeakCheckers.get(cls); + if (obj == null) { + // Lazy create checkers so we only have the ones we need. + if (cls == BluetoothController.class) { + obj = new FakeBluetoothController(this); + } else if (cls == LocationController.class) { + obj = new FakeLocationController(this); + } else if (cls == RotationLockController.class) { + obj = new FakeRotationLockController(this); + } else if (cls == ZenModeController.class) { + obj = new FakeZenModeController(this); + } else if (cls == CastController.class) { + obj = new FakeCastController(this); + } else if (cls == HotspotController.class) { + obj = new FakeHotspotController(this); + } else if (cls == FlashlightController.class) { + obj = new FakeFlashlightController(this); + } else if (cls == UserInfoController.class) { + obj = new FakeUserInfoController(this); + } else if (cls == KeyguardMonitor.class) { + obj = new FakeKeyguardMonitor(this); + } else if (cls == BatteryController.class) { + obj = new FakeBatteryController(this); + } else if (cls == SecurityController.class) { + obj = new FakeSecurityController(this); + } else if (cls == ManagedProfileController.class) { + obj = new FakeManagedProfileController(this); + } else if (cls == NextAlarmController.class) { + obj = new FakeNextAlarmController(this); + } else if (cls == NetworkController.class) { + obj = new FakeNetworkController(this); + } else if (cls == PluginManager.class) { + obj = new FakePluginManager(this); + } else if (cls == TunerService.class) { + obj = new FakeTunerService(this); + } else if (cls == StatusBarIconController.class) { + obj = new FakeStatusBarIconController(this); + } else { + Assert.fail(cls.getName() + " is not supported by LeakCheckedTest yet"); + } + mLeakCheckers.put(cls, obj); } - }).when(mock).removeCallback(any()); - mLeakCheckers.put(cls, mock); - return mock; + return (T) obj; + } } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/LeakInfo.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/LeakInfo.java deleted file mode 100644 index 1d016fbe092b..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/LeakInfo.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file - * except in compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the - * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ - -package com.android.systemui.utils.leaks; - -import android.util.Log; - -import org.junit.Assert; - -import java.io.PrintWriter; -import java.io.StringWriter; -import java.util.ArrayList; -import java.util.List; - -public class LeakInfo { - private static final String TAG = "LeakInfo"; - private List<Throwable> mThrowables = new ArrayList<>(); - - LeakInfo() { - } - - public void addAllocation(Throwable t) { - // TODO: Drop off the first element in the stack trace here to have a cleaner stack. - mThrowables.add(t); - } - - public void clearAllocations() { - mThrowables.clear(); - } - - void verify() { - if (mThrowables.size() == 0) return; - Log.e(TAG, "Listener or binding not properly released"); - for (Throwable t : mThrowables) { - Log.e(TAG, "Allocation found", t); - } - StringWriter writer = new StringWriter(); - mThrowables.get(0).printStackTrace(new PrintWriter(writer)); - Assert.fail("Listener or binding not properly released\n" - + writer.toString()); - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/Tracker.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/Tracker.java deleted file mode 100644 index 26ffd10b8362..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/Tracker.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file - * except in compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the - * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ - -package com.android.systemui.utils.leaks; - -import android.util.ArrayMap; - -import com.android.systemui.utils.leaks.LeakInfo; - -import java.util.Map; - -public class Tracker { - private Map<Object, LeakInfo> mObjects = new ArrayMap<>(); - - public LeakInfo getLeakInfo(Object object) { - LeakInfo leakInfo = mObjects.get(object); - if (leakInfo == null) { - leakInfo = new LeakInfo(); - mObjects.put(object, leakInfo); - } - return leakInfo; - } - - void verify() { - mObjects.values().forEach(LeakInfo::verify); - } -} diff --git a/proto/src/metrics_constants.proto b/proto/src/metrics_constants.proto index 25481cee3d92..8a3b9af0ce08 100644 --- a/proto/src/metrics_constants.proto +++ b/proto/src/metrics_constants.proto @@ -56,6 +56,12 @@ message MetricsEvent { // Type for APP_TRANSITION event: The transition brought an already existing activity to the // front. TYPE_TRANSITION_HOT_LAUNCH = 9; + + // The action was successful + TYPE_SUCCESS = 10; + + // The action failed + TYPE_FAILURE = 11; } // Known visual elements: views or controls. @@ -1180,7 +1186,7 @@ message MetricsEvent { // OS: 6.0 BRIGHTNESS_DIALOG = 220; - // OPEN: Settings > Apps > Configure Apps > Draw over other apps + // OPEN: Settings > Apps > Configure Apps > Display over other apps // CATEGORY: SETTINGS // OS: 6.0 SYSTEM_ALERT_WINDOW_APPS = 221; @@ -3227,7 +3233,7 @@ message MetricsEvent { // ACTION: Allow "Draw over other apps" for an app APP_SPECIAL_PERMISSION_APPDRAW_ALLOW = 770; - // ACTION: Deny "Draw over other apps" for an app + // ACTION: Deny "Display over other apps" for an app APP_SPECIAL_PERMISSION_APPDRAW_DENY = 771; // ACTION: Allow "VR helper services" for an app @@ -3601,6 +3607,176 @@ message MetricsEvent { // FIELD: Settings inline search result value FIELD_SETTINGS_SEARCH_INLINE_RESULT_VALUE = 880; + // ACTION: Settings > Search > Click saved queries + ACTION_CLICK_SETTINGS_SEARCH_SAVED_QUERY = 881; + + // OPEN: Settings > Security & screen lock -> Lock screen preferences + // CATEGORY: SETTINGS + SETTINGS_LOCK_SCREEN_PREFERENCES = 882; + + // ACTION: An app requested the app-op permission ACCESS_NOTIFICATIONS + // PACKAGE: The package name of the app requesting the permission + ACTION_APPOP_REQUEST_ACCESS_NOTIFICATIONS = 883; + + // ACTION: An app was granted the app-op permission ACCESS_NOTIFICATIONS + // PACKAGE: The package name of the app that was granted the permission + ACTION_APPOP_GRANT_ACCESS_NOTIFICATIONS = 884; + + // ACTION: An app requested the app-op permission ACCESS_NOTIFICATIONS and the request was denied + // PACKAGE: The package name of the app requesting the permission + ACTION_APPOP_DENIED_ACCESS_NOTIFICATIONS = 885; + + // ACTION: The app-op permission ACCESS_NOTIFICATIONS was revoked for an app + // PACKAGE: The package name of the app the permission was revoked for + ACTION_APPOP_REVOKE_ACCESS_NOTIFICATIONS = 886; + + // ACTION: An app requested the app-op permission SYSTEM_ALERT_WINDOW + // PACKAGE: The package name of the app requesting the permission + ACTION_APPOP_REQUEST_SYSTEM_ALERT_WINDOW = 887; + + // ACTION: An app was granted the app-op permission SYSTEM_ALERT_WINDOW + // PACKAGE: The package name of the app that was granted the permission + ACTION_APPOP_GRANT_SYSTEM_ALERT_WINDOW = 888; + + // ACTION: An app requested the app-op permission SYSTEM_ALERT_WINDOW and the request was denied + // PACKAGE: The package name of the app requesting the permission + ACTION_APPOP_DENIED_SYSTEM_ALERT_WINDOW = 889; + + // ACTION: The app-op permission SYSTEM_ALERT_WINDOW was revoked for an app + // PACKAGE: The package name of the app the permission was revoked for + ACTION_APPOP_REVOKE_SYSTEM_ALERT_WINDOW = 890; + + // ACTION: An app requested the app-op permission REQUEST_WRITE_SETTINGS + // PACKAGE: The package name of the app requesting the permission + ACTION_APPOP_REQUEST_WRITE_SETTINGS = 891; + + // ACTION: An app was granted the app-op permission REQUEST_WRITE_SETTINGS + // PACKAGE: The package name of the app that was granted the permission + ACTION_APPOP_GRANT_WRITE_SETTINGS = 892; + + // ACTION: An app requested the app-op permission REQUEST_WRITE_SETTINGS and the request was denied + // PACKAGE: The package name of the app requesting the permission + ACTION_APPOP_DENIED_WRITE_SETTINGS = 893; + + // ACTION: The app-op permission REQUEST_WRITE_SETTINGS was revoked for an app + // PACKAGE: The package name of the app the permission was revoked for + ACTION_APPOP_REVOKE_WRITE_SETTINGS = 894; + + // ACTION: An app requested the app-op permission REQUEST_INSTALL_PACKAGES + // PACKAGE: The package name of the app requesting the permission + ACTION_APPOP_REQUEST_REQUEST_INSTALL_PACKAGES = 895; + + // ACTION: An app was granted the app-op permission REQUEST_INSTALL_PACKAGES + // PACKAGE: The package name of the app that was granted the permission + ACTION_APPOP_GRANT_REQUEST_INSTALL_PACKAGES = 896; + + // ACTION: An app requested the app-op permission REQUEST_INSTALL_PACKAGES and the request was denied + // PACKAGE: The package name of the app requesting the permission + ACTION_APPOP_DENIED_REQUEST_INSTALL_PACKAGES = 897; + + // ACTION: The app-op permission REQUEST_INSTALL_PACKAGES was revoked for an app + // PACKAGE: The package name of the app the permission was revoked for + ACTION_APPOP_REVOKE_REQUEST_INSTALL_PACKAGES = 898; + + // ACTION: Phase 1 of instant application resolution occurred + // OS: O + ACTION_INSTANT_APP_RESOLUTION_PHASE_ONE = 899; + + // ACTION: Phase 2 of instant application resolution occurred + // OS: O + ACTION_INSTANT_APP_RESOLUTION_PHASE_TWO = 900; + + // FIELD: The amount of time for an ephemeral resolution phase; in milliseconds + // OS: O + FIELD_INSTANT_APP_RESOLUTION_DELAY_MS = 901; + + // FIELD: The status of an ephemeral resolution phase + // Value 0: success + // Value 1: no full hash match + // OS: O + FIELD_INSTANT_APP_RESOLUTION_STATUS = 902; + + // FIELD - A token to identify all events that are part of the same instant application launch + // OS: O + FIELD_INSTANT_APP_LAUNCH_TOKEN = 903; + + // FIELD - The name of the package responsible for launching the activity + // OS: O + APP_TRANSITION_CALLING_PACKAGE_NAME = 904; + + // FIELD - Whether or not the launched activity is part of an instant application + // OS: O + APP_TRANSITION_IS_EPHEMERAL = 905; + + // An autofill session was started + // Package: Package of app that is autofilled + AUTOFILL_SESSION_STARTED = 906; + + // An autofill request was processed by a service + // Type TYPE_SUCCESS: The request succeeded + // Type TYPE_FAILURE: The request failed + // Package: Package of app that is autofilled + // Tag FIELD_AUTOFILL_SERVICE: Package of service that processed the request + // Tag FIELD_AUTOFILL_NUM_DATASET: The number of datasets returned (only in success case) + AUTOFILL_REQUEST = 907; + + // Tag of a field for a package of an autofill service + FIELD_AUTOFILL_SERVICE = 908; + + // Tag of a field for the number of datasets + FIELD_AUTOFILL_NUM_DATASETS = 909; + + // An autofill dataset selection UI was shown + // Type TYPE_DISMISS: UI was explicityly canceled by the user + // Type TYPE_CLOSE: UI was destroyed without influence of the user + // Type TYPE_ACTION: dataset was selected + // Type TYPE_DETAIL: authentication was selected + // Package: Package of app that was autofilled + // Tag FIELD_AUTOFILL_FILTERTEXT_LEN: The length of the filter text + // Tag FIELD_AUTOFILL_NUM_DATASETS: The number of datasets shown + AUTOFILL_FILL_UI = 910; + + // Tag of a field for the length of the filter text + FIELD_AUTOFILL_FILTERTEXT_LEN = 911; + + // An autofill authentification succeeded + // Package: Package of app that was autofilled + AUTOFILL_AUTHENTICATED = 912; + + // An activity was autofilled and all values could be applied + // Package: Package of app that is autofilled + // Tag FIELD_AUTOFILL_NUM_VALUES: Number of values that were suggested to be autofilled + // Tag FIELD_AUTOFILL_NUM_VIEWS_FILLED: Number of views that could be filled + AUTOFILL_DATASET_APPLIED = 913; + + // Tag of a field for the number values to be filled in + FIELD_AUTOFILL_NUM_VALUES = 914; + + // Tag of a field for the number of views that were filled + FIELD_AUTOFILL_NUM_VIEWS_FILLED = 915; + + // An autofill save UI was shown + // Type TYPE_DISMISS: UI was explicityly canceled by the user + // Type TYPE_CLOSE: UI was destroyed without influence of the user + // Type TYPE_ACTION: data was saved + // Package: Package of app that was autofilled + // Tag FIELD_AUTOFILL_NUM_ID: The number of ids that are saved + AUTOFILL_SAVE_UI = 916; + + // Tag of a field for the number of saveable ids + FIELD_AUTOFILL_NUM_IDS = 917; + + // ACTION: An autofill service was reqiested to save data + // Type TYPE_SUCCESS: The request succeeded + // Type TYPE_FAILURE: The request failed + // Package: Package of app that was autofilled + // Tag FIELD_AUTOFILL_SERVICE: Package of service that processed the request + AUTOFILL_DATA_SAVE_REQUEST = 918; + + // An auto-fill session was finished + // Package: Package of app that was autofilled + AUTOFILL_SESSION_FINISHED = 919; + // ---- End O Constants, all O constants go above this line ---- // Add new aosp constants above this line. diff --git a/rs/jni/android_renderscript_RenderScript.cpp b/rs/jni/android_renderscript_RenderScript.cpp index b4630efe80e8..c532efb79345 100644 --- a/rs/jni/android_renderscript_RenderScript.cpp +++ b/rs/jni/android_renderscript_RenderScript.cpp @@ -48,9 +48,6 @@ static constexpr bool kLogApi = false; using namespace android; -template <typename... T> -void UNUSED(T... t) {} - #define PER_ARRAY_TYPE(flag, fnc, readonly, ...) { \ jint len = 0; \ void *ptr = nullptr; \ diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java index fd93865ea97e..9e4d89cbc9c5 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java @@ -77,9 +77,6 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo */ static final int FLAG_FEATURE_INJECT_MOTION_EVENTS = 0x00000010; - static final int FEATURES_AFFECTING_MOTION_EVENTS = FLAG_FEATURE_INJECT_MOTION_EVENTS - | FLAG_FEATURE_AUTOCLICK | FLAG_FEATURE_TOUCH_EXPLORATION - | FLAG_FEATURE_SCREEN_MAGNIFIER; /** * Flag for enabling the feature to control the screen magnifier. If * {@link #FLAG_FEATURE_SCREEN_MAGNIFIER} is set this flag is ignored @@ -90,6 +87,16 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo */ static final int FLAG_FEATURE_CONTROL_SCREEN_MAGNIFIER = 0x00000020; + /** + * Flag for enabling the feature to trigger the screen magnifier + * from another on-device interaction. + */ + static final int FLAG_FEATURE_TRIGGERED_SCREEN_MAGNIFIER = 0x00000040; + + static final int FEATURES_AFFECTING_MOTION_EVENTS = FLAG_FEATURE_INJECT_MOTION_EVENTS + | FLAG_FEATURE_AUTOCLICK | FLAG_FEATURE_TOUCH_EXPLORATION + | FLAG_FEATURE_SCREEN_MAGNIFIER | FLAG_FEATURE_TRIGGERED_SCREEN_MAGNIFIER; + private final Runnable mProcessBatchedEventsRunnable = new Runnable() { @Override public void run() { @@ -379,6 +386,12 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo } } + void notifyAccessibilityButtonClicked() { + if (mMagnificationGestureHandler != null) { + mMagnificationGestureHandler.notifyShortcutTriggered(); + } + } + private void enableFeatures() { resetStreamState(); @@ -393,11 +406,14 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo } if ((mEnabledFeatures & FLAG_FEATURE_CONTROL_SCREEN_MAGNIFIER) != 0 - || (mEnabledFeatures & FLAG_FEATURE_SCREEN_MAGNIFIER) != 0) { + || ((mEnabledFeatures & FLAG_FEATURE_SCREEN_MAGNIFIER) != 0) + || ((mEnabledFeatures & FLAG_FEATURE_TRIGGERED_SCREEN_MAGNIFIER) != 0)) { final boolean detectControlGestures = (mEnabledFeatures & FLAG_FEATURE_SCREEN_MAGNIFIER) != 0; + final boolean triggerable = (mEnabledFeatures + & FLAG_FEATURE_TRIGGERED_SCREEN_MAGNIFIER) != 0; mMagnificationGestureHandler = new MagnificationGestureHandler( - mContext, mAms, detectControlGestures); + mContext, mAms, detectControlGestures, triggerable); addFirstEventHandler(mMagnificationGestureHandler); } diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index b56035fb84ca..397938ac3160 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -416,10 +416,12 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { removeUser(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0)); } else if (Intent.ACTION_USER_PRESENT.equals(action)) { // We will update when the automation service dies. - UserState userState = getCurrentUserStateLocked(); - if (!userState.isUiAutomationSuppressingOtherServices()) { - if (readConfigurationForUserStateLocked(userState)) { - onUserStateChangedLocked(userState); + synchronized (mLock) { + UserState userState = getCurrentUserStateLocked(); + if (!userState.isUiAutomationSuppressingOtherServices()) { + if (readConfigurationForUserStateLocked(userState)) { + onUserStateChangedLocked(userState); + } } } } else if (Intent.ACTION_SETTING_RESTORED.equals(action)) { @@ -779,6 +781,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { userState.mIsTouchExplorationEnabled = false; userState.mIsEnhancedWebAccessibilityEnabled = false; userState.mIsDisplayMagnificationEnabled = false; + userState.mIsNavBarMagnificationEnabled = false; userState.mIsAutoclickEnabled = false; userState.mEnabledServices.clear(); } @@ -829,6 +832,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { userState.mIsTouchExplorationEnabled = touchExplorationEnabled; userState.mIsEnhancedWebAccessibilityEnabled = false; userState.mIsDisplayMagnificationEnabled = false; + userState.mIsNavBarMagnificationEnabled = false; userState.mIsAutoclickEnabled = false; userState.mEnabledServices.clear(); userState.mEnabledServices.add(service); @@ -1150,11 +1154,16 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { private void notifyAccessibilityButtonClickedLocked() { final UserState state = getCurrentUserStateLocked(); - for (int i = state.mBoundServices.size() - 1; i >= 0; i--) { - final Service service = state.mBoundServices.get(i); - // TODO(b/34720082): Only notify a single user-defined service - if (service.mRequestAccessibilityButton) { - service.notifyAccessibilityButtonClickedLocked(); + if (state.mIsNavBarMagnificationEnabled) { + mMainHandler.obtainMessage( + MainHandler.MSG_SEND_ACCESSIBILITY_BUTTON_TO_INPUT_FILTER).sendToTarget(); + } else { + for (int i = state.mBoundServices.size() - 1; i >= 0; i--) { + final Service service = state.mBoundServices.get(i); + // TODO(b/34720082): Only notify a single user-defined service + if (service.mRequestAccessibilityButton) { + service.notifyAccessibilityButtonClickedLocked(); + } } } } @@ -1546,6 +1555,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { if (userState.mIsDisplayMagnificationEnabled) { flags |= AccessibilityInputFilter.FLAG_FEATURE_SCREEN_MAGNIFIER; } + if (userState.mIsNavBarMagnificationEnabled) { + flags |= AccessibilityInputFilter.FLAG_FEATURE_TRIGGERED_SCREEN_MAGNIFIER; + } if (userHasMagnificationServicesLocked(userState)) { flags |= AccessibilityInputFilter.FLAG_FEATURE_CONTROL_SCREEN_MAGNIFIER; } @@ -1779,7 +1791,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { somethingChanged |= readTouchExplorationEnabledSettingLocked(userState); somethingChanged |= readHighTextContrastEnabledSettingLocked(userState); somethingChanged |= readEnhancedWebAccessibilityEnabledChangedLocked(userState); - somethingChanged |= readDisplayMagnificationEnabledSettingLocked(userState); + somethingChanged |= readMagnificationEnabledSettingsLocked(userState); somethingChanged |= readAutoclickEnabledSettingLocked(userState); somethingChanged |= readAccessibilityShortcutSettingLocked(userState); return somethingChanged; @@ -1808,13 +1820,19 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { return false; } - private boolean readDisplayMagnificationEnabledSettingLocked(UserState userState) { + private boolean readMagnificationEnabledSettingsLocked(UserState userState) { final boolean displayMagnificationEnabled = Settings.Secure.getIntForUser( mContext.getContentResolver(), Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED, 0, userState.mUserId) == 1; - if (displayMagnificationEnabled != userState.mIsDisplayMagnificationEnabled) { + final boolean navBarMagnificationEnabled = Settings.Secure.getIntForUser( + mContext.getContentResolver(), + Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED, + 0, userState.mUserId) == 1; + if ((displayMagnificationEnabled != userState.mIsDisplayMagnificationEnabled) + || (navBarMagnificationEnabled != userState.mIsNavBarMagnificationEnabled)) { userState.mIsDisplayMagnificationEnabled = displayMagnificationEnabled; + userState.mIsNavBarMagnificationEnabled = navBarMagnificationEnabled; return true; } return false; @@ -2016,8 +2034,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { return; } - if (userState.mIsDisplayMagnificationEnabled || - userHasListeningMagnificationServicesLocked(userState)) { + if (userState.mIsDisplayMagnificationEnabled || userState.mIsNavBarMagnificationEnabled + || userHasListeningMagnificationServicesLocked(userState)) { // Initialize the magnification controller if necessary getMagnificationController(); mMagnificationController.register(); @@ -2239,6 +2257,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { pw.append(", touchExplorationEnabled=" + userState.mIsTouchExplorationEnabled); pw.append(", displayMagnificationEnabled=" + userState.mIsDisplayMagnificationEnabled); + pw.append(", navBarMagnificationEnabled=" + + userState.mIsNavBarMagnificationEnabled); pw.append(", autoclickEnabled=" + userState.mIsAutoclickEnabled); if (userState.mUiAutomationService != null) { pw.append(", "); @@ -2318,6 +2338,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { public static final int MSG_SEND_SERVICES_STATE_CHANGED_TO_CLIENTS = 10; public static final int MSG_UPDATE_FINGERPRINT = 11; public static final int MSG_SEND_RELEVANT_EVENTS_CHANGED_TO_CLIENTS = 12; + public static final int MSG_SEND_ACCESSIBILITY_BUTTON_TO_INPUT_FILTER = 13; public MainHandler(Looper looper) { super(looper); @@ -2404,6 +2425,14 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { } }); } break; + + case MSG_SEND_ACCESSIBILITY_BUTTON_TO_INPUT_FILTER: { + synchronized (mLock) { + if (mHasInputFilter && mInputFilter != null) { + mInputFilter.notifyAccessibilityButtonClicked(); + } + } + } } } @@ -4786,6 +4815,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { public boolean mIsTextHighContrastEnabled; public boolean mIsEnhancedWebAccessibilityEnabled; public boolean mIsDisplayMagnificationEnabled; + public boolean mIsNavBarMagnificationEnabled; public boolean mIsAutoclickEnabled; public boolean mIsPerformGesturesEnabled; public boolean mIsFilterKeyEventsEnabled; @@ -4854,6 +4884,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { mIsTouchExplorationEnabled = false; mIsEnhancedWebAccessibilityEnabled = false; mIsDisplayMagnificationEnabled = false; + mIsNavBarMagnificationEnabled = false; mIsAutoclickEnabled = false; mSoftKeyboardShowMode = 0; @@ -4886,6 +4917,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { private final Uri mDisplayMagnificationEnabledUri = Settings.Secure.getUriFor( Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED); + private final Uri mNavBarMagnificationEnabledUri = Settings.Secure.getUriFor( + Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED); + private final Uri mAutoclickEnabledUri = Settings.Secure.getUriFor( Settings.Secure.ACCESSIBILITY_AUTOCLICK_ENABLED); @@ -4925,6 +4959,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { false, this, UserHandle.USER_ALL); contentResolver.registerContentObserver(mDisplayMagnificationEnabledUri, false, this, UserHandle.USER_ALL); + contentResolver.registerContentObserver(mNavBarMagnificationEnabledUri, + false, this, UserHandle.USER_ALL); contentResolver.registerContentObserver(mAutoclickEnabledUri, false, this, UserHandle.USER_ALL); contentResolver.registerContentObserver(mEnabledAccessibilityServicesUri, @@ -4964,8 +5000,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { if (readTouchExplorationEnabledSettingLocked(userState)) { onUserStateChangedLocked(userState); } - } else if (mDisplayMagnificationEnabledUri.equals(uri)) { - if (readDisplayMagnificationEnabledSettingLocked(userState)) { + } else if (mDisplayMagnificationEnabledUri.equals(uri) + || mNavBarMagnificationEnabledUri.equals(uri)) { + if (readMagnificationEnabledSettingsLocked(userState)) { onUserStateChangedLocked(userState); } } else if (mAutoclickEnabledUri.equals(uri)) { diff --git a/services/accessibility/java/com/android/server/accessibility/MagnificationController.java b/services/accessibility/java/com/android/server/accessibility/MagnificationController.java index f65046ce4350..caa74b9512d1 100644 --- a/services/accessibility/java/com/android/server/accessibility/MagnificationController.java +++ b/services/accessibility/java/com/android/server/accessibility/MagnificationController.java @@ -685,6 +685,12 @@ class MagnificationController implements Handler.Callback { } } + void setForceShowMagnifiableBounds(boolean show) { + if (mRegistered) { + mWindowManager.setForceShowMagnifiableBounds(show); + } + } + private void getMagnifiedFrameInContentCoordsLocked(Rect outFrame) { final float scale = getSentScale(); final float offsetX = getSentOffsetX(); diff --git a/services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java index f6e5340ed7ee..7e82edaae3e5 100644 --- a/services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java +++ b/services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java @@ -16,7 +16,10 @@ package com.android.server.accessibility; +import android.content.BroadcastReceiver; import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; import android.os.Handler; import android.os.Message; import android.util.MathUtils; @@ -57,22 +60,30 @@ import android.view.accessibility.AccessibilityEvent; * be the same but when the finger goes up the screen will stay magnified. * In other words, the initial magnified state is sticky. * - * 3. Pinching with any number of additional fingers when viewport dragging + * 3. Magnification can optionally be "triggered" by some external shortcut + * affordance. When this occurs via {@link #notifyShortcutTriggered()} a + * subsequent tap in a magnifiable region will engage permanent screen + * magnification as described in #1. Alternatively, a subsequent long-press + * or drag will engage magnification with viewport dragging as described in + * #2. Once magnified, all following behaviors apply whether magnification + * was engaged via a triple-tap or by a triggered shortcut. + * + * 4. Pinching with any number of additional fingers when viewport dragging * is enabled, i.e. the user triple tapped and holds, would adjust the * magnification scale which will become the current default magnification * scale. The next time the user magnifies the same magnification scale * would be used. * - * 4. When in a permanent magnified state the user can use two or more fingers + * 5. When in a permanent magnified state the user can use two or more fingers * to pan the viewport. Note that in this mode the content is panned as * opposed to the viewport dragging mode in which the viewport is moved. * - * 5. When in a permanent magnified state the user can use two or more + * 6. When in a permanent magnified state the user can use two or more * fingers to change the magnification scale which will become the current * default magnification scale. The next time the user magnifies the same * magnification scale would be used. * - * 6. The magnification scale will be persisted in settings and in the cloud. + * 7. The magnification scale will be persisted in settings and in the cloud. */ class MagnificationGestureHandler implements EventStreamTransformation { private static final String LOG_TAG = "MagnificationEventHandler"; @@ -94,8 +105,10 @@ class MagnificationGestureHandler implements EventStreamTransformation { private final MagnifiedContentInteractionStateHandler mMagnifiedContentInteractionStateHandler; private final StateViewportDraggingHandler mStateViewportDraggingHandler; + private final ScreenStateReceiver mScreenStateReceiver; - private final boolean mDetectControlGestures; + private final boolean mDetectTripleTap; + private final boolean mTriggerable; private EventStreamTransformation mNext; @@ -104,19 +117,39 @@ class MagnificationGestureHandler implements EventStreamTransformation { private boolean mTranslationEnabledBeforePan; + private boolean mShortcutTriggered; + private PointerCoords[] mTempPointerCoords; private PointerProperties[] mTempPointerProperties; private long mDelegatingStateDownTime; + /** + * @param context Context for resolving various magnification-related resources + * @param ams AccessibilityManagerService used to obtain a {@link MagnificationController} + * @param detectTripleTap {@code true} if this detector should detect and respond to triple-tap + * gestures for engaging and disengaging magnification, + * {@code false} if it should ignore such gestures + * @param triggerable {@code true} if this detector should be "triggerable" by some external + * shortcut invoking {@link #notifyShortcutTriggered}, {@code + * false} if it should ignore such triggers. + */ public MagnificationGestureHandler(Context context, AccessibilityManagerService ams, - boolean detectControlGestures) { + boolean detectTripleTap, boolean triggerable) { mMagnificationController = ams.getMagnificationController(); mDetectingStateHandler = new DetectingStateHandler(context); mStateViewportDraggingHandler = new StateViewportDraggingHandler(); mMagnifiedContentInteractionStateHandler = new MagnifiedContentInteractionStateHandler(context); - mDetectControlGestures = detectControlGestures; + mDetectTripleTap = detectTripleTap; + mTriggerable = triggerable; + + if (triggerable) { + mScreenStateReceiver = new ScreenStateReceiver(context, this); + mScreenStateReceiver.register(); + } else { + mScreenStateReceiver = null; + } transitionToState(STATE_DETECTING); } @@ -129,7 +162,7 @@ class MagnificationGestureHandler implements EventStreamTransformation { } return; } - if (!mDetectControlGestures) { + if (!mDetectTripleTap && !mTriggerable) { if (mNext != null) { dispatchTransformedEvent(event, rawEvent, policyFlags); } @@ -151,7 +184,7 @@ class MagnificationGestureHandler implements EventStreamTransformation { break; case STATE_MAGNIFIED_INTERACTION: { // mMagnifiedContentInteractionStateHandler handles events only - // if this is the current state since it uses ScaleGestureDetecotr + // if this is the current state since it uses ScaleGestureDetector // and a GestureDetector which need well formed event stream. } break; @@ -193,11 +226,34 @@ class MagnificationGestureHandler implements EventStreamTransformation { @Override public void onDestroy() { + if (mScreenStateReceiver != null) { + mScreenStateReceiver.unregister(); + } clear(); } + void notifyShortcutTriggered() { + if (mTriggerable) { + if (mMagnificationController.resetIfNeeded(true)) { + clear(); + } else { + setMagnificationShortcutTriggered(!mShortcutTriggered); + } + } + } + + private void setMagnificationShortcutTriggered(boolean state) { + if (mShortcutTriggered == state) { + return; + } + + mShortcutTriggered = state; + mMagnificationController.setForceShowMagnifiableBounds(state); + } + private void clear() { mCurrentState = STATE_DETECTING; + setMagnificationShortcutTriggered(false); mDetectingStateHandler.clear(); mStateViewportDraggingHandler.clear(); mMagnifiedContentInteractionStateHandler.clear(); @@ -575,31 +631,51 @@ class MagnificationGestureHandler implements EventStreamTransformation { mHandler.removeMessages(MESSAGE_TRANSITION_TO_DELEGATING_STATE); if (!mMagnificationController.magnificationRegionContains( event.getX(), event.getY())) { - transitionToDelegatingStateAndClear(); + transitionToDelegatingState(!mShortcutTriggered); return; } - if (mTapCount == ACTION_TAP_COUNT - 1 && mLastDownEvent != null - && GestureUtils.isMultiTap(mLastDownEvent, event, - mMultiTapTimeSlop, mMultiTapDistanceSlop, 0)) { + if (mShortcutTriggered) { Message message = mHandler.obtainMessage(MESSAGE_ON_ACTION_TAP_AND_HOLD, policyFlags, 0, event); mHandler.sendMessageDelayed(message, ViewConfiguration.getLongPressTimeout()); - } else if (mTapCount < ACTION_TAP_COUNT) { + return; + } + if (mDetectTripleTap) { + if ((mTapCount == ACTION_TAP_COUNT - 1) && (mLastDownEvent != null) + && GestureUtils.isMultiTap(mLastDownEvent, event, mMultiTapTimeSlop, + mMultiTapDistanceSlop, 0)) { + Message message = mHandler.obtainMessage(MESSAGE_ON_ACTION_TAP_AND_HOLD, + policyFlags, 0, event); + mHandler.sendMessageDelayed(message, + ViewConfiguration.getLongPressTimeout()); + } else if (mTapCount < ACTION_TAP_COUNT) { + Message message = mHandler.obtainMessage( + MESSAGE_TRANSITION_TO_DELEGATING_STATE); + mHandler.sendMessageDelayed(message, mMultiTapTimeSlop); + } + clearLastDownEvent(); + mLastDownEvent = MotionEvent.obtain(event); + } else if (mMagnificationController.isMagnifying()) { + // If magnified, consume an ACTION_DOWN until mMultiTapTimeSlop or + // mTapDistanceSlop is reached to ensure MAGNIFIED_INTERACTION is reachable. Message message = mHandler.obtainMessage( MESSAGE_TRANSITION_TO_DELEGATING_STATE); mHandler.sendMessageDelayed(message, mMultiTapTimeSlop); + return; + } else { + transitionToDelegatingState(true); + return; } - clearLastDownEvent(); - mLastDownEvent = MotionEvent.obtain(event); } break; case MotionEvent.ACTION_POINTER_DOWN: { if (mMagnificationController.isMagnifying()) { + mHandler.removeMessages(MESSAGE_TRANSITION_TO_DELEGATING_STATE); transitionToState(STATE_MAGNIFIED_INTERACTION); clear(); } else { - transitionToDelegatingStateAndClear(); + transitionToDelegatingState(true); } } break; @@ -608,29 +684,34 @@ class MagnificationGestureHandler implements EventStreamTransformation { final double distance = GestureUtils.computeDistance(mLastDownEvent, event, 0); if (Math.abs(distance) > mTapDistanceSlop) { - transitionToDelegatingStateAndClear(); + transitionToDelegatingState(true); } } } break; case MotionEvent.ACTION_UP: { - if (mLastDownEvent == null) { - return; - } - mHandler.removeMessages(MESSAGE_ON_ACTION_TAP_AND_HOLD); if (!mMagnificationController.magnificationRegionContains( event.getX(), event.getY())) { - transitionToDelegatingStateAndClear(); + transitionToDelegatingState(!mShortcutTriggered); return; } + if (mShortcutTriggered) { + clear(); + onActionTap(event, policyFlags); + return; + } + if (mLastDownEvent == null) { + return; + } + mHandler.removeMessages(MESSAGE_ON_ACTION_TAP_AND_HOLD); if (!GestureUtils.isTap(mLastDownEvent, event, mTapTimeSlop, mTapDistanceSlop, 0)) { - transitionToDelegatingStateAndClear(); + transitionToDelegatingState(true); return; } - if (mLastTapUpEvent != null && !GestureUtils.isMultiTap(mLastTapUpEvent, - event, mMultiTapTimeSlop, mMultiTapDistanceSlop, 0)) { - transitionToDelegatingStateAndClear(); + if (mLastTapUpEvent != null && !GestureUtils.isMultiTap( + mLastTapUpEvent, event, mMultiTapTimeSlop, mMultiTapDistanceSlop, 0)) { + transitionToDelegatingState(true); return; } mTapCount++; @@ -655,6 +736,7 @@ class MagnificationGestureHandler implements EventStreamTransformation { @Override public void clear() { + setMagnificationShortcutTriggered(false); mHandler.removeMessages(MESSAGE_ON_ACTION_TAP_AND_HOLD); mHandler.removeMessages(MESSAGE_TRANSITION_TO_DELEGATING_STATE); clearTapDetectionState(); @@ -714,10 +796,12 @@ class MagnificationGestureHandler implements EventStreamTransformation { } } - private void transitionToDelegatingStateAndClear() { + private void transitionToDelegatingState(boolean andClear) { transitionToState(STATE_DELEGATING); sendDelayedMotionEvents(); - clear(); + if (andClear) { + clear(); + } } private void onActionTap(MotionEvent up, int policyFlags) { @@ -820,4 +904,30 @@ class MagnificationGestureHandler implements EventStreamTransformation { mPolicyFlags = 0; } } + + /** + * BroadcastReceiver used to cancel the magnification shortcut when the screen turns off + */ + private static class ScreenStateReceiver extends BroadcastReceiver { + private final Context mContext; + private final MagnificationGestureHandler mGestureHandler; + + public ScreenStateReceiver(Context context, MagnificationGestureHandler gestureHandler) { + mContext = context; + mGestureHandler = gestureHandler; + } + + public void register() { + mContext.registerReceiver(this, new IntentFilter(Intent.ACTION_SCREEN_OFF)); + } + + public void unregister() { + mContext.unregisterReceiver(this); + } + + @Override + public void onReceive(Context context, Intent intent) { + mGestureHandler.setMagnificationShortcutTriggered(false); + } + } } diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java index a372f958c94c..c7ba1ff60a1e 100644 --- a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java +++ b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java @@ -28,6 +28,7 @@ import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.database.ContentObserver; import android.graphics.Rect; @@ -53,6 +54,7 @@ import android.view.autofill.IAutoFillManagerClient; import com.android.internal.annotations.GuardedBy; import com.android.internal.os.BackgroundThread; import com.android.internal.os.IResultReceiver; +import com.android.internal.util.Preconditions; import com.android.server.FgThread; import com.android.server.LocalServices; import com.android.server.SystemService; @@ -316,13 +318,27 @@ public final class AutofillManagerService extends SystemService { @Override public void startSession(IBinder activityToken, IBinder windowToken, IBinder appCallback, AutofillId autofillId, Rect bounds, AutofillValue value, int userId, - boolean hasCallback) { + boolean hasCallback, int flags, String packageName) { // TODO(b/33197203): make sure it's called by resumed / focused activity + activityToken = Preconditions.checkNotNull(activityToken, "activityToken"); + appCallback = Preconditions.checkNotNull(appCallback, "appCallback"); + autofillId = Preconditions.checkNotNull(autofillId, "autoFillId"); + bounds = Preconditions.checkNotNull(bounds, "bounds"); + packageName = Preconditions.checkNotNull(packageName, "packageName"); + + Preconditions.checkArgument(userId == UserHandle.getUserId(getCallingUid()), "userId"); + + try { + mContext.getPackageManager().getPackageInfoAsUser(packageName, 0, userId); + } catch (PackageManager.NameNotFoundException e) { + throw new IllegalArgumentException(packageName + " is not a valid package", e); + } + synchronized (mLock) { final AutofillManagerServiceImpl service = getServiceForUserLocked(userId); service.startSessionLocked(activityToken, windowToken, appCallback, - autofillId, bounds, value, hasCallback); + autofillId, bounds, value, hasCallback, flags, packageName); } } diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java index b6c60d0cdb17..df76009db008 100644 --- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java +++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java @@ -28,6 +28,7 @@ import static com.android.server.autofill.Helper.DEBUG; import static com.android.server.autofill.Helper.VERBOSE; import static com.android.server.autofill.Helper.findValue; +import android.annotation.NonNull; import android.annotation.Nullable; import android.app.Activity; import android.app.ActivityManager; @@ -43,6 +44,7 @@ import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.ServiceInfo; import android.graphics.Rect; +import android.metrics.LogMaker; import android.os.Binder; import android.os.Bundle; import android.os.IBinder; @@ -60,13 +62,18 @@ import android.service.autofill.SaveInfo; import android.text.TextUtils; import android.util.ArrayMap; import android.util.LocalLog; +import android.util.Log; import android.util.PrintWriterPrinter; import android.util.Slog; import android.view.autofill.AutofillId; import android.view.autofill.AutofillManager; import android.view.autofill.AutofillValue; import android.view.autofill.IAutoFillManagerClient; +import android.view.autofill.AutofillManager.AutofillCallback; + import com.android.internal.annotations.GuardedBy; +import com.android.internal.logging.MetricsLogger; +import com.android.internal.logging.nano.MetricsProto; import com.android.internal.os.HandlerCaller; import com.android.internal.os.IResultReceiver; import com.android.server.autofill.ui.AutoFillUI; @@ -91,6 +98,7 @@ final class AutofillManagerServiceImpl { private final Context mContext; private final Object mLock; private final AutoFillUI mUi; + private final MetricsLogger mMetricsLogger = new MetricsLogger(); private RemoteCallbackList<IAutoFillManagerClient> mClients; private AutofillServiceInfo mInfo; @@ -169,7 +177,7 @@ final class AutofillManagerServiceImpl { structure.sanitizeForParceling(true); // TODO(b/33197203): Need to pipe the bundle - session.mRemoteFillService.onFillRequest(structure, null); + session.mRemoteFillService.onFillRequest(structure, null, session.mFlags); } }; @@ -227,9 +235,8 @@ final class AutofillManagerServiceImpl { if (!hasService()) { final int sessionCount = mSessions.size(); for (int i = sessionCount - 1; i >= 0; i--) { - Session session = mSessions.valueAt(i); - session.destroyLocked(); - mSessions.removeAt(i); + final Session session = mSessions.valueAt(i); + session.removeSelfLocked(); } } sendStateToClients(); @@ -283,8 +290,10 @@ final class AutofillManagerServiceImpl { } } - void startSessionLocked(IBinder activityToken, IBinder windowToken, IBinder appCallbackToken, - AutofillId autofillId, Rect bounds, AutofillValue value, boolean hasCallback) { + void startSessionLocked(@NonNull IBinder activityToken, @Nullable IBinder windowToken, + @NonNull IBinder appCallbackToken, @NonNull AutofillId autofillId, @NonNull Rect bounds, + @Nullable AutofillValue value, boolean hasCallback, int flags, + @NonNull String packageName) { if (!hasService()) { return; } @@ -292,7 +301,7 @@ final class AutofillManagerServiceImpl { final String historyItem = "s=" + mInfo.getServiceInfo().packageName + " u=" + mUserId + " a=" + activityToken - + " i=" + autofillId + " b=" + bounds + " hc=" + hasCallback; + + " i=" + autofillId + " b=" + bounds + " hc=" + hasCallback + " f=" + flags; mRequestsHistory.log(historyItem); // TODO(b/33197203): Handle partitioning @@ -303,7 +312,7 @@ final class AutofillManagerServiceImpl { } final Session newSession = createSessionByTokenLocked(activityToken, - windowToken, appCallbackToken, hasCallback); + windowToken, appCallbackToken, hasCallback, flags, packageName); newSession.updateLocked(autofillId, bounds, value, FLAG_START_SESSION); } @@ -318,7 +327,13 @@ final class AutofillManagerServiceImpl { return; } - session.showSaveLocked(); + final boolean finished = session.showSaveLocked(); + if (DEBUG) { + Log.d(TAG, "finishSessionLocked(): session finished on save? " + finished); + } + if (finished) { + session.removeSelf(); + } } void cancelSessionLocked(IBinder activityToken) { @@ -331,14 +346,14 @@ final class AutofillManagerServiceImpl { Slog.w(TAG, "cancelSessionLocked(): no session for " + activityToken); return; } - - session.destroyLocked(); + session.removeSelfLocked(); } - private Session createSessionByTokenLocked(IBinder activityToken, IBinder windowToken, - IBinder appCallbackToken, boolean hasCallback) { + private Session createSessionByTokenLocked(@NonNull IBinder activityToken, + @Nullable IBinder windowToken, @NonNull IBinder appCallbackToken, boolean hasCallback, + int flags, @NonNull String packageName) { final Session newSession = new Session(mContext, activityToken, - windowToken, appCallbackToken, hasCallback); + windowToken, appCallbackToken, hasCallback, flags, packageName); mSessions.put(activityToken, newSession); /* @@ -349,7 +364,6 @@ final class AutofillManagerServiceImpl { * - display disclosure if needed */ try { - // TODO(b/33197203): add MetricsLogger call final Bundle receiverExtras = new Bundle(); receiverExtras.putBinder(EXTRA_ACTIVITY_TOKEN, activityToken); final long identity = Binder.clearCallingIdentity(); @@ -369,7 +383,6 @@ final class AutofillManagerServiceImpl { void updateSessionLocked(IBinder activityToken, AutofillId autofillId, Rect bounds, AutofillValue value, int flags) { - // TODO(b/33197203): add MetricsLogger call final Session session = mSessions.get(activityToken); if (session == null) { if (VERBOSE) { @@ -593,6 +606,9 @@ final class AutofillManagerServiceImpl { private final IBinder mActivityToken; private final IBinder mWindowToken; + /** Package name of the app that is auto-filled */ + @NonNull private final String mPackageName; + @GuardedBy("mLock") private final Map<AutofillId, ViewState> mViewStates = new ArrayMap<>(); @@ -628,13 +644,20 @@ final class AutofillManagerServiceImpl { */ private boolean mHasCallback; - private Session(Context context, IBinder activityToken, IBinder windowToken, - IBinder client, boolean hasCallback) { + /** + * Flags used to start the session. + */ + private int mFlags; + private Session(@NonNull Context context, @NonNull IBinder activityToken, + @Nullable IBinder windowToken, @NonNull IBinder client, boolean hasCallback, + int flags, @NonNull String packageName) { mRemoteFillService = new RemoteFillService(context, mInfo.getServiceInfo().getComponentName(), mUserId, this); mActivityToken = activityToken; mWindowToken = windowToken; mHasCallback = hasCallback; + mFlags = flags; + mPackageName = packageName; mClient = IAutoFillManagerClient.Stub.asInterface(client); try { @@ -648,41 +671,82 @@ final class AutofillManagerServiceImpl { } catch (RemoteException e) { Slog.w(TAG, "linkToDeath() on mClient failed: " + e); } + + mMetricsLogger.action(MetricsProto.MetricsEvent.AUTOFILL_SESSION_STARTED, mPackageName); } // FillServiceCallbacks @Override - public void onFillRequestSuccess(FillResponse response) { - // TODO(b/33197203): add MetricsLogger call + public void onFillRequestSuccess(@Nullable FillResponse response, + @NonNull String servicePackageName) { if (response == null) { + // Nothing to be done, but need to notify client. + notifyUnavailableToClient(); removeSelf(); return; } + + if ((response.getDatasets() == null || response.getDatasets().isEmpty()) + && response.getAuthentication() == null) { + // Response is "empty" from an UI point of view, need to notify client. + notifyUnavailableToClient(); + } synchronized (mLock) { processResponseLocked(response); } + + LogMaker log = (new LogMaker(MetricsProto.MetricsEvent.AUTOFILL_REQUEST)) + .setType(MetricsProto.MetricsEvent.TYPE_SUCCESS) + .setPackageName(mPackageName) + .addTaggedData(MetricsProto.MetricsEvent.FIELD_AUTOFILL_NUM_DATASETS, + response.getDatasets() == null ? 0 : response.getDatasets().size()) + .addTaggedData(MetricsProto.MetricsEvent.FIELD_AUTOFILL_SERVICE, + servicePackageName); + mMetricsLogger.write(log); } // FillServiceCallbacks @Override - public void onFillRequestFailure(CharSequence message) { - // TODO(b/33197203): add MetricsLogger call + public void onFillRequestFailure(@Nullable CharSequence message, + @NonNull String servicePackageName) { + LogMaker log = (new LogMaker(MetricsProto.MetricsEvent.AUTOFILL_REQUEST)) + .setType(MetricsProto.MetricsEvent.TYPE_FAILURE) + .setPackageName(mPackageName) + .addTaggedData(MetricsProto.MetricsEvent.FIELD_AUTOFILL_SERVICE, + servicePackageName); + mMetricsLogger.write(log); + getUiForShowing().showError(message); removeSelf(); } // FillServiceCallbacks @Override - public void onSaveRequestSuccess() { - // TODO(b/33197203): add MetricsLogger call + public void onSaveRequestSuccess(@NonNull String servicePackageName) { + LogMaker log = (new LogMaker( + MetricsProto.MetricsEvent.AUTOFILL_DATA_SAVE_REQUEST)) + .setType(MetricsProto.MetricsEvent.TYPE_SUCCESS) + .setPackageName(mPackageName) + .addTaggedData(MetricsProto.MetricsEvent.FIELD_AUTOFILL_SERVICE, + servicePackageName); + mMetricsLogger.write(log); + // Nothing left to do... removeSelf(); } // FillServiceCallbacks @Override - public void onSaveRequestFailure(CharSequence message) { - // TODO(b/33197203): add MetricsLogger call + public void onSaveRequestFailure(@Nullable CharSequence message, + @NonNull String servicePackageName) { + LogMaker log = (new LogMaker( + MetricsProto.MetricsEvent.AUTOFILL_DATA_SAVE_REQUEST)) + .setType(MetricsProto.MetricsEvent.TYPE_FAILURE) + .setPackageName(mPackageName) + .addTaggedData(MetricsProto.MetricsEvent.FIELD_AUTOFILL_SERVICE, + servicePackageName); + mMetricsLogger.write(log); + getUiForShowing().showError(message); removeSelf(); } @@ -694,9 +758,7 @@ final class AutofillManagerServiceImpl { synchronized (mLock) { fillInIntent = createAuthFillInIntent(mStructure); } - mHandlerCaller.getHandler().post(() -> { - startAuthentication(intent, fillInIntent); - }); + mHandlerCaller.getHandler().post(() -> startAuthentication(intent, fillInIntent)); } // FillServiceCallbacks @@ -716,8 +778,7 @@ final class AutofillManagerServiceImpl { Binder.restoreCallingIdentity(identity); } synchronized (mLock) { - destroyLocked(); - mSessions.remove(this); + removeSelfLocked(); } } @@ -730,9 +791,7 @@ final class AutofillManagerServiceImpl { // AutoFillUiCallback @Override public void fill(Dataset dataset) { - mHandlerCaller.getHandler().post(() -> { - autoFill(dataset); - }); + mHandlerCaller.getHandler().post(() -> autoFill(dataset)); } // AutoFillUiCallback @@ -745,17 +804,13 @@ final class AutofillManagerServiceImpl { // AutoFillUiCallback @Override public void cancelSave() { - mHandlerCaller.getHandler().post(() -> { - removeSelf(); - }); + mHandlerCaller.getHandler().post(() -> removeSelf()); } // AutoFillUiCallback @Override public void onEvent(AutofillId id, int event) { - mHandlerCaller.getHandler().post(() -> { - notifyChangeToClient(id, event); - }); + mHandlerCaller.getHandler().post(() -> notifyChangeToClient(id, event)); } public void setAuthenticationResultLocked(Bundle data) { @@ -765,6 +820,9 @@ final class AutofillManagerServiceImpl { Parcelable result = data.getParcelable( AutofillManager.EXTRA_AUTHENTICATION_RESULT); if (result instanceof FillResponse) { + mMetricsLogger.action(MetricsProto.MetricsEvent.AUTOFILL_AUTHENTICATED, + mPackageName); + mCurrentResponse = (FillResponse) result; processResponseLocked(mCurrentResponse); } else if (result instanceof Dataset) { @@ -782,12 +840,14 @@ final class AutofillManagerServiceImpl { } /** - * Show the save UI, when session can be saved. + * Shows the save UI, when session can be saved. + * + * @return {@code true} if session is done, or {@code false} if it's pending user action. */ - public void showSaveLocked() { + public boolean showSaveLocked() { if (mStructure == null) { Slog.wtf(TAG, "showSaveLocked(): no mStructure"); - return; + return true; } if (mCurrentResponse == null) { // Happens when the activity / session was finished before the service replied, or @@ -795,42 +855,108 @@ final class AutofillManagerServiceImpl { if (DEBUG) { Slog.d(TAG, "showSaveLocked(): no mCurrentResponse"); } - return; + return true; } final SaveInfo saveInfo = mCurrentResponse.getSaveInfo(); if (DEBUG) { Slog.d(TAG, "showSaveLocked(): saveInfo=" + saveInfo); } - if (saveInfo == null || saveInfo.getSavableIds() == null - || saveInfo.getSavableIds().isEmpty()) { - return; + /* + * The Save dialog is only shown if all conditions below are met: + * + * - saveInfo is not null + * - autofillValue of all required ids is not null + * - autofillValue of at least one id (required or optional) has changed. + */ + + if (saveInfo == null) { + return true; } - final int size = saveInfo.getSavableIds().size(); - for (int i = 0; i < size; i++) { - final AutofillId id = saveInfo.getSavableIds().valueAt(i); + final AutofillId[] requiredIds = saveInfo.getRequiredIds(); + if (requiredIds == null || requiredIds.length == 0) { + Slog.w(TAG, "showSaveLocked(): no required ids on saveInfo"); + return true; + } + + boolean allRequiredAreNotEmpty = true; + boolean atLeastOneChanged = false; + for (int i = 0; i < requiredIds.length; i++) { + final AutofillId id = requiredIds[i]; final ViewState state = mViewStates.get(id); - if (state != null && state.mValueUpdated) { + if (state == null || state.mAutofillValue == null + || state.mAutofillValue.isEmpty()) { + final ViewNode node = findViewNodeByIdLocked(id); + if (node == null) { + Slog.w(TAG, "Service passed invalid id on SavableInfo: " + id); + allRequiredAreNotEmpty = false; + break; + } + final AutofillValue initialValue = node.getAutofillValue(); + if (initialValue == null || initialValue.isEmpty()) { + if (DEBUG) { + Slog.d(TAG, "finishSessionLocked(): empty initial value for " + id ); + } + allRequiredAreNotEmpty = false; + break; + } + } + if (state.mValueUpdated) { final AutofillValue filledValue = findValue(mAutoFilledDataset, id); - if (state.mAutofillValue == null || state.mAutofillValue.equals(filledValue)) { - continue; + if (!state.mAutofillValue.equals(filledValue)) { + if (DEBUG) { + Slog.d(TAG, "finishSessionLocked(): found a change on " + id + ": " + + filledValue + " => " + state.mAutofillValue); + } + atLeastOneChanged = true; } - if (DEBUG) { - Slog.d(TAG, "finishSessionLocked(): found a change on " + id + ": " - + state.mAutofillValue); + } else { + if (state.mAutofillValue == null || state.mAutofillValue.isEmpty()) { + if (DEBUG) { + Slog.d(TAG, "finishSessionLocked(): empty value for " + id + ": " + + state.mAutofillValue); + } + allRequiredAreNotEmpty = false; + break; + + } + } + } + + if (allRequiredAreNotEmpty) { + if (!atLeastOneChanged && saveInfo.getOptionalIds() != null) { + for (int i = 0; i < saveInfo.getOptionalIds().length; i++) { + final AutofillId id = saveInfo.getOptionalIds()[i]; + final ViewState state = mViewStates.get(id); + if (state != null && state.mAutofillValue != null && state.mValueUpdated) { + final AutofillValue filledValue = findValue(mAutoFilledDataset, id); + if (!state.mAutofillValue.equals(filledValue)) { + if (DEBUG) { + Slog.d(TAG, "finishSessionLocked(): found a change on optional " + + id + ": " + filledValue + " => " + + state.mAutofillValue); + } + atLeastOneChanged = true; + break; + } + } } + } + if (atLeastOneChanged) { getUiForShowing().showSaveUi( mInfo.getServiceInfo().loadLabel(mContext.getPackageManager()), - saveInfo); - return; + saveInfo, mPackageName); + return false; } } - // Nothing changed... if (DEBUG) { - Slog.d(TAG, "showSaveLocked(): with no changes, comes no responsibilities"); + Slog.d(TAG, "showSaveLocked(): with no changes, comes no responsibilities." + + "allRequiredAreNotNull=" + allRequiredAreNotEmpty + + ", atLeastOneChanged=" + atLeastOneChanged); } + return true; } /** @@ -872,7 +998,7 @@ final class AutofillManagerServiceImpl { mStructure.dump(); } - mRemoteFillService.onSaveRequest(mStructure, extras); + mRemoteFillService.onSaveRequest(mStructure, extras, () -> removeSelf()); } void updateLocked(AutofillId id, Rect bounds, AutofillValue value, int flags) { @@ -913,7 +1039,11 @@ final class AutofillManagerServiceImpl { viewState.mAutofillValue = value; // Update the chooser UI - getUiForShowing().filterFillUi(value.coerceToString()); + if (value.isText()) { + getUiForShowing().filterFillUi(value.getTextValue().toString()); + } else { + getUiForShowing().filterFillUi(null); + } } return; @@ -951,16 +1081,12 @@ final class AutofillManagerServiceImpl { @Override public void onFillReady(ViewState viewState, FillResponse response, Rect bounds, AutofillId filledId, @Nullable AutofillValue value) { - String filterText = ""; - if (value != null) { - // TODO(b/33197203): Handle other AutofillValue types - final CharSequence text = value.getTextValue(); - if (text != null) { - filterText = text.toString(); - } + String filterText = null; + if (value != null && value.isText()) { + filterText = value.getTextValue().toString(); } - getUiForShowing().showFillUi(filledId, response, bounds, filterText); + getUiForShowing().showFillUi(filledId, response, bounds, filterText, mPackageName); } private void notifyChangeToClient(AutofillId id, int event) { @@ -972,17 +1098,24 @@ final class AutofillManagerServiceImpl { } } + private void notifyUnavailableToClient() { + if (mCurrentViewState == null) { + // TODO(b/33197203): temporary sanity check; should never happen + Slog.w(TAG, "notifyUnavailable(): mCurrentViewState is null"); + return; + } + notifyChangeToClient(mCurrentViewState.mId, AutofillCallback.EVENT_INPUT_UNAVAILABLE); + } + private void processResponseLocked(FillResponse response) { if (DEBUG) { Slog.d(TAG, "processResponseLocked(auth=" + response.getAuthentication() + "):" + response); } - // TODO(b/33197203): add MetricsLogger calls - if (mCurrentViewState == null) { // TODO(b/33197203): temporary sanity check; should never happen - Slog.w(TAG, "processResponseLocked(): mCurrentResponse is null"); + Slog.w(TAG, "processResponseLocked(): mCurrentViewState is null"); return; } @@ -1034,6 +1167,7 @@ final class AutofillManagerServiceImpl { void dumpLocked(String prefix, PrintWriter pw) { pw.print(prefix); pw.print("mActivityToken: "); pw.println(mActivityToken); + pw.print(prefix); pw.print("mFlags: "); pw.println(mFlags); pw.print(prefix); pw.print("mCurrentResponse: "); pw.println(mCurrentResponse); pw.print(prefix); pw.print("mAutoFilledDataset: "); pw.println(mAutoFilledDataset); pw.print(prefix); pw.print("mCurrentViewStates: "); pw.println(mCurrentViewState); @@ -1113,17 +1247,23 @@ final class AutofillManagerServiceImpl { private void destroyLocked() { mRemoteFillService.destroy(); mUi.setCallback(null, null); + + mMetricsLogger.action(MetricsProto.MetricsEvent.AUTOFILL_SESSION_FINISHED, + mPackageName); } - private void removeSelf() { - if (VERBOSE) { - Slog.v(TAG, "removeSelf()"); + void removeSelf() { + synchronized (mLock) { + removeSelfLocked(); } + } - synchronized (mLock) { - destroyLocked(); - mSessions.remove(mActivityToken); + private void removeSelfLocked() { + if (VERBOSE) { + Slog.v(TAG, "removeSelfLocked()"); } + destroyLocked(); + mSessions.remove(mActivityToken); } } } diff --git a/services/autofill/java/com/android/server/autofill/RemoteFillService.java b/services/autofill/java/com/android/server/autofill/RemoteFillService.java index eeff37cef7e7..d1c8b4f84a91 100644 --- a/services/autofill/java/com/android/server/autofill/RemoteFillService.java +++ b/services/autofill/java/com/android/server/autofill/RemoteFillService.java @@ -16,6 +16,8 @@ package com.android.server.autofill; +import static com.android.server.autofill.Helper.DEBUG; + import android.annotation.NonNull; import android.annotation.Nullable; import android.app.assist.AssistStructure; @@ -38,8 +40,10 @@ import android.service.autofill.IFillCallback; import android.service.autofill.ISaveCallback; import android.text.format.DateUtils; import android.util.Slog; + import com.android.internal.os.HandlerCaller; import com.android.server.FgThread; +import com.android.server.autofill.AutofillManagerServiceImpl.Session; import java.io.PrintWriter; import java.lang.ref.WeakReference; @@ -55,8 +59,6 @@ import java.lang.ref.WeakReference; final class RemoteFillService implements DeathRecipient { private static final String LOG_TAG = "RemoteFillService"; - private static final boolean DEBUG = Helper.DEBUG; - // How long after the last interaction with the service we would unbind private static final long TIMEOUT_IDLE_BIND_MILLIS = 5 * DateUtils.SECOND_IN_MILLIS; @@ -87,10 +89,10 @@ final class RemoteFillService implements DeathRecipient { private PendingRequest mPendingRequest; public interface FillServiceCallbacks { - void onFillRequestSuccess(FillResponse response); - void onFillRequestFailure(CharSequence message); - void onSaveRequestSuccess(); - void onSaveRequestFailure(CharSequence message); + void onFillRequestSuccess(@Nullable FillResponse response, @NonNull String servicePackageName); + void onFillRequestFailure(@Nullable CharSequence message, @NonNull String servicePackageName); + void onSaveRequestSuccess(@NonNull String servicePackageName); + void onSaveRequestFailure(@Nullable CharSequence message, @NonNull String servicePackageName); void onServiceDied(RemoteFillService service); void onDisableSelf(); } @@ -132,15 +134,17 @@ final class RemoteFillService implements DeathRecipient { mCallbacks.onServiceDied(this); } - public void onFillRequest(@NonNull AssistStructure structure, @Nullable Bundle extras) { + public void onFillRequest(@NonNull AssistStructure structure, @Nullable Bundle extras, + int flags) { cancelScheduledUnbind(); - final PendingFillRequest request = new PendingFillRequest(structure, extras, this); + final PendingFillRequest request = new PendingFillRequest(structure, extras, this, flags); mHandler.obtainMessageO(MyHandler.MSG_ON_PENDING_REQUEST, request).sendToTarget(); } - public void onSaveRequest(@NonNull AssistStructure structure, @Nullable Bundle extras) { + public void onSaveRequest(@NonNull AssistStructure structure, @Nullable Bundle extras, + @Nullable Runnable finalizer) { cancelScheduledUnbind(); - final PendingSaveRequest request = new PendingSaveRequest(structure, extras, this); + final PendingSaveRequest request = new PendingSaveRequest(structure, extras, this, finalizer); mHandler.obtainMessageO(MyHandler.MSG_ON_PENDING_REQUEST, request).sendToTarget(); } @@ -240,7 +244,7 @@ final class RemoteFillService implements DeathRecipient { } mBinding = false; if (isBound()) { - // TODO(b/33197203, b/35395043): synchronize access instead + // TODO(b/33197203): synchronize access instead? // Need to double check if it's null, since it could be set on onServiceDisconnected() if (mAutoFillService != null) { try { @@ -261,7 +265,7 @@ final class RemoteFillService implements DeathRecipient { FillResponse response) { mHandler.getHandler().post(() -> { if (handleResponseCallbackCommon(pendingRequest)) { - mCallbacks.onFillRequestSuccess(response); + mCallbacks.onFillRequestSuccess(response, mComponentName.getPackageName()); } }); } @@ -270,7 +274,7 @@ final class RemoteFillService implements DeathRecipient { CharSequence message) { mHandler.getHandler().post(() -> { if (handleResponseCallbackCommon(pendingRequest)) { - mCallbacks.onFillRequestFailure(message); + mCallbacks.onFillRequestFailure(message, mComponentName.getPackageName()); } }); } @@ -278,7 +282,7 @@ final class RemoteFillService implements DeathRecipient { private void dispatchOnSaveRequestSuccess(PendingRequest pendingRequest) { mHandler.getHandler().post(() -> { if (handleResponseCallbackCommon(pendingRequest)) { - mCallbacks.onSaveRequestSuccess(); + mCallbacks.onSaveRequestSuccess(mComponentName.getPackageName()); } }); } @@ -287,7 +291,7 @@ final class RemoteFillService implements DeathRecipient { CharSequence message) { mHandler.getHandler().post(() -> { if (handleResponseCallbackCommon(pendingRequest)) { - mCallbacks.onSaveRequestFailure(message); + mCallbacks.onSaveRequestFailure(message, mComponentName.getPackageName()); } }); } @@ -322,7 +326,7 @@ final class RemoteFillService implements DeathRecipient { } try { - // TODO(b/33197203, b/35395043): synchronize access instead + // TODO(b/33197203): synchronize access instead? // Need to double check if it's null, since it could be set on // onServiceDisconnected() if (mAutoFillService != null) { @@ -413,16 +417,18 @@ final class RemoteFillService implements DeathRecipient { private static final class PendingFillRequest extends PendingRequest { private final Object mLock = new Object(); private final WeakReference<RemoteFillService> mWeakService; - private AssistStructure mStructure; - private Bundle mExtras; + private final AssistStructure mStructure; + private final Bundle mExtras; private final IFillCallback mCallback; private ICancellationSignal mCancellation; private boolean mCancelled; + private int mFlags; public PendingFillRequest(AssistStructure structure, - Bundle extras, RemoteFillService service) { + Bundle extras, RemoteFillService service, int flags) { mStructure = structure; mExtras = extras; + mFlags = flags; mWeakService = new WeakReference<>(service); mCallback = new IFillCallback.Stub() { @Override @@ -469,11 +475,7 @@ final class RemoteFillService implements DeathRecipient { if (remoteService != null) { try { remoteService.mAutoFillService.onFillRequest(mStructure, - mExtras, mCallback); - synchronized (mLock) { - mStructure = null; - mExtras = null; - } + mExtras, mCallback, mFlags); } catch (RemoteException e) { Slog.e(LOG_TAG, "Error calling on fill request", e); cancel(); @@ -503,17 +505,18 @@ final class RemoteFillService implements DeathRecipient { } private static final class PendingSaveRequest extends PendingRequest { - private final Object mLock = new Object(); private final WeakReference<RemoteFillService> mWeakService; - private AssistStructure mStructure; - private Bundle mExtras; + private final AssistStructure mStructure; + private final Bundle mExtras; private final ISaveCallback mCallback; + private final Runnable mFinalizer; - public PendingSaveRequest(@NonNull AssistStructure structure, - @Nullable Bundle extras, @NonNull RemoteFillService service) { + public PendingSaveRequest(@NonNull AssistStructure structure, @Nullable Bundle extras, + @NonNull RemoteFillService service, @Nullable Runnable finalizer) { mStructure = structure; mExtras = extras; mWeakService = new WeakReference<>(service); + mFinalizer = finalizer; mCallback = new ISaveCallback.Stub() { @Override public void onSuccess() { @@ -537,19 +540,17 @@ final class RemoteFillService implements DeathRecipient { @Override public void run() { - RemoteFillService service = mWeakService.get(); + final RemoteFillService service = mWeakService.get(); if (service != null) { try { - service.mAutoFillService.onSaveRequest(mStructure, - mExtras, mCallback); - synchronized (mLock) { - mStructure = null; - mExtras = null; - } + service.mAutoFillService.onSaveRequest(mStructure, mExtras, mCallback); } catch (RemoteException e) { Slog.e(LOG_TAG, "Error calling on save request", e); } } + if (mFinalizer != null) { + mFinalizer.run(); + } } @Override diff --git a/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java b/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java index 3eefa7cb6506..776fa1e58db8 100644 --- a/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java +++ b/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java @@ -23,6 +23,7 @@ import android.annotation.Nullable; import android.content.Context; import android.content.IntentSender; import android.graphics.Rect; +import android.metrics.LogMaker; import android.os.Handler; import android.os.IBinder; import android.service.autofill.Dataset; @@ -34,6 +35,8 @@ import android.util.Slog; import android.view.autofill.AutofillId; import android.widget.Toast; +import com.android.internal.logging.MetricsLogger; +import com.android.internal.logging.nano.MetricsProto; import com.android.server.UiThread; import java.io.PrintWriter; @@ -60,6 +63,7 @@ public final class AutoFillUI { private @Nullable IBinder mWindowToken; private int mSaveTimeoutMs = (int) (5 * DateUtils.SECOND_IN_MILLIS); + private final MetricsLogger mMetricsLogger = new MetricsLogger(); public interface AutoFillUiCallback { void authenticate(@NonNull IntentSender intent); @@ -123,7 +127,7 @@ public final class AutoFillUI { } hideSaveUiUiThread(); if (mFillUi != null) { - mFillUi.filter(filterText); + mFillUi.setFilterText(filterText); } }); } @@ -152,9 +156,17 @@ public final class AutoFillUI { * @param response the current fill response * @param anchorBounds bounds of the focused view * @param filterText text of the view to be filled + * @param packageName package name of the activity that is filled */ public void showFillUi(@NonNull AutofillId focusedId, @NonNull FillResponse response, - @NonNull Rect anchorBounds, @Nullable String filterText) { + @NonNull Rect anchorBounds, @Nullable String filterText, @NonNull String packageName) { + LogMaker log = (new LogMaker(MetricsProto.MetricsEvent.AUTOFILL_FILL_UI)) + .setPackageName(packageName) + .addTaggedData(MetricsProto.MetricsEvent.FIELD_AUTOFILL_FILTERTEXT_LEN, + filterText == null ? 0 : filterText.length()) + .addTaggedData(MetricsProto.MetricsEvent.FIELD_AUTOFILL_NUM_DATASETS, + response.getDatasets() == null ? 0 : response.getDatasets().size()); + mHandler.post(() -> { if (!hasCallback()) { return; @@ -164,6 +176,7 @@ public final class AutoFillUI { mWindowToken, anchorBounds, filterText, new FillUi.Callback() { @Override public void onResponsePicked(FillResponse response) { + log.setType(MetricsProto.MetricsEvent.TYPE_DETAIL); hideFillUiUiThread(); if (mCallback != null) { mCallback.authenticate(response.getAuthentication()); @@ -172,17 +185,25 @@ public final class AutoFillUI { @Override public void onDatasetPicked(Dataset dataset) { + log.setType(MetricsProto.MetricsEvent.TYPE_ACTION); hideFillUiUiThread(); if (mCallback != null) { mCallback.fill(dataset); } - // TODO(b/33197203): add MetricsLogger call } @Override public void onCanceled() { + log.setType(MetricsProto.MetricsEvent.TYPE_DISMISS); hideFillUiUiThread(); - // TODO(b/33197203): add MetricsLogger call + } + + @Override + public void onDestroy() { + if (log.getType() == MetricsProto.MetricsEvent.TYPE_UNKNOWN) { + log.setType(MetricsProto.MetricsEvent.TYPE_CLOSE); + } + mMetricsLogger.write(log); } }); mCallback.onEvent(focusedId, EVENT_INPUT_SHOWN); @@ -192,7 +213,16 @@ public final class AutoFillUI { /** * Shows the UI asking the user to save for autofill. */ - public void showSaveUi(@NonNull CharSequence providerLabel, @NonNull SaveInfo info) { + public void showSaveUi(@NonNull CharSequence providerLabel, @NonNull SaveInfo info, + @NonNull String packageName) { + int numIds = 0; + numIds += info.getRequiredIds() == null ? 0 : info.getRequiredIds().length; + numIds += info.getOptionalIds() == null ? 0 : info.getOptionalIds().length; + + LogMaker log = (new LogMaker(MetricsProto.MetricsEvent.AUTOFILL_SAVE_UI)) + .setPackageName(packageName).addTaggedData( + MetricsProto.MetricsEvent.FIELD_AUTOFILL_NUM_IDS, numIds); + mHandler.post(() -> { if (!hasCallback()) { return; @@ -202,16 +232,16 @@ public final class AutoFillUI { new SaveUi.OnSaveListener() { @Override public void onSave() { + log.setType(MetricsProto.MetricsEvent.TYPE_ACTION); hideSaveUiUiThread(); if (mCallback != null) { mCallback.save(); } - // TODO(b/33197203): add MetricsLogger call } @Override public void onCancel(IntentSender listener) { - // TODO(b/33197203): add MetricsLogger call + log.setType(MetricsProto.MetricsEvent.TYPE_DISMISS); hideSaveUiUiThread(); if (listener != null) { try { @@ -225,6 +255,14 @@ public final class AutoFillUI { mCallback.cancelSave(); } } + + @Override + public void onDestroy() { + if (log.getType() == MetricsProto.MetricsEvent.TYPE_UNKNOWN) { + log.setType(MetricsProto.MetricsEvent.TYPE_CLOSE); + } + mMetricsLogger.write(log); + } }, mSaveTimeoutMs); }); } @@ -247,11 +285,17 @@ public final class AutoFillUI { } public void dump(PrintWriter pw) { - pw.println("AufoFill UI"); + pw.println("Autofill UI"); final String prefix = " "; - pw.print(prefix); pw.print("showsFillUi: "); pw.println(mFillUi != null); + final String prefix2 = " "; pw.print(prefix); pw.print("showsSaveUi: "); pw.println(mSaveUi != null); pw.print(prefix); pw.print("save timeout: "); pw.println(mSaveTimeoutMs); + if (mFillUi != null) { + pw.print(prefix); pw.println("showsFillUi: true"); + mFillUi.dump(pw, prefix2); + } else { + pw.print(prefix); pw.println("showsFillUi: false"); + } } @android.annotation.UiThread diff --git a/services/autofill/java/com/android/server/autofill/ui/FillUi.java b/services/autofill/java/com/android/server/autofill/ui/FillUi.java index 3fdcd9e85876..a8c87523117e 100644 --- a/services/autofill/java/com/android/server/autofill/ui/FillUi.java +++ b/services/autofill/java/com/android/server/autofill/ui/FillUi.java @@ -39,6 +39,7 @@ import android.widget.ListView; import com.android.internal.R; import libcore.util.Objects; +import java.io.PrintWriter; import java.util.ArrayList; final class FillUi { @@ -50,6 +51,7 @@ final class FillUi { void onResponsePicked(@NonNull FillResponse response); void onDatasetPicked(@NonNull Dataset dataset); void onCanceled(); + void onDestroy(); } private final Rect mAnchorBounds = new Rect(); @@ -63,6 +65,7 @@ final class FillUi { private final @Nullable ArrayAdapter<ViewItem> mAdapter; private @Nullable String mFilterText; + private final String mAccessibilityTitle; private int mContentWidth; private int mContentHeight; @@ -76,6 +79,8 @@ final class FillUi { mAnchorBounds.set(anchorBounds); mCallback = callback; + mAccessibilityTitle = context.getString(R.string.autofill_picker_accessibility_title); + if (response.getAuthentication() != null) { mListView = null; mAdapter = null; @@ -113,8 +118,13 @@ final class FillUi { Slog.e(TAG, "Error inflating remote views", e); continue; } - items.add(new ViewItem(dataset, value.coerceToString() - .toLowerCase(), view)); + + String valueText = null; + if (value.isText()) { + valueText = value.getTextValue().toString().toLowerCase(); + } + + items.add(new ViewItem(dataset, valueText, view)); } } @@ -134,7 +144,13 @@ final class FillUi { mCallback.onDatasetPicked(vi.getDataset()); }); - filter(filterText); + if (filterText == null) { + mFilterText = null; + } else { + mFilterText = filterText.toLowerCase(); + } + + applyNewFilterText(); mWindow = new AnchoredWindow(windowToken, mListView); } } @@ -147,16 +163,8 @@ final class FillUi { } } - public void filter(@Nullable String filterText) { - throwIfDestroyed(); - if (mAdapter == null) { - return; - } - if (Objects.equal(mFilterText, filterText)) { - return; - } - mFilterText = filterText; - mAdapter.getFilter().filter(filterText, (count) -> { + private void applyNewFilterText() { + mAdapter.getFilter().filter(mFilterText, (count) -> { if (mDestroyed) { return; } @@ -176,8 +184,29 @@ final class FillUi { }); } + public void setFilterText(@Nullable String filterText) { + throwIfDestroyed(); + if (mAdapter == null) { + return; + } + + if (filterText == null) { + filterText = null; + } else { + filterText = filterText.toLowerCase(); + } + + if (Objects.equal(mFilterText, filterText)) { + return; + } + mFilterText = filterText; + + applyNewFilterText(); + } + public void destroy() { throwIfDestroyed(); + mCallback.onDestroy(); mWindow.hide(); mDestroyed = true; } @@ -235,7 +264,7 @@ final class FillUi { ViewItem(Dataset dataset, String value, View view) { mDataset = dataset; - mValue = value.toLowerCase(); + mValue = value; mView = view; } @@ -296,6 +325,7 @@ final class FillUi { public void show(int desiredWidth, int desiredHeight, Rect anchorBounds) { final WindowManager.LayoutParams params = new WindowManager.LayoutParams(); + params.setTitle("FillUi"); params.token = mActivityToken; params.type = WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG; @@ -304,6 +334,7 @@ final class FillUi { | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN; + params.accessibilityTitle = mAccessibilityTitle; mWm.getDefaultDisplay().getRealSize(mTempPoint); final int screenWidth = mTempPoint.x; @@ -354,4 +385,16 @@ final class FillUi { } } } + + public void dump(PrintWriter pw, String prefix) { + pw.print(prefix); pw.print("mAnchorBounds: "); pw.println(mAnchorBounds); + pw.print(prefix); pw.print("mCallback: "); pw.println(mCallback != null); + pw.print(prefix); pw.print("mListView: "); pw.println(mListView); + pw.print(prefix); pw.print("mAdapter: "); pw.println(mAdapter != null); + pw.print(prefix); pw.print("mFilterText: "); pw.println(mFilterText); + pw.print(prefix); pw.print("mAccessibilityTitle: "); pw.println(mAccessibilityTitle); + pw.print(prefix); pw.print("mContentWidth: "); pw.println(mContentWidth); + pw.print(prefix); pw.print("mContentHeight: "); pw.println(mContentHeight); + pw.print(prefix); pw.print("mDestroyed: "); pw.println(mDestroyed); + } } diff --git a/services/autofill/java/com/android/server/autofill/ui/Helper.java b/services/autofill/java/com/android/server/autofill/ui/Helper.java new file mode 100644 index 000000000000..996e4213ac44 --- /dev/null +++ b/services/autofill/java/com/android/server/autofill/ui/Helper.java @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.autofill.ui; + +final class Helper { + + static final boolean DEBUG = true; // TODO(b/33197203): set to false when stable + static final boolean VERBOSE = false; + private Helper() { + throw new UnsupportedOperationException("contains static members only"); + } +} diff --git a/services/autofill/java/com/android/server/autofill/ui/SaveUi.java b/services/autofill/java/com/android/server/autofill/ui/SaveUi.java index 3f409ade68ed..509351bf0858 100644 --- a/services/autofill/java/com/android/server/autofill/ui/SaveUi.java +++ b/services/autofill/java/com/android/server/autofill/ui/SaveUi.java @@ -16,6 +16,8 @@ package com.android.server.autofill.ui; +import static com.android.server.autofill.ui.Helper.DEBUG; + import android.annotation.NonNull; import android.app.Dialog; import android.content.Context; @@ -23,6 +25,7 @@ import android.content.IntentSender; import android.os.Handler; import android.service.autofill.SaveInfo; import android.text.format.DateUtils; +import android.util.Slog; import android.view.Gravity; import android.view.Window; import android.view.WindowManager; @@ -37,22 +40,66 @@ import com.android.server.UiThread; * Autofill Save Prompt */ final class SaveUi { + + private static final String TAG = "SaveUi"; + public interface OnSaveListener { void onSave(); void onCancel(IntentSender listener); + void onDestroy(); + } + + private class OneTimeListener implements OnSaveListener { + + private final OnSaveListener mRealListener; + private boolean mDone; + + OneTimeListener(OnSaveListener realListener) { + mRealListener = realListener; + } + + @Override + public void onSave() { + if (DEBUG) Slog.d(TAG, "onSave(): " + mDone); + if (mDone) { + return; + } + mDone = true; + mRealListener.onSave(); + } + + @Override + public void onCancel(IntentSender listener) { + if (DEBUG) Slog.d(TAG, "onCancel(): " + mDone); + if (mDone) { + return; + } + mDone = true; + mRealListener.onCancel(listener); + } + + @Override + public void onDestroy() { + if (DEBUG) Slog.d(TAG, "onDestroy(): " + mDone); + if (mDone) { + return; + } + mDone = true; + mRealListener.onDestroy(); + } } private final Handler mHandler = UiThread.getHandler(); private final @NonNull Dialog mDialog; - private final @NonNull OnSaveListener mListener; + private final @NonNull OneTimeListener mListener; private boolean mDestroyed; SaveUi(@NonNull Context context, @NonNull CharSequence providerLabel, @NonNull SaveInfo info, @NonNull OnSaveListener listener, int lifeTimeMs) { - mListener = listener; + mListener = new OneTimeListener(listener); final LayoutInflater inflater = LayoutInflater.from(context); final View view = inflater.inflate(R.layout.autofill_save, null); @@ -117,11 +164,17 @@ final class SaveUi { mDialog.show(); - mHandler.postDelayed(() -> mListener.onCancel(null), lifeTimeMs); + mHandler.postDelayed(() -> { + if (!mListener.mDone) { + mListener.onCancel(null); + Slog.d(TAG, "Save snackbar timed out after " + lifeTimeMs + "ms"); + } + }, lifeTimeMs); } void destroy() { throwIfDestroyed(); + mListener.onDestroy(); mHandler.removeCallbacksAndMessages(mListener); mDialog.dismiss(); mDestroyed = true; diff --git a/services/core/Android.mk b/services/core/Android.mk index 1864d3407cd7..794ece65b03d 100644 --- a/services/core/Android.mk +++ b/services/core/Android.mk @@ -23,7 +23,9 @@ LOCAL_JAVA_LIBRARIES := \ android.hardware.power@1.0-java \ android.hardware.tv.cec@1.0-java -LOCAL_STATIC_JAVA_LIBRARIES := tzdata_update2 \ +LOCAL_STATIC_JAVA_LIBRARIES := \ + tzdata_shared2 \ + tzdata_update2 \ android.hidl.base@1.0-java-static \ android.hardware.biometrics.fingerprint@2.1-java-static \ diff --git a/services/core/java/com/android/server/BackgroundDexOptJobService.java b/services/core/java/com/android/server/BackgroundDexOptJobService.java deleted file mode 100644 index 69e6ac50fa8a..000000000000 --- a/services/core/java/com/android/server/BackgroundDexOptJobService.java +++ /dev/null @@ -1,306 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server; - -import static com.android.server.pm.PackageManagerService.DEBUG_DEXOPT; - -import android.app.job.JobInfo; -import android.app.job.JobParameters; -import android.app.job.JobScheduler; -import android.app.job.JobService; -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.os.BatteryManager; -import android.os.Environment; -import android.os.ServiceManager; -import android.os.storage.StorageManager; -import android.util.ArraySet; -import android.util.Log; -import com.android.server.pm.PackageManagerService; - -import java.io.File; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.TimeUnit; - -public class BackgroundDexOptJobService extends JobService { - private static final String TAG = "BackgroundDexOptJobService"; - - private static final boolean DEBUG = false; - - private static final int JOB_IDLE_OPTIMIZE = 800; - private static final int JOB_POST_BOOT_UPDATE = 801; - - private static final long IDLE_OPTIMIZATION_PERIOD = DEBUG - ? TimeUnit.MINUTES.toMillis(1) - : TimeUnit.DAYS.toMillis(1); - - private static ComponentName sDexoptServiceName = new ComponentName( - "android", - BackgroundDexOptJobService.class.getName()); - - /** - * Set of failed packages remembered across job runs. - */ - static final ArraySet<String> sFailedPackageNames = new ArraySet<String>(); - - /** - * Atomics set to true if the JobScheduler requests an abort. - */ - final AtomicBoolean mAbortPostBootUpdate = new AtomicBoolean(false); - final AtomicBoolean mAbortIdleOptimization = new AtomicBoolean(false); - - /** - * Atomic set to true if one job should exit early because another job was started. - */ - final AtomicBoolean mExitPostBootUpdate = new AtomicBoolean(false); - - private final File mDataDir = Environment.getDataDirectory(); - - public static void schedule(Context context) { - JobScheduler js = (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE); - - // Schedule a one-off job which scans installed packages and updates - // out-of-date oat files. - js.schedule(new JobInfo.Builder(JOB_POST_BOOT_UPDATE, sDexoptServiceName) - .setMinimumLatency(TimeUnit.MINUTES.toMillis(1)) - .setOverrideDeadline(TimeUnit.MINUTES.toMillis(1)) - .build()); - - // Schedule a daily job which scans installed packages and compiles - // those with fresh profiling data. - js.schedule(new JobInfo.Builder(JOB_IDLE_OPTIMIZE, sDexoptServiceName) - .setRequiresDeviceIdle(true) - .setRequiresCharging(true) - .setPeriodic(IDLE_OPTIMIZATION_PERIOD) - .build()); - - if (DEBUG_DEXOPT) { - Log.i(TAG, "Jobs scheduled"); - } - } - - public static void notifyPackageChanged(String packageName) { - // The idle maintanance job skips packages which previously failed to - // compile. The given package has changed and may successfully compile - // now. Remove it from the list of known failing packages. - synchronized (sFailedPackageNames) { - sFailedPackageNames.remove(packageName); - } - } - - // Returns the current battery level as a 0-100 integer. - private int getBatteryLevel() { - IntentFilter filter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED); - Intent intent = registerReceiver(null, filter); - int level = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1); - int scale = intent.getIntExtra(BatteryManager.EXTRA_SCALE, -1); - - if (level < 0 || scale <= 0) { - // Battery data unavailable. This should never happen, so assume the worst. - return 0; - } - - return (100 * level / scale); - } - - private long getLowStorageThreshold() { - @SuppressWarnings("deprecation") - final long lowThreshold = StorageManager.from(this).getStorageLowBytes(mDataDir); - if (lowThreshold == 0) { - Log.e(TAG, "Invalid low storage threshold"); - } - - return lowThreshold; - } - - private boolean runPostBootUpdate(final JobParameters jobParams, - final PackageManagerService pm, final ArraySet<String> pkgs) { - if (mExitPostBootUpdate.get()) { - // This job has already been superseded. Do not start it. - return false; - } - new Thread("BackgroundDexOptService_PostBootUpdate") { - @Override - public void run() { - postBootUpdate(jobParams, pm, pkgs); - } - - }.start(); - return true; - } - - private void postBootUpdate(JobParameters jobParams, PackageManagerService pm, - ArraySet<String> pkgs) { - // Load low battery threshold from the system config. This is a 0-100 integer. - final int lowBatteryThreshold = getResources().getInteger( - com.android.internal.R.integer.config_lowBatteryWarningLevel); - final long lowThreshold = getLowStorageThreshold(); - - mAbortPostBootUpdate.set(false); - - for (String pkg : pkgs) { - if (mAbortPostBootUpdate.get()) { - // JobScheduler requested an early abort. - return; - } - if (mExitPostBootUpdate.get()) { - // Different job, which supersedes this one, is running. - break; - } - if (getBatteryLevel() < lowBatteryThreshold) { - // Rather bail than completely drain the battery. - break; - } - long usableSpace = mDataDir.getUsableSpace(); - if (usableSpace < lowThreshold) { - // Rather bail than completely fill up the disk. - Log.w(TAG, "Aborting background dex opt job due to low storage: " + - usableSpace); - break; - } - - if (DEBUG_DEXOPT) { - Log.i(TAG, "Updating package " + pkg); - } - - // Update package if needed. Note that there can be no race between concurrent - // jobs because PackageDexOptimizer.performDexOpt is synchronized. - - // checkProfiles is false to avoid merging profiles during boot which - // might interfere with background compilation (b/28612421). - // Unfortunately this will also means that "pm.dexopt.boot=speed-profile" will - // behave differently than "pm.dexopt.bg-dexopt=speed-profile" but that's a - // trade-off worth doing to save boot time work. - pm.performDexOpt(pkg, - /* checkProfiles */ false, - PackageManagerService.REASON_BOOT, - /* force */ false); - } - // Ran to completion, so we abandon our timeslice and do not reschedule. - jobFinished(jobParams, /* reschedule */ false); - } - - private boolean runIdleOptimization(final JobParameters jobParams, - final PackageManagerService pm, final ArraySet<String> pkgs) { - new Thread("BackgroundDexOptService_IdleOptimization") { - @Override - public void run() { - idleOptimization(jobParams, pm, pkgs); - } - }.start(); - return true; - } - - private void idleOptimization(JobParameters jobParams, PackageManagerService pm, - ArraySet<String> pkgs) { - Log.i(TAG, "Performing idle optimizations"); - // If post-boot update is still running, request that it exits early. - mExitPostBootUpdate.set(true); - - mAbortIdleOptimization.set(false); - - final long lowThreshold = getLowStorageThreshold(); - for (String pkg : pkgs) { - if (mAbortIdleOptimization.get()) { - // JobScheduler requested an early abort. - return; - } - - synchronized (sFailedPackageNames) { - if (sFailedPackageNames.contains(pkg)) { - // Skip previously failing package - continue; - } - } - - long usableSpace = mDataDir.getUsableSpace(); - if (usableSpace < lowThreshold) { - // Rather bail than completely fill up the disk. - Log.w(TAG, "Aborting background dex opt job due to low storage: " + - usableSpace); - break; - } - - // Conservatively add package to the list of failing ones in case performDexOpt - // never returns. - synchronized (sFailedPackageNames) { - sFailedPackageNames.add(pkg); - } - // Optimize package if needed. Note that there can be no race between - // concurrent jobs because PackageDexOptimizer.performDexOpt is synchronized. - if (pm.performDexOpt(pkg, - /* checkProfiles */ true, - PackageManagerService.REASON_BACKGROUND_DEXOPT, - /* force */ false)) { - // Dexopt succeeded, remove package from the list of failing ones. - synchronized (sFailedPackageNames) { - sFailedPackageNames.remove(pkg); - } - } - } - // Ran to completion, so we abandon our timeslice and do not reschedule. - jobFinished(jobParams, /* reschedule */ false); - } - - @Override - public boolean onStartJob(JobParameters params) { - if (DEBUG_DEXOPT) { - Log.i(TAG, "onStartJob"); - } - - // NOTE: PackageManagerService.isStorageLow uses a different set of criteria from - // the checks above. This check is not "live" - the value is determined by a background - // restart with a period of ~1 minute. - PackageManagerService pm = (PackageManagerService)ServiceManager.getService("package"); - if (pm.isStorageLow()) { - if (DEBUG_DEXOPT) { - Log.i(TAG, "Low storage, skipping this run"); - } - return false; - } - - final ArraySet<String> pkgs = pm.getOptimizablePackages(); - if (pkgs == null || pkgs.isEmpty()) { - if (DEBUG_DEXOPT) { - Log.i(TAG, "No packages to optimize"); - } - return false; - } - - if (params.getJobId() == JOB_POST_BOOT_UPDATE) { - return runPostBootUpdate(params, pm, pkgs); - } else { - return runIdleOptimization(params, pm, pkgs); - } - } - - @Override - public boolean onStopJob(JobParameters params) { - if (DEBUG_DEXOPT) { - Log.i(TAG, "onStopJob"); - } - - if (params.getJobId() == JOB_POST_BOOT_UPDATE) { - mAbortPostBootUpdate.set(true); - } else { - mAbortIdleOptimization.set(true); - } - return false; - } -} diff --git a/services/core/java/com/android/server/FontManagerService.java b/services/core/java/com/android/server/FontManagerService.java index 593c3220fdb5..55a945a6fce1 100644 --- a/services/core/java/com/android/server/FontManagerService.java +++ b/services/core/java/com/android/server/FontManagerService.java @@ -71,11 +71,8 @@ public class FontManagerService extends IFontManager.Stub { return null; } - final int size = config.getFamilies().size(); - for (int i = 0; i < size; ++i) { - FontConfig.Family family = config.getFamilies().get(i); - for (int j = 0; j < family.getFonts().size(); ++j) { - FontConfig.Font font = family.getFonts().get(j); + for (FontConfig.Family family : config.getFamilies()) { + for (FontConfig.Font font : family.getFonts()) { File fontFile = new File(font.getFontName()); try { font.setFd(ParcelFileDescriptor.open( diff --git a/services/core/java/com/android/server/LockSettingsService.java b/services/core/java/com/android/server/LockSettingsService.java index bcee2c1bd9ea..c946d0937017 100644 --- a/services/core/java/com/android/server/LockSettingsService.java +++ b/services/core/java/com/android/server/LockSettingsService.java @@ -146,6 +146,7 @@ public class LockSettingsService extends ILockSettings.Stub { private final LockPatternUtils mLockPatternUtils; private final NotificationManager mNotificationManager; private final UserManager mUserManager; + private final DevicePolicyManager mDevicePolicyManager; private final IActivityManager mActivityManager; private final KeyStore mKeyStore; @@ -333,6 +334,10 @@ public class LockSettingsService extends ILockSettings.Stub { return (UserManager) mContext.getSystemService(Context.USER_SERVICE); } + public DevicePolicyManager getDevicePolicyManager() { + return (DevicePolicyManager) mContext.getSystemService(Context.DEVICE_POLICY_SERVICE); + } + public KeyStore getKeyStore() { return KeyStore.getInstance(); } @@ -380,6 +385,7 @@ public class LockSettingsService extends ILockSettings.Stub { mStorage = injector.getStorage(); mNotificationManager = injector.getNotificationManager(); mUserManager = injector.getUserManager(); + mDevicePolicyManager = injector.getDevicePolicyManager(); mStrongAuthTracker = injector.getStrongAuthTracker(); mStrongAuthTracker.register(mStrongAuth); @@ -1482,7 +1488,7 @@ public class LockSettingsService extends ILockSettings.Stub { return VerifyCredentialResponse.OK; } - if (TextUtils.isEmpty(credential)) { + if (storedHash == null || TextUtils.isEmpty(credential)) { return VerifyCredentialResponse.ERROR; } @@ -2015,14 +2021,17 @@ public class LockSettingsService extends ILockSettings.Stub { } } long handle = getSyntheticPasswordHandleLocked(userId); - AuthenticationToken auth = mSpManager.unwrapPasswordBasedSyntheticPassword( - getGateKeeperService(), handle, savedCredential, userId).authToken; + AuthenticationResult authResult = mSpManager.unwrapPasswordBasedSyntheticPassword( + getGateKeeperService(), handle, savedCredential, userId); + VerifyCredentialResponse response = authResult.gkResponse; + AuthenticationToken auth = authResult.authToken; if (auth != null) { // We are performing a trusted credential change i.e. a correct existing credential // is provided setLockCredentialWithAuthTokenLocked(credential, credentialType, auth, userId); mSpManager.destroyPasswordBasedSyntheticPassword(handle, userId); - } else { + } else if (response != null + && response.getResponseCode() == VerifyCredentialResponse.RESPONSE_ERROR){ // We are performing an untrusted credential change i.e. by DevicePolicyManager. // So provision a new SP and SID. This would invalidate existing escrow tokens. // Still support this for now but this flow will be removed in the next release. @@ -2031,6 +2040,10 @@ public class LockSettingsService extends ILockSettings.Stub { initializeSyntheticPasswordLocked(null, credential, credentialType, userId); synchronizeUnifiedWorkChallengeForProfiles(userId, null); mSpManager.destroyPasswordBasedSyntheticPassword(handle, userId); + } else /* response == null || responseCode == VerifyCredentialResponse.RESPONSE_RETRY */ { + Slog.w(TAG, "spBasedSetLockCredentialInternalLocked: " + + (response != null ? "rate limit exceeded" : "failed")); + return; } notifyActivePasswordMetricsAvailable(credential, userId); @@ -2042,7 +2055,7 @@ public class LockSettingsService extends ILockSettings.Stub { if (DEBUG) Slog.d(TAG, "addEscrowToken: user=" + userId); synchronized (mSpManager) { enableSyntheticPasswordLocked(); - // Migrate to synthetic password based credentials if ther user has no password, + // Migrate to synthetic password based credentials if the user has no password, // the token can then be activated immediately. AuthenticationToken auth = null; if (!isUserSecure(userId)) { @@ -2201,22 +2214,20 @@ public class LockSettingsService extends ILockSettings.Stub { Slog.i(TAG, "Managed profile can have escrow token"); return; } - DevicePolicyManager dpm = (DevicePolicyManager) - mContext.getSystemService(Context.DEVICE_POLICY_SERVICE); // Devices with Device Owner should have escrow enabled on all users. - if (dpm.getDeviceOwnerComponentOnAnyUser() != null) { + if (mDevicePolicyManager.getDeviceOwnerComponentOnAnyUser() != null) { Slog.i(TAG, "Corp-owned device can have escrow token"); return; } // We could also have a profile owner on the given (non-managed) user for unicorn cases - if (dpm.getProfileOwnerAsUser(userId) != null) { + if (mDevicePolicyManager.getProfileOwnerAsUser(userId) != null) { Slog.i(TAG, "User with profile owner can have escrow token"); return; } // If the device is yet to be provisioned (still in SUW), there is still // a chance that Device Owner will be set on the device later, so postpone // disabling escrow token for now. - if (!dpm.isDeviceProvisioned()) { + if (!mDevicePolicyManager.isDeviceProvisioned()) { Slog.i(TAG, "Postpone disabling escrow tokens until device is provisioned"); return; } diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java index 3667ecdec472..8e6310fdacbb 100644 --- a/services/core/java/com/android/server/StorageManagerService.java +++ b/services/core/java/com/android/server/StorageManagerService.java @@ -2721,7 +2721,7 @@ class StorageManagerService extends IStorageManager.Stub */ @Override public int getPasswordType() { - mContext.enforceCallingOrSelfPermission(Manifest.permission.STORAGE_INTERNAL, + mContext.enforceCallingOrSelfPermission(Manifest.permission.CRYPT_KEEPER, "no permission to access the crypt keeper"); waitForReady(); @@ -2747,7 +2747,7 @@ class StorageManagerService extends IStorageManager.Stub */ @Override public void setField(String field, String contents) throws RemoteException { - mContext.enforceCallingOrSelfPermission(Manifest.permission.STORAGE_INTERNAL, + mContext.enforceCallingOrSelfPermission(Manifest.permission.CRYPT_KEEPER, "no permission to access the crypt keeper"); waitForReady(); @@ -2767,7 +2767,7 @@ class StorageManagerService extends IStorageManager.Stub */ @Override public String getField(String field) throws RemoteException { - mContext.enforceCallingOrSelfPermission(Manifest.permission.STORAGE_INTERNAL, + mContext.enforceCallingOrSelfPermission(Manifest.permission.CRYPT_KEEPER, "no permission to access the crypt keeper"); waitForReady(); @@ -2793,7 +2793,7 @@ class StorageManagerService extends IStorageManager.Stub */ @Override public boolean isConvertibleToFBE() throws RemoteException { - mContext.enforceCallingOrSelfPermission(Manifest.permission.STORAGE_INTERNAL, + mContext.enforceCallingOrSelfPermission(Manifest.permission.CRYPT_KEEPER, "no permission to access the crypt keeper"); waitForReady(); @@ -2809,7 +2809,7 @@ class StorageManagerService extends IStorageManager.Stub @Override public String getPassword() throws RemoteException { - mContext.enforceCallingOrSelfPermission(Manifest.permission.STORAGE_INTERNAL, + mContext.enforceCallingOrSelfPermission(Manifest.permission.CRYPT_KEEPER, "only keyguard can retrieve password"); if (!isReady()) { @@ -2834,7 +2834,7 @@ class StorageManagerService extends IStorageManager.Stub @Override public void clearPassword() throws RemoteException { - mContext.enforceCallingOrSelfPermission(Manifest.permission.STORAGE_INTERNAL, + mContext.enforceCallingOrSelfPermission(Manifest.permission.CRYPT_KEEPER, "only keyguard can clear password"); if (!isReady()) { diff --git a/services/core/java/com/android/server/SyntheticPasswordManager.java b/services/core/java/com/android/server/SyntheticPasswordManager.java index 62678801a9a2..2517613c03e0 100644 --- a/services/core/java/com/android/server/SyntheticPasswordManager.java +++ b/services/core/java/com/android/server/SyntheticPasswordManager.java @@ -526,7 +526,7 @@ public class SyntheticPasswordManager { * RESPONSE_OK, since user authentication failures are detected earlier when trying to * decrypt SP. */ - public VerifyCredentialResponse verifyChallenge(IGateKeeperService gatekeeper, + public @Nullable VerifyCredentialResponse verifyChallenge(IGateKeeperService gatekeeper, @NonNull AuthenticationToken auth, long challenge, int userId) throws RemoteException { byte[] spHandle = loadSyntheticPasswordHandle(userId); if (spHandle == null) { diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java index 0d5a3e00618b..f954f75aa05e 100644 --- a/services/core/java/com/android/server/accounts/AccountManagerService.java +++ b/services/core/java/com/android/server/accounts/AccountManagerService.java @@ -35,7 +35,6 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityManager; import android.app.ActivityThread; -import android.app.AppGlobals; import android.app.AppOpsManager; import android.app.INotificationManager; import android.app.Notification; @@ -64,7 +63,6 @@ import android.content.pm.Signature; import android.content.pm.UserInfo; import android.database.Cursor; import android.database.sqlite.SQLiteStatement; -import android.net.Uri; import android.os.Binder; import android.os.Bundle; import android.os.Environment; @@ -80,7 +78,6 @@ import android.os.StrictMode; import android.os.SystemClock; import android.os.UserHandle; import android.os.UserManager; -import android.os.storage.StorageManager; import android.text.TextUtils; import android.util.Log; import android.util.Pair; @@ -113,7 +110,6 @@ import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; -import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.HashSet; @@ -158,6 +154,11 @@ public class AccountManagerService public void onUnlockUser(int userHandle) { mService.onUnlockUser(userHandle); } + + @Override + public void onCleanupUser(int userHandle) { + mService.onCleanupUser(userHandle); + } } final Context mContext; @@ -303,18 +304,6 @@ public class AccountManagerService } }, intentFilter); - IntentFilter userFilter = new IntentFilter(); - userFilter.addAction(Intent.ACTION_USER_REMOVED); - mContext.registerReceiverAsUser(new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - String action = intent.getAction(); - if (Intent.ACTION_USER_REMOVED.equals(action)) { - onUserRemoved(intent); - } - } - }, UserHandle.ALL, userFilter, null, null); - injector.addLocalService(new AccountManagerInternalImpl()); // Need to cancel account request notifications if the update/install can access the account @@ -1133,16 +1122,12 @@ public class AccountManagerService } } - private void onUserRemoved(Intent intent) { - int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1); - if (userId < 1) return; - + private void onCleanupUser(int userId) { + Log.i(TAG, "onCleanupUser " + userId); UserAccounts accounts; - boolean userUnlocked; synchronized (mUsers) { accounts = mUsers.get(userId); mUsers.remove(userId); - userUnlocked = mLocalUnlockedUsers.get(userId); mLocalUnlockedUsers.delete(userId); } if (accounts != null) { @@ -1150,18 +1135,6 @@ public class AccountManagerService accounts.accountsDb.close(); } } - Log.i(TAG, "Removing database files for user " + userId); - File dbFile = new File(mInjector.getDeDatabaseName(userId)); - - AccountsDb.deleteDbFileWarnIfFailed(dbFile); - // Remove CE file if user is unlocked, or FBE is not enabled - boolean fbeEnabled = StorageManager.isFileEncryptedNativeOrEmulated(); - if (!fbeEnabled || userUnlocked) { - File ceDb = new File(mInjector.getCeDatabaseName(userId)); - if (ceDb.exists()) { - AccountsDb.deleteDbFileWarnIfFailed(ceDb); - } - } } @VisibleForTesting @@ -4003,12 +3976,12 @@ public class AccountManagerService public Account[] getAccountsAsUser(String type, int userId, String opPackageName) { int callingUid = Binder.getCallingUid(); mAppOpsManager.checkPackage(callingUid, opPackageName); - return getAccountsAsUser(type, userId, opPackageName /* callingPackage */, -1, + return getAccountsAsUserForPackage(type, userId, opPackageName /* callingPackage */, -1, opPackageName, false /* includeUserManagedNotVisible */); } @NonNull - private Account[] getAccountsAsUser( + private Account[] getAccountsAsUserForPackage( String type, int userId, String callingPackage, @@ -4061,7 +4034,7 @@ public class AccountManagerService return getAccountsInternal( accounts, callingUid, - callingPackage, + opPackageName, visibleAccountTypes, includeUserManagedNotVisible); } finally { @@ -4178,7 +4151,7 @@ public class AccountManagerService throw new SecurityException("getAccountsForPackage() called from unauthorized uid " + callingUid + " with uid=" + uid); } - return getAccountsAsUser(null, UserHandle.getCallingUserId(), packageName, uid, + return getAccountsAsUserForPackage(null, UserHandle.getCallingUserId(), packageName, uid, opPackageName, true /* includeUserManagedNotVisible */); } @@ -4197,11 +4170,10 @@ public class AccountManagerService return EMPTY_ACCOUNT_ARRAY; } if (!UserHandle.isSameApp(callingUid, Process.SYSTEM_UID) - && !isAccountManagedByCaller(type, callingUid, userId)) { - return EMPTY_ACCOUNT_ARRAY; + && (type != null && !isAccountManagedByCaller(type, callingUid, userId))) { + return EMPTY_ACCOUNT_ARRAY; } - - return getAccountsAsUser(type, userId, + return getAccountsAsUserForPackage(type, userId, packageName, packageUid, opPackageName, true /* includeUserManagedNotVisible */); } @@ -4363,7 +4335,7 @@ public class AccountManagerService * security policy. * * In particular we want to make sure that the Authenticator doesn't try to trick users - * into launching aribtrary intents on the device via by tricking to click authenticator + * into launching arbitrary intents on the device via by tricking to click authenticator * supplied entries in the system Settings app. */ protected void checkKeyIntent( @@ -4375,12 +4347,9 @@ public class AccountManagerService ResolveInfo resolveInfo = pm.resolveActivityAsUser(intent, 0, mAccounts.userId); ActivityInfo targetActivityInfo = resolveInfo.activityInfo; int targetUid = targetActivityInfo.applicationInfo.uid; - if (!GrantCredentialsPermissionActivity.class.getName().equals( - targetActivityInfo.getClass().getName()) - && !CantAddAccountActivity.class - .equals(targetActivityInfo.getClass().getName()) - && PackageManager.SIGNATURE_MATCH != pm.checkSignatures(authUid, - targetUid)) { + if (!isExportedSystemActivity(targetActivityInfo) + && (PackageManager.SIGNATURE_MATCH != pm.checkSignatures(authUid, + targetUid))) { String pkgName = targetActivityInfo.packageName; String activityName = targetActivityInfo.name; String tmpl = "KEY_INTENT resolved to an Activity (%s) in a package (%s) that " @@ -4393,6 +4362,13 @@ public class AccountManagerService } } + private boolean isExportedSystemActivity(ActivityInfo activityInfo) { + String className = activityInfo.name; + return "android".equals(activityInfo.packageName) && + (GrantCredentialsPermissionActivity.class.getName().equals(className) + || CantAddAccountActivity.class.getName().equals(className)); + } + private void close() { synchronized (mSessions) { if (mSessions.remove(toString()) == null) { @@ -5371,10 +5347,13 @@ public class AccountManagerService @NonNull private Account[] filterAccounts(UserAccounts accounts, Account[] unfiltered, int callingUid, String callingPackage, boolean includeManagedNotVisible) { - // filter based on visibility. + String visibilityFilterPackage = callingPackage; + if (visibilityFilterPackage == null) { + visibilityFilterPackage = getPackageNameForUid(callingUid); + } Map<Account, Integer> firstPass = new LinkedHashMap<>(); for (Account account : unfiltered) { - int visibility = resolveAccountVisibility(account, callingPackage, accounts); + int visibility = resolveAccountVisibility(account, visibilityFilterPackage, accounts); if ((visibility == AccountManager.VISIBILITY_VISIBLE || visibility == AccountManager.VISIBILITY_USER_MANAGED_VISIBLE) || (includeManagedNotVisible @@ -5394,7 +5373,7 @@ public class AccountManagerService @NonNull private Map<Account, Integer> filterSharedAccounts(UserAccounts userAccounts, @NonNull Map<Account, Integer> unfiltered, int callingUid, - String callingPackage) { + @Nullable String callingPackage) { // first part is to filter shared accounts. // unfiltered type check is not necessary. if (getUserManager() == null || userAccounts == null || userAccounts.userId < 0 @@ -5474,7 +5453,7 @@ public class AccountManagerService */ @NonNull protected Account[] getAccountsFromCacheLocked(UserAccounts userAccounts, String accountType, - int callingUid, String callingPackage, boolean includeManagedNotVisible) { + int callingUid, @Nullable String callingPackage, boolean includeManagedNotVisible) { if (callingPackage == null) { callingPackage = getPackageNameForUid(callingUid); } diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 6bf77ae80b44..55d661cfb534 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -6691,16 +6691,15 @@ public class ActivityManagerService extends IActivityManager.Stub : new ProfilerInfo(profileFile, profileFd, samplingInterval, profileAutoStop, profileStreamingOutput); - // We deprecated Build.SERIAL and only apps that target pre NMR1 - // SDK can see it. Since access to the serial is now behind a - // permission we push down the value. + // We deprecated Build.SERIAL and it is not accessible to + // apps that target the v2 security sandbox. Since access to + // the serial is now behind a permission we push down the value. String buildSerial = Build.UNKNOWN; - // TODO: SHTOPSHIP Uncomment the check when clients migrate -// if (appInfo.targetSdkVersion <= Build.VERSION_CODES.N_MR1) { + if (appInfo.targetSandboxVersion != 2) { buildSerial = IDeviceIdentifiersPolicyService.Stub.asInterface( ServiceManager.getService(Context.DEVICE_IDENTIFIERS_SERVICE)) .getSerial(); -// } + } // Check if this is a secondary process that should be incorporated into some // currently active instrumentation. (Note we do this AFTER all of the profiling diff --git a/services/core/java/com/android/server/am/ActivityMetricsLogger.java b/services/core/java/com/android/server/am/ActivityMetricsLogger.java index ebbce0227b3e..918747b37076 100644 --- a/services/core/java/com/android/server/am/ActivityMetricsLogger.java +++ b/services/core/java/com/android/server/am/ActivityMetricsLogger.java @@ -251,7 +251,8 @@ class ActivityMetricsLogger { * ActivityManagerInternal.APP_TRANSITION_* reasons. */ void notifyTransitionStarting(SparseIntArray stackIdReasons) { - if (!isAnyTransitionActive() || mLoggedTransitionStarting) { + // TODO (b/36339388): Figure out why stackIdReasons can be null + if (stackIdReasons == null || !isAnyTransitionActive() || mLoggedTransitionStarting) { return; } mCurrentTransitionDelayMs = calculateCurrentDelay(); diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java index 2e26bedd50f8..2b2471b28aa5 100644 --- a/services/core/java/com/android/server/am/ActivityRecord.java +++ b/services/core/java/com/android/server/am/ActivityRecord.java @@ -1953,11 +1953,6 @@ final class ActivityRecord implements AppWindowContainerListener { } void setRequestedOrientation(int requestedOrientation) { - if (task != null && (!task.mFullscreen || !task.getStack().mFullscreen)) { - // Fixed screen orientation isn't supported when activities aren't in full screen mode. - return; - } - final int displayId = getDisplayId(); final Configuration displayConfig = mStackSupervisor.getDisplayOverrideConfiguration(displayId); diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java index 2d91cadbd586..2885e663af3f 100644 --- a/services/core/java/com/android/server/am/ActivityStack.java +++ b/services/core/java/com/android/server/am/ActivityStack.java @@ -66,6 +66,7 @@ import static com.android.server.am.ActivityRecord.APPLICATION_ACTIVITY_TYPE; import static com.android.server.am.ActivityRecord.ASSISTANT_ACTIVITY_TYPE; import static com.android.server.am.ActivityRecord.HOME_ACTIVITY_TYPE; import static com.android.server.am.ActivityStackSupervisor.FindTaskResult; +import static com.android.server.am.ActivityStackSupervisor.ON_TOP; import static com.android.server.am.ActivityStackSupervisor.PRESERVE_WINDOWS; import static com.android.server.wm.AppTransition.TRANSIT_ACTIVITY_CLOSE; import static com.android.server.wm.AppTransition.TRANSIT_ACTIVITY_OPEN; @@ -505,7 +506,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai } onParentChanged(); - activityDisplay.attachStack(this, onTop); + activityDisplay.attachStack(this, findStackInsertIndex(onTop)); if (mStackId == DOCKED_STACK_ID) { // If we created a docked stack we want to resize it so it resizes all other stacks // in the system. @@ -799,16 +800,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai } mStacks.remove(this); - int addIndex = mStacks.size(); - if (addIndex > 0) { - final ActivityStack topStack = mStacks.get(addIndex - 1); - if (StackId.isAlwaysOnTop(topStack.mStackId) && topStack != this) { - // If the top stack is always on top, we move this stack just below it. - addIndex--; - } - } - - mStacks.add(addIndex, this); + mStacks.add(findStackInsertIndex(ON_TOP), this); mStackSupervisor.setFocusStackUnchecked(reason, this); if (task != null) { insertTaskAtTop(task, null); @@ -841,6 +833,25 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai } } + /** + * @return the index to insert a new stack into, taking the always-on-top stacks into account. + */ + private int findStackInsertIndex(boolean onTop) { + if (onTop) { + int addIndex = mStacks.size(); + if (addIndex > 0) { + final ActivityStack topStack = mStacks.get(addIndex - 1); + if (StackId.isAlwaysOnTop(topStack.mStackId) && topStack != this) { + // If the top stack is always on top, we move this stack just below it. + addIndex--; + } + } + return addIndex; + } else { + return 0; + } + } + boolean isFocusable() { if (StackId.canReceiveKeys(mStackId)) { return true; @@ -1573,9 +1584,9 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai // If the assistant stack is focused and translucent, then the docked stack is always // visible - if (topStack.isAssistantStack() - && topStack.isStackTranslucent(starting, DOCKED_STACK_ID)) { - return STACK_VISIBLE; + if (topStack.isAssistantStack()) { + return (topStack.isStackTranslucent(starting, DOCKED_STACK_ID)) ? STACK_VISIBLE + : STACK_INVISIBLE; } // Otherwise, the docked stack is always visible, except in the case where the top @@ -3205,7 +3216,8 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai } // Move the home stack to the top if this stack is fullscreen or there is no // other visible stack. - if (mStackSupervisor.moveHomeStackTaskToTop(myReason)) { + if (task.isOverHomeStack() && + mStackSupervisor.moveHomeStackTaskToTop(myReason)) { // Activity focus was already adjusted. Nothing else to do... return; } diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java index 2ae815e305c1..42efe0b5d8e0 100644 --- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java +++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java @@ -4654,14 +4654,10 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D mDisplayId = display.getDisplayId(); } - void attachStack(ActivityStack stack, boolean onTop) { + void attachStack(ActivityStack stack, int position) { if (DEBUG_STACK) Slog.v(TAG_STACK, "attachStack: attaching " + stack - + " to displayId=" + mDisplayId + " onTop=" + onTop); - if (onTop) { - mStacks.add(stack); - } else { - mStacks.add(0, stack); - } + + " to displayId=" + mDisplayId + " position=" + position); + mStacks.add(position, stack); } void detachStack(ActivityStack stack) { diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java index 278789568b85..dd3d4e0d1be2 100644 --- a/services/core/java/com/android/server/am/BroadcastQueue.java +++ b/services/core/java/com/android/server/am/BroadcastQueue.java @@ -1152,17 +1152,14 @@ public final class BroadcastQueue { skip = true; } } - final boolean visibleToInstantApps = - (r.intent.getFlags() & Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS) != 0; if (!skip && info.activityInfo.applicationInfo.isInstantApp() - && !visibleToInstantApps && r.callingUid != info.activityInfo.applicationInfo.uid) { Slog.w(TAG, "Instant App Denial: receiving " + r.intent + " to " + component.flattenToShortString() + " due to sender " + r.callerPackage + " (uid " + r.callingUid + ")" - + " not specifying FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS"); + + " Instant Apps do not support manifest receivers"); skip = true; } if (!skip && r.callerInstantApp diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java index c0c433e5e1b9..99fe418e66e1 100644 --- a/services/core/java/com/android/server/am/TaskRecord.java +++ b/services/core/java/com/android/server/am/TaskRecord.java @@ -2013,10 +2013,6 @@ final class TaskRecord extends ConfigurationContainer implements TaskWindowConta final Configuration parentConfig = getParent().getConfiguration(); final float density = parentConfig.densityDpi * DisplayMetrics.DENSITY_DEFAULT_SCALE; - // TODO: Orientation? - config.orientation = (config.screenWidthDp <= config.screenHeightDp) - ? Configuration.ORIENTATION_PORTRAIT - : Configuration.ORIENTATION_LANDSCAPE; if (mStack != null) { final StackWindowController stackController = mStack.getWindowContainerController(); stackController.adjustConfigurationForBounds(bounds, insetBounds, @@ -2030,6 +2026,10 @@ final class TaskRecord extends ConfigurationContainer implements TaskWindowConta Slog.wtf(TAG, "Expected stack when caclulating override config"); } + config.orientation = (config.screenWidthDp <= config.screenHeightDp) + ? Configuration.ORIENTATION_PORTRAIT + : Configuration.ORIENTATION_LANDSCAPE; + // For calculating screen layout, we need to use the non-decor inset screen area for the // calculation for compatibility reasons, i.e. screen area without system bars that could // never go away in Honeycomb. diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index 79b99a33d6d3..333d27bd5360 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -1241,6 +1241,13 @@ public class AudioService extends IAudioService.Stub /** @see AudioManager#adjustStreamVolume(int, int, int) */ public void adjustStreamVolume(int streamType, int direction, int flags, String callingPackage) { + if ( streamType == AudioManager.STREAM_ACCESSIBILITY + && (PackageManager.PERMISSION_GRANTED != mContext.checkCallingOrSelfPermission( + android.Manifest.permission.BIND_ACCESSIBILITY_SERVICE))) { + Log.w(TAG, "Trying to call adjustStreamVolume() for a11y without" + + "BIND_ACCESSIBILITY_SERVICE / callingPackage=" + callingPackage); + return; + } adjustStreamVolume(streamType, direction, flags, callingPackage, callingPackage, Binder.getCallingUid()); } @@ -1552,6 +1559,13 @@ public class AudioService extends IAudioService.Stub /** @see AudioManager#setStreamVolume(int, int, int) */ public void setStreamVolume(int streamType, int index, int flags, String callingPackage) { + if ( streamType == AudioManager.STREAM_ACCESSIBILITY + && (PackageManager.PERMISSION_GRANTED != mContext.checkCallingOrSelfPermission( + android.Manifest.permission.BIND_ACCESSIBILITY_SERVICE))) { + Log.w(TAG, "Trying to call setStreamVolume() for a11y without" + + " BIND_ACCESSIBILITY_SERVICE callingPackage=" + callingPackage); + return; + } setStreamVolume(streamType, index, flags, callingPackage, callingPackage, Binder.getCallingUid()); } diff --git a/services/core/java/com/android/server/connectivity/IpConnectivityEventBuilder.java b/services/core/java/com/android/server/connectivity/IpConnectivityEventBuilder.java index d5fa26c49251..81e891a90499 100644 --- a/services/core/java/com/android/server/connectivity/IpConnectivityEventBuilder.java +++ b/services/core/java/com/android/server/connectivity/IpConnectivityEventBuilder.java @@ -151,7 +151,8 @@ final public class IpConnectivityEventBuilder { } private static void setDnsEvent(IpConnectivityEvent out, DnsEvent in) { - IpConnectivityLogClass.DNSLookupBatch dnsLookupBatch = new IpConnectivityLogClass.DNSLookupBatch(); + IpConnectivityLogClass.DNSLookupBatch dnsLookupBatch = + new IpConnectivityLogClass.DNSLookupBatch(); dnsLookupBatch.networkId = netIdOf(in.netId); dnsLookupBatch.eventTypes = bytesToInts(in.eventTypes); dnsLookupBatch.returnCodes = bytesToInts(in.returnCodes); @@ -160,7 +161,8 @@ final public class IpConnectivityEventBuilder { } private static void setIpManagerEvent(IpConnectivityEvent out, IpManagerEvent in) { - IpConnectivityLogClass.IpProvisioningEvent ipProvisioningEvent = new IpConnectivityLogClass.IpProvisioningEvent(); + IpConnectivityLogClass.IpProvisioningEvent ipProvisioningEvent = + new IpConnectivityLogClass.IpProvisioningEvent(); ipProvisioningEvent.ifName = in.ifName; ipProvisioningEvent.eventType = in.eventType; ipProvisioningEvent.latencyMs = (int) in.durationMs; @@ -168,14 +170,16 @@ final public class IpConnectivityEventBuilder { } private static void setIpReachabilityEvent(IpConnectivityEvent out, IpReachabilityEvent in) { - IpConnectivityLogClass.IpReachabilityEvent ipReachabilityEvent = new IpConnectivityLogClass.IpReachabilityEvent(); + IpConnectivityLogClass.IpReachabilityEvent ipReachabilityEvent = + new IpConnectivityLogClass.IpReachabilityEvent(); ipReachabilityEvent.ifName = in.ifName; ipReachabilityEvent.eventType = in.eventType; out.setIpReachabilityEvent(ipReachabilityEvent); } private static void setDefaultNetworkEvent(IpConnectivityEvent out, DefaultNetworkEvent in) { - IpConnectivityLogClass.DefaultNetworkEvent defaultNetworkEvent = new IpConnectivityLogClass.DefaultNetworkEvent(); + IpConnectivityLogClass.DefaultNetworkEvent defaultNetworkEvent = + new IpConnectivityLogClass.DefaultNetworkEvent(); defaultNetworkEvent.networkId = netIdOf(in.netId); defaultNetworkEvent.previousNetworkId = netIdOf(in.prevNetId); defaultNetworkEvent.transportTypes = in.transportTypes; @@ -184,7 +188,8 @@ final public class IpConnectivityEventBuilder { } private static void setNetworkEvent(IpConnectivityEvent out, NetworkEvent in) { - IpConnectivityLogClass.NetworkEvent networkEvent = new IpConnectivityLogClass.NetworkEvent(); + IpConnectivityLogClass.NetworkEvent networkEvent = + new IpConnectivityLogClass.NetworkEvent(); networkEvent.networkId = netIdOf(in.netId); networkEvent.eventType = in.eventType; networkEvent.latencyMs = (int) in.durationMs; @@ -192,7 +197,8 @@ final public class IpConnectivityEventBuilder { } private static void setValidationProbeEvent(IpConnectivityEvent out, ValidationProbeEvent in) { - IpConnectivityLogClass.ValidationProbeEvent validationProbeEvent = new IpConnectivityLogClass.ValidationProbeEvent(); + IpConnectivityLogClass.ValidationProbeEvent validationProbeEvent = + new IpConnectivityLogClass.ValidationProbeEvent(); validationProbeEvent.networkId = netIdOf(in.netId); validationProbeEvent.latencyMs = (int) in.durationMs; validationProbeEvent.probeType = in.probeType; @@ -201,8 +207,10 @@ final public class IpConnectivityEventBuilder { } private static void setApfProgramEvent(IpConnectivityEvent out, ApfProgramEvent in) { - IpConnectivityLogClass.ApfProgramEvent apfProgramEvent = new IpConnectivityLogClass.ApfProgramEvent(); + IpConnectivityLogClass.ApfProgramEvent apfProgramEvent = + new IpConnectivityLogClass.ApfProgramEvent(); apfProgramEvent.lifetime = in.lifetime; + apfProgramEvent.effectiveLifetime = in.actualLifetime; apfProgramEvent.filteredRas = in.filteredRas; apfProgramEvent.currentRas = in.currentRas; apfProgramEvent.programLength = in.programLength; @@ -216,7 +224,8 @@ final public class IpConnectivityEventBuilder { } private static void setApfStats(IpConnectivityEvent out, ApfStats in) { - IpConnectivityLogClass.ApfStatistics apfStatistics = new IpConnectivityLogClass.ApfStatistics(); + IpConnectivityLogClass.ApfStatistics apfStatistics = + new IpConnectivityLogClass.ApfStatistics(); apfStatistics.durationMs = in.durationMs; apfStatistics.receivedRas = in.receivedRas; apfStatistics.matchingRas = in.matchingRas; @@ -224,6 +233,8 @@ final public class IpConnectivityEventBuilder { apfStatistics.zeroLifetimeRas = in.zeroLifetimeRas; apfStatistics.parseErrors = in.parseErrors; apfStatistics.programUpdates = in.programUpdates; + apfStatistics.programUpdatesAll = in.programUpdatesAll; + apfStatistics.programUpdatesAllowingMulticast = in.programUpdatesAllowingMulticast; apfStatistics.maxProgramSize = in.maxProgramSize; out.setApfStatistics(apfStatistics); } diff --git a/services/core/java/com/android/server/connectivity/tethering/IPv6TetheringInterfaceServices.java b/services/core/java/com/android/server/connectivity/tethering/IPv6TetheringInterfaceServices.java index dec2f77b6455..8c6430c8a2ae 100644 --- a/services/core/java/com/android/server/connectivity/tethering/IPv6TetheringInterfaceServices.java +++ b/services/core/java/com/android/server/connectivity/tethering/IPv6TetheringInterfaceServices.java @@ -16,6 +16,8 @@ package com.android.server.connectivity.tethering; +import static android.net.util.NetworkConstants.RFC7421_PREFIX_LENGTH; + import android.net.INetd; import android.net.IpPrefix; import android.net.LinkAddress; @@ -48,7 +50,6 @@ import java.util.Objects; public class IPv6TetheringInterfaceServices { private static final String TAG = IPv6TetheringInterfaceServices.class.getSimpleName(); private static final IpPrefix LINK_LOCAL_PREFIX = new IpPrefix("fe80::/64"); - private static final int RFC7421_IP_PREFIX_LENGTH = 64; private final String mIfName; private final INetworkManagementService mNMService; @@ -124,7 +125,7 @@ public class IPv6TetheringInterfaceServices { params.hasDefaultRoute = v6only.hasIPv6DefaultRoute(); for (LinkAddress linkAddr : v6only.getLinkAddresses()) { - if (linkAddr.getPrefixLength() != RFC7421_IP_PREFIX_LENGTH) continue; + if (linkAddr.getPrefixLength() != RFC7421_PREFIX_LENGTH) continue; final IpPrefix prefix = new IpPrefix( linkAddr.getAddress(), linkAddr.getPrefixLength()); @@ -206,7 +207,7 @@ public class IPv6TetheringInterfaceServices { for (Inet6Address dns : deprecatedDnses) { final String dnsString = dns.getHostAddress(); try { - netd.interfaceDelAddress(mIfName, dnsString, RFC7421_IP_PREFIX_LENGTH); + netd.interfaceDelAddress(mIfName, dnsString, RFC7421_PREFIX_LENGTH); } catch (ServiceSpecificException | RemoteException e) { Log.e(TAG, "Failed to remove local dns IP: " + dnsString, e); } @@ -223,7 +224,7 @@ public class IPv6TetheringInterfaceServices { for (Inet6Address dns : addedDnses) { final String dnsString = dns.getHostAddress(); try { - netd.interfaceAddAddress(mIfName, dnsString, RFC7421_IP_PREFIX_LENGTH); + netd.interfaceAddAddress(mIfName, dnsString, RFC7421_PREFIX_LENGTH); } catch (ServiceSpecificException | RemoteException e) { Log.e(TAG, "Failed to add local dns IP: " + dnsString, e); newDnses.remove(dns); diff --git a/services/core/java/com/android/server/fingerprint/AuthenticationClient.java b/services/core/java/com/android/server/fingerprint/AuthenticationClient.java index 14f2e8617081..552f0d1f8b17 100644 --- a/services/core/java/com/android/server/fingerprint/AuthenticationClient.java +++ b/services/core/java/com/android/server/fingerprint/AuthenticationClient.java @@ -83,6 +83,7 @@ public abstract class AuthenticationClient extends ClientMonitor { if (inLockoutMode) { try { Slog.w(TAG, "Forcing lockout (fp driver code should do this!)"); + stop(false); // cancel fingerprint authentication receiver.onError(getHalDeviceId(), FingerprintManager.FINGERPRINT_ERROR_LOCKOUT, 0 /* vendorCode */); } catch (RemoteException e) { @@ -107,7 +108,7 @@ public abstract class AuthenticationClient extends ClientMonitor { public int start() { IBiometricsFingerprint daemon = getFingerprintDaemon(); if (daemon == null) { - Slog.w(TAG, "start authentication: no fingeprintd!"); + Slog.w(TAG, "start authentication: no fingerprint HAL!"); return ERROR_ESRCH; } try { @@ -130,7 +131,7 @@ public abstract class AuthenticationClient extends ClientMonitor { public int stop(boolean initiatedByClient) { IBiometricsFingerprint daemon = getFingerprintDaemon(); if (daemon == null) { - Slog.w(TAG, "stopAuthentication: no fingeprintd!"); + Slog.w(TAG, "stopAuthentication: no fingerprint HAL!"); return ERROR_ESRCH; } try { diff --git a/services/core/java/com/android/server/fingerprint/ClientMonitor.java b/services/core/java/com/android/server/fingerprint/ClientMonitor.java index 43bb21d2b44a..492cd61c9ca9 100644 --- a/services/core/java/com/android/server/fingerprint/ClientMonitor.java +++ b/services/core/java/com/android/server/fingerprint/ClientMonitor.java @@ -28,13 +28,13 @@ import android.util.Slog; import java.util.NoSuchElementException; /** - * Abstract base class for keeping track and dispatching events from fingerprintd to the + * Abstract base class for keeping track and dispatching events from fingerprint HAL to the * the current client. Subclasses are responsible for coordinating the interaction with - * fingerprintd for the specific action (e.g. authenticate, enroll, enumerate, etc.). + * fingerprint HAL for the specific action (e.g. authenticate, enroll, enumerate, etc.). */ public abstract class ClientMonitor implements IBinder.DeathRecipient { protected static final String TAG = FingerprintService.TAG; // TODO: get specific name - protected static final int ERROR_ESRCH = 3; // Likely fingerprintd is dead. See errno.h. + protected static final int ERROR_ESRCH = 3; // Likely fingerprint HAL is dead. See errno.h. protected static final boolean DEBUG = FingerprintService.DEBUG; private IBinder mToken; private IFingerprintServiceReceiver mReceiver; @@ -77,13 +77,13 @@ public abstract class ClientMonitor implements IBinder.DeathRecipient { } /** - * Contacts fingerprintd to start the client. + * Contacts fingerprint HAL to start the client. * @return 0 on succes, errno from driver on failure */ public abstract int start(); /** - * Contacts fingerprintd to stop the client. + * Contacts fingerprint HAL to stop the client. * @param initiatedByClient whether the operation is at the request of a client */ public abstract int stop(boolean initiatedByClient); @@ -108,7 +108,7 @@ public abstract class ClientMonitor implements IBinder.DeathRecipient { public abstract boolean onEnumerationResult(int fingerId, int groupId, int remaining); /** - * Called when we get notification from fingerprintd that an image has been acquired. + * Called when we get notification from fingerprint HAL that an image has been acquired. * Common to authenticate and enroll. * @param acquiredInfo info about the current image acquisition * @return true if client should be removed @@ -131,7 +131,7 @@ public abstract class ClientMonitor implements IBinder.DeathRecipient { } /** - * Called when we get notification from fingerprintd that an error has occurred with the + * Called when we get notification from fingerprint HAL that an error has occurred with the * current operation. Common to authenticate, enroll, enumerate and remove. * @param error * @return true if client should be removed diff --git a/services/core/java/com/android/server/fingerprint/EnrollClient.java b/services/core/java/com/android/server/fingerprint/EnrollClient.java index eddcd5b89865..e1b78a8d3c68 100644 --- a/services/core/java/com/android/server/fingerprint/EnrollClient.java +++ b/services/core/java/com/android/server/fingerprint/EnrollClient.java @@ -80,7 +80,7 @@ public abstract class EnrollClient extends ClientMonitor { public int start() { IBiometricsFingerprint daemon = getFingerprintDaemon(); if (daemon == null) { - Slog.w(TAG, "enroll: no fingeprintd!"); + Slog.w(TAG, "enroll: no fingerprint HAL!"); return ERROR_ESRCH; } final int timeout = (int) (ENROLLMENT_TIMEOUT_MS / MS_PER_SEC); @@ -102,7 +102,7 @@ public abstract class EnrollClient extends ClientMonitor { public int stop(boolean initiatedByClient) { IBiometricsFingerprint daemon = getFingerprintDaemon(); if (daemon == null) { - Slog.w(TAG, "stopEnrollment: no fingeprintd!"); + Slog.w(TAG, "stopEnrollment: no fingerprint HAL!"); return ERROR_ESRCH; } try { diff --git a/services/core/java/com/android/server/fingerprint/EnumerateClient.java b/services/core/java/com/android/server/fingerprint/EnumerateClient.java index 55bf689bd3c6..34f245f19d12 100644 --- a/services/core/java/com/android/server/fingerprint/EnumerateClient.java +++ b/services/core/java/com/android/server/fingerprint/EnumerateClient.java @@ -58,7 +58,7 @@ public abstract class EnumerateClient extends ClientMonitor { public int stop(boolean initiatedByClient) { IBiometricsFingerprint daemon = getFingerprintDaemon(); if (daemon == null) { - Slog.w(TAG, "stopAuthentication: no fingeprintd!"); + Slog.w(TAG, "stopAuthentication: no fingerprint HAL!"); return ERROR_ESRCH; } try { diff --git a/services/core/java/com/android/server/fingerprint/FingerprintService.java b/services/core/java/com/android/server/fingerprint/FingerprintService.java index 0f299426f73f..bdba64f72995 100644 --- a/services/core/java/com/android/server/fingerprint/FingerprintService.java +++ b/services/core/java/com/android/server/fingerprint/FingerprintService.java @@ -97,7 +97,6 @@ public class FingerprintService extends SystemService implements IHwBinder.Death static final String TAG = "FingerprintService"; static final boolean DEBUG = true; private static final String FP_DATA_DIR = "fpdata"; - private static final String FINGERPRINT_HIDL = "fingerprint_hal"; private static final int MSG_USER_SWITCHING = 10; private static final String ACTION_LOCKOUT_RESET = "com.android.server.fingerprint.ACTION_LOCKOUT_RESET"; @@ -206,7 +205,7 @@ public class FingerprintService extends SystemService implements IHwBinder.Death @Override public void serviceDied(long cookie) { - Slog.v(TAG, "fingerprintd died"); + Slog.v(TAG, "fingerprint HAL died"); MetricsLogger.count(mContext, "fingerprintd_died", 1); synchronized (this) { mDaemon = null; @@ -219,7 +218,7 @@ public class FingerprintService extends SystemService implements IHwBinder.Death public synchronized IBiometricsFingerprint getFingerprintDaemon() { if (mDaemon == null) { try { - mDaemon = IBiometricsFingerprint.getService(FINGERPRINT_HIDL); + mDaemon = IBiometricsFingerprint.getService(); } catch (java.util.NoSuchElementException e) { // Service doesn't exist or cannot be opened. Logged below. } catch (RemoteException e) { @@ -235,7 +234,7 @@ public class FingerprintService extends SystemService implements IHwBinder.Death try { mHalDeviceId = mDaemon.setNotify(mDaemonCallback); } catch (RemoteException e) { - Slog.e(TAG, "Failed to open fingeprintd HAL", e); + Slog.e(TAG, "Failed to open fingerprint HAL", e); mDaemon = null; // try again later! } @@ -391,7 +390,7 @@ public class FingerprintService extends SystemService implements IHwBinder.Death public long startPreEnroll(IBinder token) { IBiometricsFingerprint daemon = getFingerprintDaemon(); if (daemon == null) { - Slog.w(TAG, "startPreEnroll: no fingeprintd!"); + Slog.w(TAG, "startPreEnroll: no fingerprint HAL!"); return 0; } try { @@ -405,7 +404,7 @@ public class FingerprintService extends SystemService implements IHwBinder.Death public int startPostEnroll(IBinder token) { IBiometricsFingerprint daemon = getFingerprintDaemon(); if (daemon == null) { - Slog.w(TAG, "startPostEnroll: no fingeprintd!"); + Slog.w(TAG, "startPostEnroll: no fingerprint HAL!"); return 0; } try { @@ -417,7 +416,7 @@ public class FingerprintService extends SystemService implements IHwBinder.Death } /** - * Calls fingerprintd to switch states to the new task. If there's already a current task, + * Calls fingerprint HAL to switch states to the new task. If there's already a current task, * it calls cancel() and sets mPendingClient to begin when the current task finishes * ({@link FingerprintManager#FINGERPRINT_ERROR_CANCELED}). * @param newClient the new client that wants to connect @@ -447,7 +446,7 @@ public class FingerprintService extends SystemService implements IHwBinder.Death IFingerprintServiceReceiver receiver, boolean restricted) { IBiometricsFingerprint daemon = getFingerprintDaemon(); if (daemon == null) { - Slog.w(TAG, "startRemove: no fingeprintd!"); + Slog.w(TAG, "startRemove: no fingerprint HAL!"); return; } RemovalClient client = new RemovalClient(getContext(), mHalDeviceId, token, @@ -469,7 +468,7 @@ public class FingerprintService extends SystemService implements IHwBinder.Death IFingerprintServiceReceiver receiver, boolean restricted) { IBiometricsFingerprint daemon = getFingerprintDaemon(); if (daemon == null) { - Slog.w(TAG, "startEnumerate: no fingeprintd!"); + Slog.w(TAG, "startEnumerate: no fingerprint HAL!"); return; } EnumerateClient client = new EnumerateClient(getContext(), mHalDeviceId, token, diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index be8aaf02db41..3dcc5d997e08 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -22,10 +22,10 @@ import static android.content.pm.PackageManager.FEATURE_TELEVISION; import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL; import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL_ALL; import static android.service.notification.NotificationListenerService.REASON_CHANNEL_BANNED; -import static android.service.notification.NotificationListenerService.REASON_DELEGATE_CANCEL; -import static android.service.notification.NotificationListenerService.REASON_DELEGATE_CANCEL_ALL; -import static android.service.notification.NotificationListenerService.REASON_DELEGATE_CLICK; -import static android.service.notification.NotificationListenerService.REASON_DELEGATE_ERROR; +import static android.service.notification.NotificationListenerService.REASON_CANCEL; +import static android.service.notification.NotificationListenerService.REASON_CANCEL_ALL; +import static android.service.notification.NotificationListenerService.REASON_CLICK; +import static android.service.notification.NotificationListenerService.REASON_ERROR; import static android.service.notification.NotificationListenerService.REASON_GROUP_SUMMARY_CANCELED; import static android.service.notification.NotificationListenerService.REASON_LISTENER_CANCEL; import static android.service.notification.NotificationListenerService.REASON_LISTENER_CANCEL_ALL; @@ -88,7 +88,6 @@ import android.database.ContentObserver; import android.media.AudioManager; import android.media.AudioManagerInternal; import android.media.IRingtonePlayer; -import android.metrics.LogMaker; import android.net.Uri; import android.os.Binder; import android.os.Bundle; @@ -179,7 +178,6 @@ import java.io.PrintWriter; import java.nio.charset.StandardCharsets; import java.util.ArrayDeque; import java.util.ArrayList; -import java.util.Date; import java.util.Iterator; import java.util.List; import java.util.Map; @@ -543,7 +541,7 @@ public class NotificationManagerService extends SystemService { @Override public void onClearAll(int callingUid, int callingPid, int userId) { synchronized (mNotificationLock) { - cancelAllLocked(callingUid, callingPid, userId, REASON_DELEGATE_CANCEL_ALL, null, + cancelAllLocked(callingUid, callingPid, userId, REASON_CANCEL_ALL, null, /*includeCurrentProfiles*/ true); } } @@ -567,7 +565,7 @@ public class NotificationManagerService extends SystemService { cancelNotification(callingUid, callingPid, sbn.getPackageName(), sbn.getTag(), sbn.getId(), Notification.FLAG_AUTO_CANCEL, Notification.FLAG_FOREGROUND_SERVICE, false, r.getUserId(), - REASON_DELEGATE_CLICK, null); + REASON_CLICK, null); } } @@ -596,7 +594,7 @@ public class NotificationManagerService extends SystemService { String pkg, String tag, int id, int userId) { cancelNotification(callingUid, callingPid, pkg, tag, id, 0, Notification.FLAG_ONGOING_EVENT | Notification.FLAG_FOREGROUND_SERVICE, - true, userId, REASON_DELEGATE_CANCEL, null); + true, userId, REASON_CANCEL, null); } @Override @@ -631,7 +629,7 @@ public class NotificationManagerService extends SystemService { Slog.d(TAG, "onNotification error pkg=" + pkg + " tag=" + tag + " id=" + id + "; will crashApplication(uid=" + uid + ", pid=" + initialPid + ")"); cancelNotification(callingUid, callingPid, pkg, tag, id, 0, 0, false, userId, - REASON_DELEGATE_ERROR, null); + REASON_ERROR, null); long ident = Binder.clearCallingIdentity(); try { ActivityManager.getService().crashApplication(uid, initialPid, pkg, -1, @@ -1658,8 +1656,7 @@ public class NotificationManagerService extends SystemService { public NotificationChannel getNotificationChannelForPackage(String pkg, int uid, String channelId, boolean includeDeleted) { checkCallerIsSystem(); - return mRankingHelper.getNotificationChannel - (pkg, uid, channelId, includeDeleted); + return mRankingHelper.getNotificationChannel(pkg, uid, channelId, includeDeleted); } @Override @@ -1675,6 +1672,27 @@ public class NotificationManagerService extends SystemService { } @Override + public ParceledListSlice<NotificationChannelGroup> getNotificationChannelGroups( + String pkg) { + checkCallerIsSystemOrSameApp(pkg); + return new ParceledListSlice<>(new ArrayList( + mRankingHelper.getNotificationChannelGroups(pkg, Binder.getCallingUid()))); + } + + @Override + public void deleteNotificationChannelGroup(String pkg, String channelGroupId) { + checkCallerIsSystemOrSameApp(pkg); + + List<String> deletedChannelIds = mRankingHelper.deleteNotificationChannelGroup( + pkg, Binder.getCallingUid(), channelGroupId); + for (int i = 0; i < deletedChannelIds.size(); i++) { + cancelAllNotificationsInt(MY_UID, MY_PID, pkg, deletedChannelIds.get(i), 0, 0, true, + UserHandle.getUserId(Binder.getCallingUid()), REASON_CHANNEL_BANNED, null); + } + savePolicyFile(); + } + + @Override public void updateNotificationChannelForPackage(String pkg, int uid, NotificationChannel channel) { enforceSystemOrSystemUI("Caller not system or systemui"); @@ -1698,6 +1716,12 @@ public class NotificationManagerService extends SystemService { } @Override + public int getDeletedChannelCount(String pkg, int uid) { + enforceSystemOrSystemUI("getDeletedChannelCount"); + return mRankingHelper.getDeletedChannelCount(pkg, uid); + } + + @Override public ParceledListSlice<NotificationChannelGroup> getNotificationChannelGroupsForPackage( String pkg, int uid, boolean includeDeleted) { checkCallerIsSystem(); @@ -3349,7 +3373,7 @@ public class NotificationManagerService extends SystemService { Slog.e(TAG, "Not posting notification without small icon: " + notification); if (old != null && !old.isCanceled) { mListeners.notifyRemovedLocked(n, - NotificationListenerService.REASON_DELEGATE_ERROR); + NotificationListenerService.REASON_ERROR); mHandler.post(new Runnable() { @Override public void run() { @@ -3996,8 +4020,8 @@ public class NotificationManagerService extends SystemService { // Record usage stats // TODO: add unbundling stats? switch (reason) { - case REASON_DELEGATE_CANCEL: - case REASON_DELEGATE_CANCEL_ALL: + case REASON_CANCEL: + case REASON_CANCEL_ALL: case REASON_LISTENER_CANCEL: case REASON_LISTENER_CANCEL_ALL: mUsageStats.registerDismissedByUser(r); @@ -4057,7 +4081,7 @@ public class NotificationManagerService extends SystemService { // Ideally we'd do this in the caller of this method. However, that would // require the caller to also find the notification. - if (reason == REASON_DELEGATE_CLICK) { + if (reason == REASON_CLICK) { mUsageStats.registerClickedByUser(r); } diff --git a/services/core/java/com/android/server/notification/RankingHelper.java b/services/core/java/com/android/server/notification/RankingHelper.java index 4b1804c8e7a7..02f92fedb85d 100644 --- a/services/core/java/com/android/server/notification/RankingHelper.java +++ b/services/core/java/com/android/server/notification/RankingHelper.java @@ -15,8 +15,6 @@ */ package com.android.server.notification; -import static android.app.NotificationManager.IMPORTANCE_NONE; - import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.MetricsLogger; @@ -69,7 +67,6 @@ public class RankingHelper implements RankingConfig { private static final String ATT_VERSION = "version"; private static final String ATT_NAME = "name"; - private static final String ATT_NAME_RES_ID = "name_res_id"; private static final String ATT_UID = "uid"; private static final String ATT_ID = "id"; private static final String ATT_PRIORITY = "priority"; @@ -195,14 +192,9 @@ public class RankingHelper implements RankingConfig { if (TAG_GROUP.equals(tagName)) { String id = parser.getAttributeValue(null, ATT_ID); CharSequence groupName = parser.getAttributeValue(null, ATT_NAME); - int groupNameRes = safeInt(parser, ATT_NAME_RES_ID, 0); if (!TextUtils.isEmpty(id)) { - NotificationChannelGroup group = null; - if (groupName != null) { - group = new NotificationChannelGroup(id, groupName); - } else { - group = new NotificationChannelGroup(id, groupNameRes); - } + NotificationChannelGroup group + = new NotificationChannelGroup(id, groupName); r.groups.put(id, group); } } @@ -210,19 +202,12 @@ public class RankingHelper implements RankingConfig { if (TAG_CHANNEL.equals(tagName)) { String id = parser.getAttributeValue(null, ATT_ID); CharSequence channelName = parser.getAttributeValue(null, ATT_NAME); - int channelNameRes = safeInt(parser, ATT_NAME_RES_ID, 0); int channelImportance = safeInt(parser, ATT_IMPORTANCE, DEFAULT_IMPORTANCE); - if (!TextUtils.isEmpty(id)) { - NotificationChannel channel; - if (channelName != null) { - channel = new NotificationChannel(id, channelName, - channelImportance); - } else { - channel = new NotificationChannel(id, channelNameRes, - channelImportance); - } + if (!TextUtils.isEmpty(id) && !TextUtils.isEmpty(channelName)) { + NotificationChannel channel = new NotificationChannel(id, + channelName, channelImportance); channel.populateFromXml(parser); r.channels.put(id, channel); } @@ -302,7 +287,7 @@ public class RankingHelper implements RankingConfig { NotificationChannel channel; channel = new NotificationChannel( NotificationChannel.DEFAULT_CHANNEL_ID, - R.string.default_notification_channel_label, + mContext.getString(R.string.default_notification_channel_label), r.importance); channel.setBypassDnd(r.priority == Notification.PRIORITY_MAX); channel.setLockscreenVisibility(r.visibility); @@ -482,8 +467,7 @@ public class RankingHelper implements RankingConfig { Preconditions.checkNotNull(pkg); Preconditions.checkNotNull(group); Preconditions.checkNotNull(group.getId()); - Preconditions.checkNotNull(!TextUtils.isEmpty(group.getName()) - || group.getNameResId() != 0); + Preconditions.checkNotNull(!TextUtils.isEmpty(group.getName())); Record r = getOrCreateRecord(pkg, uid); if (r == null) { throw new IllegalArgumentException("Invalid package"); @@ -504,8 +488,7 @@ public class RankingHelper implements RankingConfig { Preconditions.checkNotNull(pkg); Preconditions.checkNotNull(channel); Preconditions.checkNotNull(channel.getId()); - Preconditions.checkArgument(!TextUtils.isEmpty(channel.getName()) - || channel.getNameResId() != 0); + Preconditions.checkArgument(!TextUtils.isEmpty(channel.getName())); Record r = getOrCreateRecord(pkg, uid); if (r == null) { throw new IllegalArgumentException("Invalid package"); @@ -524,7 +507,7 @@ public class RankingHelper implements RankingConfig { existing.setDeleted(false); } - existing.setNameResId(channel.getNameResId()); + existing.setName(channel.getName()); MetricsLogger.action(getChannelLog(channel, pkg)); updateConfig(); @@ -669,8 +652,6 @@ public class RankingHelper implements RankingConfig { @Override public void deleteNotificationChannel(String pkg, int uid, String channelId) { - Preconditions.checkNotNull(pkg); - Preconditions.checkNotNull(channelId); Record r = getRecord(pkg, uid); if (r == null) { return; @@ -682,6 +663,7 @@ public class RankingHelper implements RankingConfig { LogMaker lm = getChannelLog(channel, pkg); lm.setType(MetricsProto.MetricsEvent.TYPE_CLOSE); MetricsLogger.action(lm); + updateConfig(); } @Override @@ -694,6 +676,7 @@ public class RankingHelper implements RankingConfig { return; } r.channels.remove(channelId); + updateConfig(); } @Override @@ -710,6 +693,7 @@ public class RankingHelper implements RankingConfig { r.channels.remove(key); } } + updateConfig(); } public NotificationChannelGroup getNotificationChannelGroup(String groupId, String pkg, @@ -734,12 +718,15 @@ public class RankingHelper implements RankingConfig { final NotificationChannel nc = r.channels.valueAt(i); if (includeDeleted || !nc.isDeleted()) { if (nc.getGroup() != null) { - NotificationChannelGroup ncg = groups.get(nc.getGroup()); - if (ncg == null ) { - ncg = r.groups.get(nc.getGroup()).clone(); - groups.put(nc.getGroup(), ncg); + if (r.groups.get(nc.getGroup()) != null) { + NotificationChannelGroup ncg = groups.get(nc.getGroup()); + if (ncg == null) { + ncg = r.groups.get(nc.getGroup()).clone(); + groups.put(nc.getGroup(), ncg); + + } + ncg.addChannel(nc); } - ncg.addChannel(nc); } else { nonGrouped.addChannel(nc); } @@ -751,8 +738,29 @@ public class RankingHelper implements RankingConfig { return new ParceledListSlice<>(new ArrayList<>(groups.values())); } + public List<String> deleteNotificationChannelGroup(String pkg, int uid, + String groupId) { + List<String> deletedChannelIds = new ArrayList<>(); + Record r = getRecord(pkg, uid); + if (r == null || TextUtils.isEmpty(groupId)) { + return deletedChannelIds; + } + + r.groups.remove(groupId); + + int N = r.channels.size(); + for (int i = 0; i < N; i++) { + final NotificationChannel nc = r.channels.valueAt(i); + if (groupId.equals(nc.getGroup())) { + nc.setDeleted(true); + deletedChannelIds.add(nc.getId()); + } + } + updateConfig(); + return deletedChannelIds; + } + @Override - @VisibleForTesting public Collection<NotificationChannelGroup> getNotificationChannelGroups(String pkg, int uid) { Record r = getRecord(pkg, uid); @@ -781,6 +789,23 @@ public class RankingHelper implements RankingConfig { return new ParceledListSlice<>(channels); } + public int getDeletedChannelCount(String pkg, int uid) { + Preconditions.checkNotNull(pkg); + int deletedCount = 0; + Record r = getRecord(pkg, uid); + if (r == null) { + return deletedCount; + } + int N = r.channels.size(); + for (int i = 0; i < N; i++) { + final NotificationChannel nc = r.channels.valueAt(i); + if (nc.isDeleted()) { + deletedCount++; + } + } + return deletedCount; + } + /** * Sets importance. */ diff --git a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java index b085179c7fc5..6af1c3b20df2 100644 --- a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java +++ b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java @@ -288,6 +288,10 @@ final class OverlayManagerServiceImpl { if (overlayPackage == null) { return false; } + // Static overlay is always being enabled. + if (!enable && overlayPackage.isStaticOverlay) { + return false; + } try { final OverlayInfo oi = mSettings.getOverlayInfo(packageName, userId); @@ -333,17 +337,28 @@ final class OverlayManagerServiceImpl { } } + boolean isPackageUpdatableOverlay(@NonNull final String packageName, final int userId) { + final PackageInfo overlayPackage = mPackageManager.getPackageInfo(packageName, userId); + if (overlayPackage == null || overlayPackage.isStaticOverlay) { + return false; + } + return true; + } + boolean setPriority(@NonNull final String packageName, @NonNull final String newParentPackageName, final int userId) { - return mSettings.setPriority(packageName, newParentPackageName, userId); + return isPackageUpdatableOverlay(packageName, userId) && + mSettings.setPriority(packageName, newParentPackageName, userId); } boolean setHighestPriority(@NonNull final String packageName, final int userId) { - return mSettings.setHighestPriority(packageName, userId); + return isPackageUpdatableOverlay(packageName, userId) && + mSettings.setHighestPriority(packageName, userId); } boolean setLowestPriority(@NonNull final String packageName, final int userId) { - return mSettings.setLowestPriority(packageName, userId); + return isPackageUpdatableOverlay(packageName, userId) && + mSettings.setLowestPriority(packageName, userId); } void onDump(@NonNull final PrintWriter pw) { @@ -368,7 +383,9 @@ final class OverlayManagerServiceImpl { private void updateState(@Nullable final PackageInfo targetPackage, @NonNull final PackageInfo overlayPackage, final int userId) throws OverlayManagerSettings.BadKeyException { - if (targetPackage != null) { + // Static RROs targeting to "android", ie framework-res.apk, are handled by native layers. + if (targetPackage != null && + !("android".equals(targetPackage.packageName) && overlayPackage.isStaticOverlay)) { mIdmapManager.createIdmap(targetPackage, overlayPackage, userId); } diff --git a/services/core/java/com/android/server/pm/BackgroundDexOptService.java b/services/core/java/com/android/server/pm/BackgroundDexOptService.java index 7aa96cfddc62..d8900c0546fd 100644 --- a/services/core/java/com/android/server/pm/BackgroundDexOptService.java +++ b/services/core/java/com/android/server/pm/BackgroundDexOptService.java @@ -49,8 +49,6 @@ public class BackgroundDexOptService extends JobService { private static final boolean DEBUG = false; - private static final long RETRY_LATENCY = 4 * AlarmManager.INTERVAL_HOUR; - private static final int JOB_IDLE_OPTIMIZE = 800; private static final int JOB_POST_BOOT_UPDATE = 801; diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java index 71bfa648bd5f..e1426fdcf3ef 100644 --- a/services/core/java/com/android/server/pm/LauncherAppsService.java +++ b/services/core/java/com/android/server/pm/LauncherAppsService.java @@ -245,9 +245,12 @@ public class LauncherAppsService extends SystemService { // STOPSHIP Remove the whitelist. if ("com.google.android.talk".equals(callingPackage) - || "com.google.android.quicksearchbox".equals(callingPackage)) { + || "com.google.android.quicksearchbox".equals(callingPackage) + || "com.google.android.googlequicksearchbox".equals(callingPackage) + ) { return false; } + // STOPSHIP Change it to 'e'. Slog.wtfStack(TAG, message + " by " + callingPackage + " for another profile " + targetUserId + " from " + callingUserId); diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index c27806d80ac9..fb3080b4376f 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -260,7 +260,6 @@ import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.Preconditions; import com.android.internal.util.XmlUtils; import com.android.server.AttributeCache; -import com.android.server.BackgroundDexOptJobService; import com.android.server.DeviceIdleController; import com.android.server.EventLogTags; import com.android.server.FgThread; @@ -272,6 +271,7 @@ import com.android.server.SystemConfig; import com.android.server.SystemServerInitThreadPool; import com.android.server.Watchdog; import com.android.server.net.NetworkPolicyManagerInternal; +import com.android.server.pm.BackgroundDexOptService; import com.android.server.pm.Installer.InstallerException; import com.android.server.pm.PermissionsState.PermissionState; import com.android.server.pm.Settings.DatabaseVersion; @@ -399,7 +399,7 @@ public class PackageManagerService extends IPackageManager.Stub { private static final boolean HIDE_EPHEMERAL_APIS = false; private static final boolean ENABLE_FREE_CACHE_V2 = - SystemProperties.getBoolean("fw.free_cache_v2", false); + SystemProperties.getBoolean("fw.free_cache_v2", true); private static final int RADIO_UID = Process.PHONE_UID; private static final int LOG_UID = Process.LOG_UID; @@ -2038,12 +2038,15 @@ public class PackageManagerService extends IPackageManager.Stub { final boolean supportsRuntimePermissions = pkg.applicationInfo.targetSdkVersion >= Build.VERSION_CODES.M; + final boolean instantApp = isInstantApp(pkg.packageName, userId); + for (String permission : pkg.requestedPermissions) { final BasePermission bp; synchronized (mPackages) { bp = mSettings.mPermissions.get(permission); } if (bp != null && (bp.isRuntime() || bp.isDevelopment()) + && (!instantApp || bp.isInstant()) && (grantedPermissions == null || ArrayUtils.contains(grantedPermissions, permission))) { final int flags = permissionsState.getPermissionFlags(permission, userId); @@ -10565,9 +10568,9 @@ public class PackageManagerService extends IPackageManager.Stub { ps.pkg.applicationInfo.primaryCpuAbi = adjustedAbi; Slog.i(TAG, "Adjusting ABI for " + ps.name + " to " + adjustedAbi + " (requirer=" - + (requirer == null ? "null" : requirer.pkg.packageName) + + (requirer != null ? requirer.pkg : "null") + ", scannedPackage=" - + (scannedPackage != null ? scannedPackage.packageName : "null") + + (scannedPackage != null ? scannedPackage : "null") + ")"); try { mInstaller.rmdex(ps.codePathString, @@ -12190,6 +12193,11 @@ public class PackageManagerService extends IPackageManager.Stub { if (!isInstantApp && userState.instantApp) { return null; } + // throw out instant app filters if updates are available; will trigger + // instant app resolution + if (userState.instantApp && ps.isUpdateAvailable()) { + return null; + } final ResolveInfo res = new ResolveInfo(); res.activityInfo = ai; if ((mFlags&PackageManager.GET_RESOLVED_FILTER) != 0) { @@ -16845,11 +16853,11 @@ public class PackageManagerService extends IPackageManager.Stub { mDexManager.isUsedByOtherApps(pkg.packageName)); Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); - // Notify BackgroundDexOptJobService that the package has been changed. + // Notify BackgroundDexOptService that the package has been changed. // If this is an update of a package which used to fail to compile, // BDOS will remove it from its blacklist. // TODO: Layering violation - BackgroundDexOptJobService.notifyPackageChanged(pkg.packageName); + BackgroundDexOptService.notifyPackageChanged(pkg.packageName); } if (!args.doRename(res.returnCode, pkg, oldCodePath)) { @@ -16885,6 +16893,7 @@ public class PackageManagerService extends IPackageManager.Stub { final PackageSetting ps = mSettings.mPackages.get(pkgName); if (ps != null) { res.newUsers = ps.queryInstalledUsers(sUserManager.getUserIds(), true); + ps.setUpdateAvailable(false /*updateAvailable*/); } final int childCount = (pkg.childPackages != null) ? pkg.childPackages.size() : 0; @@ -19674,6 +19683,17 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName()); } @Override + public void setUpdateAvailable(String packageName, boolean updateAvailable) { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.INSTALL_PACKAGES, null); + synchronized (mPackages) { + final PackageSetting pkgSetting = mSettings.mPackages.get(packageName); + if (pkgSetting != null) { + pkgSetting.setUpdateAvailable(updateAvailable); + } + } + } + + @Override public void setComponentEnabledSetting(ComponentName componentName, int newState, int flags, int userId) { if (!sUserManager.exists(userId)) return; diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java index a7349fc8454c..751d9af33463 100644 --- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java +++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java @@ -147,6 +147,8 @@ class PackageManagerShellCommand extends ShellCommand { return runSetHomeActivity(); case "get-privapp-permissions": return runGetPrivappPermissions(); + case "has-feature": + return runHasFeature(); default: return handleDefaultCommands(cmd); } @@ -1268,6 +1270,28 @@ class PackageManagerShellCommand extends ShellCommand { return 0; } + private int runHasFeature() { + final PrintWriter err = getErrPrintWriter(); + final String featureName = getNextArg(); + if (featureName == null) { + err.println("Error: expected FEATURE name"); + return 1; + } + final String versionString = getNextArg(); + try { + final int version = (versionString == null) ? 0 : Integer.parseInt(versionString); + final boolean hasFeature = mInterface.hasSystemFeature(featureName, version); + getOutPrintWriter().println(hasFeature); + return hasFeature ? 0 : 1; + } catch (NumberFormatException e) { + err.println("Error: illegal version number " + versionString); + return 1; + } catch (RemoteException e) { + err.println(e.toString()); + return 1; + } + } + private static String checkAbiArgument(String abi) { if (TextUtils.isEmpty(abi)) { throw new IllegalArgumentException("Missing ABI argument"); @@ -1649,6 +1673,9 @@ class PackageManagerShellCommand extends ShellCommand { pw.println(" Unsuspends the specified package (as user)."); pw.println(" set-home-activity [--user USER_ID] TARGET-COMPONENT"); pw.println(" set the default home activity (aka launcher)."); + pw.println(" has-feature FEATURE_NAME [version]"); + pw.println(" prints true and returns exit status 0 when system has a FEATURE_NAME,"); + pw.println(" otherwise prints false and returns exit status 1"); pw.println(); Intent.printIntentArgsHelp(pw , ""); } diff --git a/services/core/java/com/android/server/pm/PackageSettingBase.java b/services/core/java/com/android/server/pm/PackageSettingBase.java index b9c43da77916..dfed72fa9b61 100644 --- a/services/core/java/com/android/server/pm/PackageSettingBase.java +++ b/services/core/java/com/android/server/pm/PackageSettingBase.java @@ -141,6 +141,8 @@ abstract class PackageSettingBase extends SettingBase { String volumeUuid; /** The category of this app, as hinted by the installer */ int categoryHint = ApplicationInfo.CATEGORY_UNDEFINED; + /** Whether or not an update is available. Ostensibly only for instant apps. */ + boolean updateAvailable; IntentFilterVerificationInfo verificationInfo; @@ -219,6 +221,14 @@ abstract class PackageSettingBase extends SettingBase { timeStamp = newStamp; } + public void setUpdateAvailable(boolean updateAvailable) { + this.updateAvailable = updateAvailable; + } + + public boolean isUpdateAvailable() { + return updateAvailable; + } + /** * Makes a shallow copy of the given package settings. * @@ -268,6 +278,7 @@ abstract class PackageSettingBase extends SettingBase { usesStaticLibrariesVersions = orig.usesStaticLibrariesVersions != null ? Arrays.copyOf(orig.usesStaticLibrariesVersions, orig.usesStaticLibrariesVersions.length) : null; + updateAvailable = orig.updateAvailable; } private PackageUserState modifyUserState(int userId) { diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java index 570b31ffa7b5..7bd34246fe02 100644 --- a/services/core/java/com/android/server/pm/Settings.java +++ b/services/core/java/com/android/server/pm/Settings.java @@ -2831,6 +2831,9 @@ final class Settings { if (pkg.parentPackageName != null) { serializer.attribute(null, "parentPackageName", pkg.parentPackageName); } + if (pkg.updateAvailable) { + serializer.attribute(null, "updateAvailable", "true"); + } writeChildPackagesLPw(serializer, pkg.childPackageNames); @@ -3698,6 +3701,7 @@ final class Settings { String isOrphaned = null; String volumeUuid = null; String categoryHintString = null; + String updateAvailable = null; int categoryHint = ApplicationInfo.CATEGORY_UNDEFINED; String uidError = null; int pkgFlags = 0; @@ -3726,6 +3730,7 @@ final class Settings { primaryCpuAbiString = parser.getAttributeValue(null, "primaryCpuAbi"); secondaryCpuAbiString = parser.getAttributeValue(null, "secondaryCpuAbi"); cpuAbiOverrideString = parser.getAttributeValue(null, "cpuAbiOverride"); + updateAvailable = parser.getAttributeValue(null, "updateAvailable"); if (primaryCpuAbiString == null && legacyCpuAbiString != null) { primaryCpuAbiString = legacyCpuAbiString; @@ -3905,6 +3910,7 @@ final class Settings { packageSetting.legacyNativeLibraryPathString = legacyNativeLibraryPathStr; packageSetting.primaryCpuAbiString = primaryCpuAbiString; packageSetting.secondaryCpuAbiString = secondaryCpuAbiString; + packageSetting.updateAvailable = "true".equals(updateAvailable); // Handle legacy string here for single-user mode final String enabledStr = parser.getAttributeValue(null, ATTR_ENABLED); if (enabledStr != null) { diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java index 7885748fdf13..21fe5ba6cfba 100644 --- a/services/core/java/com/android/server/pm/ShortcutPackage.java +++ b/services/core/java/com/android/server/pm/ShortcutPackage.java @@ -720,7 +720,11 @@ class ShortcutPackage extends ShortcutPackageItem { // Disable dynamic shortcuts whose target activity is gone. if (si.isDynamic()) { - if (!s.injectIsMainActivity(si.getActivity(), getPackageUserId())) { + if (si.getActivity() == null) { + // Note if it's dynamic, it must have a target activity, but b/36228253. + s.wtf("null activity detected."); + // TODO Maybe remove it? + } else if (!s.injectIsMainActivity(si.getActivity(), getPackageUserId())) { Slog.w(TAG, String.format( "%s is no longer main activity. Disabling shorcut %s.", getPackageName(), si.getId())); @@ -931,6 +935,10 @@ class ShortcutPackage extends ShortcutPackageItem { } final ComponentName activity = si.getActivity(); + if (activity == null) { + mShortcutUser.mService.wtf("null activity detected."); + continue; + } ArrayList<ShortcutInfo> list = activitiesToShortcuts.get(activity); if (list == null) { diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java index 8998212039e9..ef46baebfd0e 100644 --- a/services/core/java/com/android/server/pm/ShortcutService.java +++ b/services/core/java/com/android/server/pm/ShortcutService.java @@ -3237,6 +3237,10 @@ public class ShortcutService extends IShortcutService.Stub { boolean injectIsMainActivity(@NonNull ComponentName activity, int userId) { final long start = injectElapsedRealtime(); try { + if (activity == null) { + wtf("null activity detected"); + return false; + } if (DUMMY_MAIN_ACTIVITY.equals(activity.getClassName())) { return true; } diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index b9fcf4e39203..1f97d7d9cf47 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -1810,8 +1810,8 @@ public class UserManagerService extends IUserManager.Stub { if (type == XmlPullParser.START_TAG) { if (parser.getName().equals(TAG_RESTRICTIONS)) { synchronized (mGuestRestrictions) { - mGuestRestrictions.putAll( - UserRestrictionsUtils.readRestrictions(parser)); + UserRestrictionsUtils + .readRestrictions(parser, mGuestRestrictions); } } break; diff --git a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java index 36eba8e1b6bb..cb2ed6e0d00f 100644 --- a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java +++ b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java @@ -207,6 +207,7 @@ public class UserRestrictionsUtils { } public static void readRestrictions(XmlPullParser parser, Bundle restrictions) { + restrictions.clear(); for (String key : USER_RESTRICTIONS) { final String value = parser.getAttributeValue(null, key); if (value != null) { diff --git a/services/core/java/com/android/server/pm/dex/DexManager.java b/services/core/java/com/android/server/pm/dex/DexManager.java index 755c486d1be5..83dd392988bf 100644 --- a/services/core/java/com/android/server/pm/dex/DexManager.java +++ b/services/core/java/com/android/server/pm/dex/DexManager.java @@ -432,7 +432,7 @@ public class DexManager { // Ignore framework code. // TODO(calin): is there a better way to detect it? if (dexPath.startsWith("/system/framework/")) { - new DexSearchResult("framework", DEX_SEARCH_NOT_FOUND); + return new DexSearchResult("framework", DEX_SEARCH_NOT_FOUND); } // First, check if the package which loads the dex file actually owns it. diff --git a/services/core/java/com/android/server/tv/TvInputHardwareManager.java b/services/core/java/com/android/server/tv/TvInputHardwareManager.java index 8043c651751c..08eca73e1702 100644 --- a/services/core/java/com/android/server/tv/TvInputHardwareManager.java +++ b/services/core/java/com/android/server/tv/TvInputHardwareManager.java @@ -17,6 +17,7 @@ package com.android.server.tv; import static android.media.tv.TvInputManager.INPUT_STATE_CONNECTED; +import static android.media.tv.TvInputManager.INPUT_STATE_CONNECTED_STANDBY; import static android.media.tv.TvInputManager.INPUT_STATE_DISCONNECTED; import android.content.BroadcastReceiver; @@ -109,7 +110,6 @@ class TvInputHardwareManager implements TvInputHal.Callback { private int mCurrentIndex = 0; private int mCurrentMaxIndex = 0; - // TODO: Should handle STANDBY case. private final SparseBooleanArray mHdmiStateMap = new SparseBooleanArray(); private final List<Message> mPendingHdmiDeviceEvents = new LinkedList<>(); @@ -209,11 +209,13 @@ class TvInputHardwareManager implements TvInputHal.Callback { + deviceId); return; } + int previousConfigsLength = connection.getConfigsLengthLocked(); connection.updateConfigsLocked(configs); String inputId = mHardwareInputIdMap.get(deviceId); - if (inputId != null) { + if (inputId != null + && (previousConfigsLength == 0) != (connection.getConfigsLengthLocked() == 0)) { mHandler.obtainMessage(ListenerHandler.STATE_CHANGED, - convertConnectedToState(configs.length > 0), 0, inputId).sendToTarget(); + connection.getInputStateLocked(), 0, inputId).sendToTarget(); } ITvInputHardwareCallback callback = connection.getCallbackLocked(); if (callback != null) { @@ -263,14 +265,6 @@ class TvInputHardwareManager implements TvInputHal.Callback { || connectionCallingUid != callingUid || connectionResolvedUserId != resolvedUserId; } - private int convertConnectedToState(boolean connected) { - if (connected) { - return INPUT_STATE_CONNECTED; - } else { - return INPUT_STATE_DISCONNECTED; - } - } - public void addHardwareInput(int deviceId, TvInputInfo info) { synchronized (mLock) { String oldInputId = mHardwareInputIdMap.get(deviceId); @@ -293,18 +287,22 @@ class TvInputHardwareManager implements TvInputHal.Callback { } String inputId = mHardwareInputIdMap.get(hardwareInfo.getDeviceId()); if (inputId != null && inputId.equals(info.getId())) { - mHandler.obtainMessage(ListenerHandler.STATE_CHANGED, - convertConnectedToState(mHdmiStateMap.valueAt(i)), 0, - inputId).sendToTarget(); + // No HDMI hotplug does not necessarily mean disconnected, as old devices may + // not report hotplug state correctly. Using INPUT_STATE_CONNECTED_STANDBY to + // denote unknown state. + int state = mHdmiStateMap.valueAt(i) + ? INPUT_STATE_CONNECTED + : INPUT_STATE_CONNECTED_STANDBY; + mHandler.obtainMessage( + ListenerHandler.STATE_CHANGED, state, 0, inputId).sendToTarget(); return; } } - // For the rest of the devices, we can tell by the number of available streams. + // For the rest of the devices, we can tell by the cable connection status. Connection connection = mConnections.get(deviceId); if (connection != null) { mHandler.obtainMessage(ListenerHandler.STATE_CHANGED, - convertConnectedToState(connection.getConfigsLocked().length > 0), 0, - info.getId()).sendToTarget(); + connection.getInputStateLocked(), 0, info.getId()).sendToTarget(); } } } @@ -716,6 +714,26 @@ class TvInputHardwareManager implements TvInputHal.Callback { + ", mResolvedUserId: " + mResolvedUserId + " }"; } + + private int getConfigsLengthLocked() { + return mConfigs == null ? 0 : mConfigs.length; + } + + private int getInputStateLocked() { + int configsLength = getConfigsLengthLocked(); + if (configsLength > 0) { + return INPUT_STATE_CONNECTED; + } + switch (mHardwareInfo.getCableConnectionStatus()) { + case TvInputHardwareInfo.CABLE_CONNECTION_STATUS_CONNECTED: + return INPUT_STATE_CONNECTED; + case TvInputHardwareInfo.CABLE_CONNECTION_STATUS_DISCONNECTED: + return INPUT_STATE_DISCONNECTED; + case TvInputHardwareInfo.CABLE_CONNECTION_STATUS_UNKNOWN: + default: + return INPUT_STATE_CONNECTED_STANDBY; + } + } } private class TvInputHardwareImpl extends ITvInputHardware.Stub { @@ -1199,8 +1217,14 @@ class TvInputHardwareManager implements TvInputHal.Callback { if (inputId == null) { return; } - mHandler.obtainMessage(ListenerHandler.STATE_CHANGED, - convertConnectedToState(event.isConnected()), 0, inputId).sendToTarget(); + // No HDMI hotplug does not necessarily mean disconnected, as old devices may + // not report hotplug state correctly. Using INPUT_STATE_CONNECTED_STANDBY to + // denote unknown state. + int state = event.isConnected() + ? INPUT_STATE_CONNECTED + : INPUT_STATE_CONNECTED_STANDBY; + mHandler.obtainMessage( + ListenerHandler.STATE_CHANGED, state, 0, inputId).sendToTarget(); } } } diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java index e0261304f0f0..52763a179e73 100644 --- a/services/core/java/com/android/server/tv/TvInputManagerService.java +++ b/services/core/java/com/android/server/tv/TvInputManagerService.java @@ -288,7 +288,7 @@ public final class TvInputManagerService extends SystemService { userState.serviceStateMap.put(component, serviceState); updateServiceConnectionLocked(component, userId); } else { - inputList.addAll(serviceState.hardwareInputList); + inputList.addAll(serviceState.hardwareInputMap.values()); } } else { try { @@ -2105,7 +2105,7 @@ public final class TvInputManagerService extends SystemService { private final ServiceConnection connection; private final ComponentName component; private final boolean isHardware; - private final List<TvInputInfo> hardwareInputList = new ArrayList<>(); + private final Map<String, TvInputInfo> hardwareInputMap = new HashMap<>(); private ITvInputService service; private ServiceCallback callback; @@ -2216,7 +2216,7 @@ public final class TvInputManagerService extends SystemService { } if (serviceState.isHardware) { - serviceState.hardwareInputList.clear(); + serviceState.hardwareInputMap.clear(); for (TvInputHardwareInfo hardware : mTvInputHardwareManager.getHardwareList()) { try { serviceState.service.notifyHardwareAdded(hardware); @@ -2283,7 +2283,7 @@ public final class TvInputManagerService extends SystemService { private void addHardwareInputLocked(TvInputInfo inputInfo) { ServiceState serviceState = getServiceStateLocked(mComponent, mUserId); - serviceState.hardwareInputList.add(inputInfo); + serviceState.hardwareInputMap.put(inputInfo.getId(), inputInfo); buildTvInputListLocked(mUserId, null); } @@ -2309,15 +2309,7 @@ public final class TvInputManagerService extends SystemService { ensureHardwarePermission(); synchronized (mLock) { ServiceState serviceState = getServiceStateLocked(mComponent, mUserId); - boolean removed = false; - for (Iterator<TvInputInfo> it = serviceState.hardwareInputList.iterator(); - it.hasNext(); ) { - if (it.next().getId().equals(inputId)) { - it.remove(); - removed = true; - break; - } - } + boolean removed = serviceState.hardwareInputMap.remove(inputId) != null; if (removed) { buildTvInputListLocked(mUserId, null); mTvInputHardwareManager.removeHardwareInput(inputId); diff --git a/services/core/java/com/android/server/vr/CompatibilityDisplay.java b/services/core/java/com/android/server/vr/CompatibilityDisplay.java index 5e17daa0f153..8f95cc74c914 100644 --- a/services/core/java/com/android/server/vr/CompatibilityDisplay.java +++ b/services/core/java/com/android/server/vr/CompatibilityDisplay.java @@ -1,6 +1,8 @@ package com.android.server.vr; +import static android.view.Display.INVALID_DISPLAY; + import android.app.Service; import android.content.BroadcastReceiver; import android.content.Context; @@ -86,10 +88,8 @@ class CompatibilityDisplay { startVirtualDisplay(); } } else { - // TODO: Remove conditional when launching apps 2D doesn't force VrMode to stop. - if (!DEBUG) { - stopVirtualDisplay(); - } + // Stop virtual display to test exit condition + stopVirtualDisplay(); } } @@ -138,6 +138,19 @@ class CompatibilityDisplay { } } + public int getVirtualDisplayId() { + synchronized(vdLock) { + if (mVirtualDisplay != null) { + int virtualDisplayId = mVirtualDisplay.getDisplay().getDisplayId(); + if (DEBUG) { + Log.e(TAG, "VD id: " + virtualDisplayId); + } + return virtualDisplayId; + } + } + return INVALID_DISPLAY; + } + private void startVirtualDisplay() { if (DEBUG) { Log.d(TAG, "Request to start VD, DM:" + mDisplayManager); diff --git a/services/core/java/com/android/server/vr/VrManagerInternal.java b/services/core/java/com/android/server/vr/VrManagerInternal.java index 58e4bdc36e6a..210aa44c0c03 100644 --- a/services/core/java/com/android/server/vr/VrManagerInternal.java +++ b/services/core/java/com/android/server/vr/VrManagerInternal.java @@ -90,6 +90,15 @@ public abstract class VrManagerInternal { public abstract void setPersistentVrModeEnabled(boolean enabled); /** + * Return {@link android.view.Display.INVALID_DISPLAY} if there exists no virtual display + * currently or the display id of the current virtual display. + * + * @return {@link android.view.Display.INVALID_DISPLAY} if there is no virtual display + * currently, else return the display id of the virtual display + */ + public abstract int getCompatibilityDisplayId(); + + /** * Adds listener that reports state changes to persistent VR mode. */ public abstract void addPersistentVrModeStateListener(PersistentVrStateListener listener); diff --git a/services/core/java/com/android/server/vr/VrManagerService.java b/services/core/java/com/android/server/vr/VrManagerService.java index 8a23173ca208..a00115cba2a2 100644 --- a/services/core/java/com/android/server/vr/VrManagerService.java +++ b/services/core/java/com/android/server/vr/VrManagerService.java @@ -15,6 +15,8 @@ */ package com.android.server.vr; +import static android.view.Display.INVALID_DISPLAY; + import android.Manifest; import android.app.ActivityManager; import android.app.AppOpsManager; @@ -392,6 +394,11 @@ public class VrManagerService extends SystemService implements EnabledComponentC } @Override + public int getCompatibilityDisplayId() { + return VrManagerService.this.getCompatibilityDisplayId(); + } + + @Override protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { if (getContext().checkCallingOrSelfPermission(android.Manifest.permission.DUMP) != PackageManager.PERMISSION_GRANTED) { @@ -495,6 +502,11 @@ public class VrManagerService extends SystemService implements EnabledComponentC } @Override + public int getCompatibilityDisplayId() { + return VrManagerService.this.getCompatibilityDisplayId(); + } + + @Override public void addPersistentVrModeStateListener(PersistentVrStateListener listener) { VrManagerService.this.addPersistentVrModeStateListener(listener); } @@ -1054,6 +1066,14 @@ public class VrManagerService extends SystemService implements EnabledComponentC } } + private int getCompatibilityDisplayId() { + if (mCompatibilityDisplay != null) { + return mCompatibilityDisplay.getVirtualDisplayId(); + } + Slog.w(TAG, "CompatibilityDisplay is null!"); + return INVALID_DISPLAY; + } + private void setPersistentModeAndNotifyListenersLocked(boolean enabled) { if (mPersistentVrModeEnabled == enabled) { return; diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java index 1510dd126c74..5abc4e4d2e56 100644 --- a/services/core/java/com/android/server/wm/AccessibilityController.java +++ b/services/core/java/com/android/server/wm/AccessibilityController.java @@ -222,6 +222,14 @@ final class AccessibilityController { || mWindowsForAccessibilityObserver != null); } + /** NOTE: This has to be called within a surface transaction. */ + public void setForceShowMagnifiableBoundsLocked(boolean show) { + if (mDisplayMagnifier != null) { + mDisplayMagnifier.setForceShowMagnifiableBoundsLocked(show); + mDisplayMagnifier.drawMagnifiedRegionBorderIfNeededLocked(); + } + } + private static void populateTransformationMatrixLocked(WindowState windowState, Matrix outMatrix) { sTempFloats[Matrix.MSCALE_X] = windowState.mWinAnimator.mDsDx; @@ -266,6 +274,8 @@ final class AccessibilityController { private final long mLongAnimationDuration; + private boolean mForceShowMagnifiableBounds = false; + public DisplayMagnifier(WindowManagerService windowManagerService, MagnificationCallbacks callbacks) { mContext = windowManagerService.mContext; @@ -283,6 +293,15 @@ final class AccessibilityController { mWindowManagerService.scheduleAnimationLocked(); } + public void setForceShowMagnifiableBoundsLocked(boolean show) { + mForceShowMagnifiableBounds = show; + mMagnifedViewport.setMagnifiedRegionBorderShownLocked(show, true); + } + + public boolean isForceShowingMagnifiableBoundsLocked() { + return mForceShowMagnifiableBounds; + } + public void onRectangleOnScreenRequestedLocked(Rect rectangle) { if (DEBUG_RECTANGLE_REQUESTED) { Slog.i(LOG_TAG, "Rectangle on screen requested: " + rectangle); @@ -488,7 +507,8 @@ final class AccessibilityController { // to show the border. We will do so when the pending message is handled. if (!mHandler.hasMessages( MyHandler.MESSAGE_SHOW_MAGNIFIED_REGION_BOUNDS_IF_NEEDED)) { - setMagnifiedRegionBorderShownLocked(isMagnifyingLocked(), true); + setMagnifiedRegionBorderShownLocked( + isMagnifyingLocked() || isForceShowingMagnifiableBoundsLocked(), true); } } @@ -600,11 +620,11 @@ final class AccessibilityController { } public void onRotationChangedLocked() { - // If we are magnifying, hide the magnified border window immediately so + // If we are showing the magnification border, hide it immediately so // the user does not see strange artifacts during rotation. The screenshot - // used for rotation has already the border. After the rotation is complete + // used for rotation already has the border. After the rotation is complete // we will show the border. - if (isMagnifyingLocked()) { + if (isMagnifyingLocked() || isForceShowingMagnifiableBoundsLocked()) { setMagnifiedRegionBorderShownLocked(false, false); final long delay = (long) (mLongAnimationDuration * mWindowManagerService.getWindowAnimationScaleLocked()); @@ -926,7 +946,8 @@ final class AccessibilityController { case MESSAGE_SHOW_MAGNIFIED_REGION_BOUNDS_IF_NEEDED : { synchronized (mWindowManagerService.mWindowMap) { - if (mMagnifedViewport.isMagnifyingLocked()) { + if (mMagnifedViewport.isMagnifyingLocked() + || isForceShowingMagnifiableBoundsLocked()) { mMagnifedViewport.setMagnifiedRegionBorderShownLocked(true, true); mWindowManagerService.scheduleAnimationLocked(); } diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java index 4191d31d4d05..7a36da202aad 100644 --- a/services/core/java/com/android/server/wm/AppTransition.java +++ b/services/core/java/com/android/server/wm/AppTransition.java @@ -44,6 +44,7 @@ import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ANIM; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_APP_TRANSITIONS; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; +import static com.android.server.wm.WindowStateAnimator.STACK_CLIP_BEFORE_ANIM; import static com.android.server.wm.WindowStateAnimator.STACK_CLIP_NONE; import static com.android.server.wm.WindowStateAnimator.STACK_CLIP_AFTER_ANIM; @@ -692,7 +693,7 @@ public class AppTransition implements Dump { private void getDefaultNextAppTransitionStartRect(Rect rect) { if (mDefaultNextAppTransitionAnimationSpec == null || mDefaultNextAppTransitionAnimationSpec.rect == null) { - Slog.wtf(TAG, "Starting rect for app requested, but none available", new Throwable()); + Slog.e(TAG, "Starting rect for app requested, but none available", new Throwable()); rect.setEmpty(); } else { rect.set(mDefaultNextAppTransitionAnimationSpec.rect); @@ -705,7 +706,7 @@ public class AppTransition implements Dump { spec = mDefaultNextAppTransitionAnimationSpec; } if (spec == null || spec.rect == null) { - Slog.wtf(TAG, "Starting rect for task: " + taskId + " requested, but not available", + Slog.e(TAG, "Starting rect for task: " + taskId + " requested, but not available", new Throwable()); rect.setEmpty(); } else { @@ -1652,10 +1653,14 @@ public class AppTransition implements Dump { } int getAppStackClipMode() { + // When dismiss keyguard animation occurs, clip before the animation to prevent docked + // app from showing beyond the divider + if (mNextAppTransition == TRANSIT_KEYGUARD_GOING_AWAY + || mNextAppTransition == TRANSIT_KEYGUARD_GOING_AWAY_ON_WALLPAPER) { + return STACK_CLIP_BEFORE_ANIM; + } return mNextAppTransition == TRANSIT_ACTIVITY_RELAUNCH || mNextAppTransition == TRANSIT_DOCK_TASK_FROM_RECENTS - || mNextAppTransition == TRANSIT_KEYGUARD_GOING_AWAY - || mNextAppTransition == TRANSIT_KEYGUARD_GOING_AWAY_ON_WALLPAPER || mNextAppTransitionType == NEXT_TRANSIT_TYPE_CLIP_REVEAL ? STACK_CLIP_NONE : STACK_CLIP_AFTER_ANIM; diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java index 2f221df182c0..c20ee973bac5 100644 --- a/services/core/java/com/android/server/wm/AppWindowToken.java +++ b/services/core/java/com/android/server/wm/AppWindowToken.java @@ -163,9 +163,6 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree private boolean mLastContainsShowWhenLockedWindow; private boolean mLastContainsDismissKeyguardWindow; - private ArrayList<WindowSurfaceController.SurfaceControlWithBackground> mSurfaceViewBackgrounds = - new ArrayList<>(); - ArrayDeque<Rect> mFrozenBounds = new ArrayDeque<>(); ArrayDeque<Configuration> mFrozenMergedConfig = new ArrayDeque<>(); @@ -970,36 +967,6 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree mService.mWindowPlacerLocked.performSurfacePlacement(); } - void addSurfaceViewBackground(WindowSurfaceController.SurfaceControlWithBackground background) { - mSurfaceViewBackgrounds.add(background); - } - - void removeSurfaceViewBackground(WindowSurfaceController.SurfaceControlWithBackground background) { - mSurfaceViewBackgrounds.remove(background); - updateSurfaceViewBackgroundVisibilities(); - } - - // We use DimLayers behind SurfaceViews to prevent holes while resizing and creating. - // However, we need to ensure one SurfaceView doesn't cover another when they are both placed - // below the main app window (as traditionally a SurfaceView which is never drawn - // to is totally translucent). So we look at all our SurfaceView backgrounds and only enable - // the background for the SurfaceView with lowest Z order - void updateSurfaceViewBackgroundVisibilities() { - WindowSurfaceController.SurfaceControlWithBackground bottom = null; - int bottomLayer = Integer.MAX_VALUE; - for (int i = 0; i < mSurfaceViewBackgrounds.size(); i++) { - WindowSurfaceController.SurfaceControlWithBackground sc = mSurfaceViewBackgrounds.get(i); - if (sc.mVisible && sc.mLayer < bottomLayer) { - bottomLayer = sc.mLayer; - bottom = sc; - } - } - for (int i = 0; i < mSurfaceViewBackgrounds.size(); i++) { - WindowSurfaceController.SurfaceControlWithBackground sc = mSurfaceViewBackgrounds.get(i); - sc.updateBackgroundVisibility(sc != bottom); - } - } - void resetJustMovedInStack() { for (int i = mChildren.size() - 1; i >= 0; i--) { (mChildren.get(i)).resetJustMovedInStack(); diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index cb3a663f0ae7..e5b00f3df79a 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -73,18 +73,24 @@ import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_STACK; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_TOKEN_MOVEMENT; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WALLPAPER; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WALLPAPER_LIGHT; +import static com.android.server.wm.WindowManagerDebugConfig.SHOW_LIGHT_TRANSACTIONS; import static com.android.server.wm.WindowManagerDebugConfig.SHOW_STACK_CRAWLS; import static com.android.server.wm.WindowManagerDebugConfig.SHOW_TRANSACTIONS; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; +import static com.android.server.wm.WindowManagerService.CUSTOM_SCREEN_ROTATION; import static com.android.server.wm.WindowManagerService.H.SEND_NEW_CONFIGURATION; import static com.android.server.wm.WindowManagerService.H.UPDATE_DOCKED_STACK_DIVIDER; import static com.android.server.wm.WindowManagerService.H.WINDOW_HIDE_TIMEOUT; import static com.android.server.wm.WindowManagerService.LAYOUT_REPEAT_THRESHOLD; +import static com.android.server.wm.WindowManagerService.MAX_ANIMATION_DURATION; +import static com.android.server.wm.WindowManagerService.SEAMLESS_ROTATION_TIMEOUT_DURATION; import static com.android.server.wm.WindowManagerService.TYPE_LAYER_MULTIPLIER; import static com.android.server.wm.WindowManagerService.TYPE_LAYER_OFFSET; import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_WILL_PLACE_SURFACES; +import static com.android.server.wm.WindowManagerService.WINDOWS_FREEZING_SCREENS_ACTIVE; import static com.android.server.wm.WindowManagerService.WINDOWS_FREEZING_SCREENS_TIMEOUT; +import static com.android.server.wm.WindowManagerService.WINDOW_FREEZE_TIMEOUT_DURATION; import static com.android.server.wm.WindowManagerService.dipToPixel; import static com.android.server.wm.WindowManagerService.logSurface; import static com.android.server.wm.WindowState.RESIZE_HANDLE_WIDTH_IN_DP; @@ -94,6 +100,7 @@ import static com.android.server.wm.WindowSurfacePlacer.SET_WALLPAPER_MAY_CHANGE import android.annotation.NonNull; import android.app.ActivityManager.StackId; +import android.content.res.CompatibilityInfo; import android.content.res.Configuration; import android.graphics.Bitmap; import android.graphics.GraphicBuffer; @@ -113,6 +120,7 @@ import android.util.MutableBoolean; import android.util.Slog; import android.view.Display; import android.view.DisplayInfo; +import android.view.InputDevice; import android.view.Surface; import android.view.SurfaceControl; import android.view.WindowManagerPolicy; @@ -181,12 +189,27 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo private final DisplayInfo mDisplayInfo = new DisplayInfo(); private final Display mDisplay; private final DisplayMetrics mDisplayMetrics = new DisplayMetrics(); + /** + * For default display it contains real metrics, empty for others. + * @see WindowManagerService#createWatermarkInTransaction() + */ + final DisplayMetrics mRealDisplayMetrics = new DisplayMetrics(); + /** @see #computeCompatSmallestWidth(boolean, int, int, int, int) */ + private final DisplayMetrics mTmpDisplayMetrics = new DisplayMetrics(); + /** + * Compat metrics computed based on {@link #mDisplayMetrics}. + * @see #updateDisplayAndOrientation(int) + */ + private final DisplayMetrics mCompatDisplayMetrics = new DisplayMetrics(); + + /** The desired scaling factor for compatible apps. */ + float mCompatibleScreenScale; /** * Current rotation of the display. * Constants as per {@link android.view.Surface.Rotation}. * - * @see WindowManagerService#updateRotationUncheckedLocked(boolean, int) + * @see #updateRotationUnchecked(boolean) */ private int mRotation = 0; /** @@ -200,7 +223,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo * Flag indicating that the application is receiving an orientation that has different metrics * than it expected. E.g. Portrait instead of Landscape. * - * @see WindowManagerService#updateRotationUncheckedLocked(boolean, int) + * @see #updateRotationUnchecked(boolean) */ private boolean mAltOrientation = false; /** @@ -218,7 +241,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo */ private int mLastKeyguardForcedOrientation = SCREEN_ORIENTATION_UNSPECIFIED; - Rect mBaseDisplayRect = new Rect(); + private Rect mBaseDisplayRect = new Rect(); private Rect mContentRect = new Rect(); // Accessed directly by all users. @@ -828,6 +851,514 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo return mLastWindowForcedOrientation; } + /** + * Update rotation of the display. + * + * Returns true if the rotation has been changed. In this case YOU MUST CALL + * {@link WindowManagerService#sendNewConfiguration(int)} TO UNFREEZE THE SCREEN. + */ + boolean updateRotationUnchecked(boolean inTransaction) { + if (mService.mDeferredRotationPauseCount > 0) { + // Rotation updates have been paused temporarily. Defer the update until + // updates have been resumed. + if (DEBUG_ORIENTATION) Slog.v(TAG_WM, "Deferring rotation, rotation is paused."); + return false; + } + + ScreenRotationAnimation screenRotationAnimation = + mService.mAnimator.getScreenRotationAnimationLocked(mDisplayId); + if (screenRotationAnimation != null && screenRotationAnimation.isAnimating()) { + // Rotation updates cannot be performed while the previous rotation change + // animation is still in progress. Skip this update. We will try updating + // again after the animation is finished and the display is unfrozen. + if (DEBUG_ORIENTATION) Slog.v(TAG_WM, "Deferring rotation, animation in progress."); + return false; + } + if (mService.mDisplayFrozen) { + // Even if the screen rotation animation has finished (e.g. isAnimating + // returns false), there is still some time where we haven't yet unfrozen + // the display. We also need to abort rotation here. + if (DEBUG_ORIENTATION) Slog.v(TAG_WM, + "Deferring rotation, still finishing previous rotation"); + return false; + } + + if (!mService.mDisplayEnabled) { + // No point choosing a rotation if the display is not enabled. + if (DEBUG_ORIENTATION) Slog.v(TAG_WM, "Deferring rotation, display is not enabled."); + return false; + } + + final int oldRotation = mRotation; + final int lastOrientation = mLastOrientation; + final boolean oldAltOrientation = mAltOrientation; + int rotation = mService.mPolicy.rotationForOrientationLw(lastOrientation, oldRotation); + final boolean rotateSeamlessly; + + if (mService.mPolicy.shouldRotateSeamlessly(oldRotation, rotation)) { + final WindowState seamlessRotated = getWindow((w) -> w.mSeamlesslyRotated); + if (seamlessRotated != null) { + // We can't rotate (seamlessly or not) while waiting for the last seamless rotation + // to complete (that is, waiting for windows to redraw). It's tempting to check + // w.mSeamlessRotationCount but that could be incorrect in the case of + // window-removal. + return false; + } + + final WindowState cantSeamlesslyRotate = getWindow((w) -> + w.isChildWindow() && w.isVisibleNow() + && !w.mWinAnimator.mSurfaceController.getTransformToDisplayInverse()); + if (cantSeamlesslyRotate != null) { + // In what can only be called an unfortunate workaround we require seamlessly + // rotated child windows to have the TRANSFORM_TO_DISPLAY_INVERSE flag. Due to + // limitations in the client API, there is no way for the client to set this flag in + // a race free fashion. If we seamlessly rotate a window which does not have this + // flag, but then gains it, we will get an incorrect visual result + // (rotated viewfinder). This means if we want to support seamlessly rotating + // windows which could gain this flag, we can't rotate windows without it. This + // limits seamless rotation in N to camera framework users, windows without + // children, and native code. This is unfortunate but having the camera work is our + // primary goal. + rotateSeamlessly = false; + } else { + rotateSeamlessly = true; + } + } else { + rotateSeamlessly = false; + } + + // TODO: Implement forced rotation changes. + // Set mAltOrientation to indicate that the application is receiving + // an orientation that has different metrics than it expected. + // eg. Portrait instead of Landscape. + + final boolean altOrientation = !mService.mPolicy.rotationHasCompatibleMetricsLw( + lastOrientation, rotation); + + if (DEBUG_ORIENTATION) Slog.v(TAG_WM, "Selected orientation " + lastOrientation + + ", got rotation " + rotation + " which has " + + (altOrientation ? "incompatible" : "compatible") + " metrics"); + + if (oldRotation == rotation && oldAltOrientation == altOrientation) { + // No change. + return false; + } + + if (DEBUG_ORIENTATION) Slog.v(TAG_WM, "Rotation changed to " + rotation + + (altOrientation ? " (alt)" : "") + " from " + oldRotation + + (oldAltOrientation ? " (alt)" : "") + ", lastOrientation=" + lastOrientation); + + if (DisplayContent.deltaRotation(rotation, oldRotation) != 2) { + mService.mWaitingForConfig = true; + } + + mRotation = rotation; + mAltOrientation = altOrientation; + if (isDefaultDisplay) { + mService.mPolicy.setRotationLw(rotation); + } + + mService.mWindowsFreezingScreen = WINDOWS_FREEZING_SCREENS_ACTIVE; + mService.mH.removeMessages(WindowManagerService.H.WINDOW_FREEZE_TIMEOUT); + mService.mH.sendEmptyMessageDelayed(WindowManagerService.H.WINDOW_FREEZE_TIMEOUT, + WINDOW_FREEZE_TIMEOUT_DURATION); + + setLayoutNeeded(); + final int[] anim = new int[2]; + if (isDimming()) { + anim[0] = anim[1] = 0; + } else { + mService.mPolicy.selectRotationAnimationLw(anim); + } + + if (!rotateSeamlessly) { + mService.startFreezingDisplayLocked(inTransaction, anim[0], anim[1]); + // startFreezingDisplayLocked can reset the ScreenRotationAnimation. + screenRotationAnimation = mService.mAnimator.getScreenRotationAnimationLocked( + mDisplayId); + } else { + // The screen rotation animation uses a screenshot to freeze the screen + // while windows resize underneath. + // When we are rotating seamlessly, we allow the elements to transition + // to their rotated state independently and without a freeze required. + screenRotationAnimation = null; + + // We have to reset this in case a window was removed before it + // finished seamless rotation. + mService.mSeamlessRotationCount = 0; + } + + // We need to update our screen size information to match the new rotation. If the rotation + // has actually changed then this method will return true and, according to the comment at + // the top of the method, the caller is obligated to call computeNewConfigurationLocked(). + // By updating the Display info here it will be available to + // #computeScreenConfiguration() later. + updateDisplayAndOrientation(getConfiguration().uiMode); + + if (!inTransaction) { + if (SHOW_TRANSACTIONS) { + Slog.i(TAG_WM, ">>> OPEN TRANSACTION setRotationUnchecked"); + } + mService.openSurfaceTransaction(); + } + try { + // NOTE: We disable the rotation in the emulator because + // it doesn't support hardware OpenGL emulation yet. + if (CUSTOM_SCREEN_ROTATION && screenRotationAnimation != null + && screenRotationAnimation.hasScreenshot()) { + if (screenRotationAnimation.setRotationInTransaction( + rotation, mService.mFxSession, + MAX_ANIMATION_DURATION, mService.getTransitionAnimationScaleLocked(), + mDisplayInfo.logicalWidth, mDisplayInfo.logicalHeight)) { + mService.scheduleAnimationLocked(); + } + } + + if (rotateSeamlessly) { + forAllWindows(w -> { + w.mWinAnimator.seamlesslyRotateWindow(oldRotation, rotation); + }, true /* traverseTopToBottom */); + } + + mService.mDisplayManagerInternal.performTraversalInTransactionFromWindowManager(); + } finally { + if (!inTransaction) { + mService.closeSurfaceTransaction(); + if (SHOW_LIGHT_TRANSACTIONS) { + Slog.i(TAG_WM, "<<< CLOSE TRANSACTION setRotationUnchecked"); + } + } + } + + forAllWindows(w -> { + // Discard surface after orientation change, these can't be reused. + if (w.mAppToken != null) { + w.mAppToken.destroySavedSurfaces(); + } + if (w.mHasSurface && !rotateSeamlessly) { + if (DEBUG_ORIENTATION) Slog.v(TAG_WM, "Set mOrientationChanging of " + w); + w.mOrientationChanging = true; + mService.mRoot.mOrientationChangeComplete = false; + w.mLastFreezeDuration = 0; + } + w.mReportOrientationChanged = true; + }, true /* traverseTopToBottom */); + + if (rotateSeamlessly) { + mService.mH.removeMessages(WindowManagerService.H.SEAMLESS_ROTATION_TIMEOUT); + mService.mH.sendEmptyMessageDelayed(WindowManagerService.H.SEAMLESS_ROTATION_TIMEOUT, + SEAMLESS_ROTATION_TIMEOUT_DURATION); + } + + for (int i = mService.mRotationWatchers.size() - 1; i >= 0; i--) { + final WindowManagerService.RotationWatcher rotationWatcher + = mService.mRotationWatchers.get(i); + if (rotationWatcher.mDisplayId == mDisplayId) { + try { + rotationWatcher.mWatcher.onRotationChanged(rotation); + } catch (RemoteException e) { + // Ignore + } + } + } + + // TODO (multi-display): Magnification is supported only for the default display. + // Announce rotation only if we will not animate as we already have the + // windows in final state. Otherwise, we make this call at the rotation end. + if (screenRotationAnimation == null && mService.mAccessibilityController != null + && isDefaultDisplay) { + mService.mAccessibilityController.onRotationChangedLocked(this); + } + + return true; + } + + /** + * Update {@link #mDisplayInfo} and other internal variables when display is rotated or config + * changed. + * Do not call if {@link WindowManagerService#mDisplayReady} == false. + */ + private DisplayInfo updateDisplayAndOrientation(int uiMode) { + // Use the effective "visual" dimensions based on current rotation + final boolean rotated = (mRotation == ROTATION_90 || mRotation == ROTATION_270); + final int realdw = rotated ? mBaseDisplayHeight : mBaseDisplayWidth; + final int realdh = rotated ? mBaseDisplayWidth : mBaseDisplayHeight; + int dw = realdw; + int dh = realdh; + + if (mAltOrientation) { + if (realdw > realdh) { + // Turn landscape into portrait. + int maxw = (int)(realdh/1.3f); + if (maxw < realdw) { + dw = maxw; + } + } else { + // Turn portrait into landscape. + int maxh = (int)(realdw/1.3f); + if (maxh < realdh) { + dh = maxh; + } + } + } + + // Update application display metrics. + final int appWidth = mService.mPolicy.getNonDecorDisplayWidth(dw, dh, mRotation, uiMode, + mDisplayId); + final int appHeight = mService.mPolicy.getNonDecorDisplayHeight(dw, dh, mRotation, uiMode, + mDisplayId); + mDisplayInfo.rotation = mRotation; + mDisplayInfo.logicalWidth = dw; + mDisplayInfo.logicalHeight = dh; + mDisplayInfo.logicalDensityDpi = mBaseDisplayDensity; + mDisplayInfo.appWidth = appWidth; + mDisplayInfo.appHeight = appHeight; + if (isDefaultDisplay) { + mDisplayInfo.getLogicalMetrics(mRealDisplayMetrics, + CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null); + } + mDisplayInfo.getAppMetrics(mDisplayMetrics); + if (mDisplayScalingDisabled) { + mDisplayInfo.flags |= Display.FLAG_SCALING_DISABLED; + } else { + mDisplayInfo.flags &= ~Display.FLAG_SCALING_DISABLED; + } + + mService.mDisplayManagerInternal.setDisplayInfoOverrideFromWindowManager(mDisplayId, + mDisplayInfo); + + mBaseDisplayRect.set(0, 0, dw, dh); + + if (isDefaultDisplay) { + mCompatibleScreenScale = CompatibilityInfo.computeCompatibleScaling(mDisplayMetrics, + mCompatDisplayMetrics); + } + return mDisplayInfo; + } + + /** + * Compute display configuration based on display properties and policy settings. + * Do not call if mDisplayReady == false. + */ + void computeScreenConfiguration(Configuration config) { + final DisplayInfo displayInfo = updateDisplayAndOrientation(config.uiMode); + + final int dw = displayInfo.logicalWidth; + final int dh = displayInfo.logicalHeight; + config.orientation = (dw <= dh) ? Configuration.ORIENTATION_PORTRAIT : + Configuration.ORIENTATION_LANDSCAPE; + config.screenWidthDp = + (int)(mService.mPolicy.getConfigDisplayWidth(dw, dh, displayInfo.rotation, + config.uiMode, mDisplayId) / mDisplayMetrics.density); + config.screenHeightDp = + (int)(mService.mPolicy.getConfigDisplayHeight(dw, dh, displayInfo.rotation, + config.uiMode, mDisplayId) / mDisplayMetrics.density); + final boolean rotated = (displayInfo.rotation == Surface.ROTATION_90 + || displayInfo.rotation == Surface.ROTATION_270); + + computeSizeRangesAndScreenLayout(displayInfo, mDisplayId, rotated, config.uiMode, dw, dh, + mDisplayMetrics.density, config); + + config.screenLayout = (config.screenLayout & ~Configuration.SCREENLAYOUT_ROUND_MASK) + | ((displayInfo.flags & Display.FLAG_ROUND) != 0 + ? Configuration.SCREENLAYOUT_ROUND_YES + : Configuration.SCREENLAYOUT_ROUND_NO); + + config.compatScreenWidthDp = (int)(config.screenWidthDp / mCompatibleScreenScale); + config.compatScreenHeightDp = (int)(config.screenHeightDp / mCompatibleScreenScale); + config.compatSmallestScreenWidthDp = computeCompatSmallestWidth(rotated, config.uiMode, dw, + dh, mDisplayId); + config.densityDpi = displayInfo.logicalDensityDpi; + + config.colorMode = + (displayInfo.isHdr() + ? Configuration.COLOR_MODE_HDR_YES + : Configuration.COLOR_MODE_HDR_NO) + | (displayInfo.isWideColorGamut() + ? Configuration.COLOR_MODE_WIDE_COLOR_GAMUT_YES + : Configuration.COLOR_MODE_WIDE_COLOR_GAMUT_NO); + + // Update the configuration based on available input devices, lid switch, + // and platform configuration. + config.touchscreen = Configuration.TOUCHSCREEN_NOTOUCH; + config.keyboard = Configuration.KEYBOARD_NOKEYS; + config.navigation = Configuration.NAVIGATION_NONAV; + + int keyboardPresence = 0; + int navigationPresence = 0; + final InputDevice[] devices = mService.mInputManager.getInputDevices(); + final int len = devices != null ? devices.length : 0; + for (int i = 0; i < len; i++) { + InputDevice device = devices[i]; + if (!device.isVirtual()) { + final int sources = device.getSources(); + final int presenceFlag = device.isExternal() ? + WindowManagerPolicy.PRESENCE_EXTERNAL : + WindowManagerPolicy.PRESENCE_INTERNAL; + + // TODO(multi-display): Configure on per-display basis. + if (mService.mIsTouchDevice) { + if ((sources & InputDevice.SOURCE_TOUCHSCREEN) == + InputDevice.SOURCE_TOUCHSCREEN) { + config.touchscreen = Configuration.TOUCHSCREEN_FINGER; + } + } else { + config.touchscreen = Configuration.TOUCHSCREEN_NOTOUCH; + } + + if ((sources & InputDevice.SOURCE_TRACKBALL) == InputDevice.SOURCE_TRACKBALL) { + config.navigation = Configuration.NAVIGATION_TRACKBALL; + navigationPresence |= presenceFlag; + } else if ((sources & InputDevice.SOURCE_DPAD) == InputDevice.SOURCE_DPAD + && config.navigation == Configuration.NAVIGATION_NONAV) { + config.navigation = Configuration.NAVIGATION_DPAD; + navigationPresence |= presenceFlag; + } + + if (device.getKeyboardType() == InputDevice.KEYBOARD_TYPE_ALPHABETIC) { + config.keyboard = Configuration.KEYBOARD_QWERTY; + keyboardPresence |= presenceFlag; + } + } + } + + if (config.navigation == Configuration.NAVIGATION_NONAV && mService.mHasPermanentDpad) { + config.navigation = Configuration.NAVIGATION_DPAD; + navigationPresence |= WindowManagerPolicy.PRESENCE_INTERNAL; + } + + // Determine whether a hard keyboard is available and enabled. + // TODO(multi-display): Should the hardware keyboard be tied to a display or to a device? + boolean hardKeyboardAvailable = config.keyboard != Configuration.KEYBOARD_NOKEYS; + if (hardKeyboardAvailable != mService.mHardKeyboardAvailable) { + mService.mHardKeyboardAvailable = hardKeyboardAvailable; + mService.mH.removeMessages(WindowManagerService.H.REPORT_HARD_KEYBOARD_STATUS_CHANGE); + mService.mH.sendEmptyMessage(WindowManagerService.H.REPORT_HARD_KEYBOARD_STATUS_CHANGE); + } + + // Let the policy update hidden states. + config.keyboardHidden = Configuration.KEYBOARDHIDDEN_NO; + config.hardKeyboardHidden = Configuration.HARDKEYBOARDHIDDEN_NO; + config.navigationHidden = Configuration.NAVIGATIONHIDDEN_NO; + mService.mPolicy.adjustConfigurationLw(config, keyboardPresence, navigationPresence); + } + + private int computeCompatSmallestWidth(boolean rotated, int uiMode, int dw, int dh, + int displayId) { + mTmpDisplayMetrics.setTo(mDisplayMetrics); + final DisplayMetrics tmpDm = mTmpDisplayMetrics; + final int unrotDw, unrotDh; + if (rotated) { + unrotDw = dh; + unrotDh = dw; + } else { + unrotDw = dw; + unrotDh = dh; + } + int sw = reduceCompatConfigWidthSize(0, Surface.ROTATION_0, uiMode, tmpDm, unrotDw, unrotDh, + displayId); + sw = reduceCompatConfigWidthSize(sw, Surface.ROTATION_90, uiMode, tmpDm, unrotDh, unrotDw, + displayId); + sw = reduceCompatConfigWidthSize(sw, Surface.ROTATION_180, uiMode, tmpDm, unrotDw, unrotDh, + displayId); + sw = reduceCompatConfigWidthSize(sw, Surface.ROTATION_270, uiMode, tmpDm, unrotDh, unrotDw, + displayId); + return sw; + } + + private int reduceCompatConfigWidthSize(int curSize, int rotation, int uiMode, + DisplayMetrics dm, int dw, int dh, int displayId) { + dm.noncompatWidthPixels = mService.mPolicy.getNonDecorDisplayWidth(dw, dh, rotation, uiMode, + displayId); + dm.noncompatHeightPixels = mService.mPolicy.getNonDecorDisplayHeight(dw, dh, rotation, + uiMode, displayId); + float scale = CompatibilityInfo.computeCompatibleScaling(dm, null); + int size = (int)(((dm.noncompatWidthPixels / scale) / dm.density) + .5f); + if (curSize == 0 || size < curSize) { + curSize = size; + } + return curSize; + } + + private void computeSizeRangesAndScreenLayout(DisplayInfo displayInfo, int displayId, + boolean rotated, int uiMode, int dw, int dh, float density, Configuration outConfig) { + + // We need to determine the smallest width that will occur under normal + // operation. To this, start with the base screen size and compute the + // width under the different possible rotations. We need to un-rotate + // the current screen dimensions before doing this. + int unrotDw, unrotDh; + if (rotated) { + unrotDw = dh; + unrotDh = dw; + } else { + unrotDw = dw; + unrotDh = dh; + } + displayInfo.smallestNominalAppWidth = 1<<30; + displayInfo.smallestNominalAppHeight = 1<<30; + displayInfo.largestNominalAppWidth = 0; + displayInfo.largestNominalAppHeight = 0; + adjustDisplaySizeRanges(displayInfo, displayId, Surface.ROTATION_0, uiMode, unrotDw, + unrotDh); + adjustDisplaySizeRanges(displayInfo, displayId, Surface.ROTATION_90, uiMode, unrotDh, + unrotDw); + adjustDisplaySizeRanges(displayInfo, displayId, Surface.ROTATION_180, uiMode, unrotDw, + unrotDh); + adjustDisplaySizeRanges(displayInfo, displayId, Surface.ROTATION_270, uiMode, unrotDh, + unrotDw); + int sl = Configuration.resetScreenLayout(outConfig.screenLayout); + sl = reduceConfigLayout(sl, Surface.ROTATION_0, density, unrotDw, unrotDh, uiMode, + displayId); + sl = reduceConfigLayout(sl, Surface.ROTATION_90, density, unrotDh, unrotDw, uiMode, + displayId); + sl = reduceConfigLayout(sl, Surface.ROTATION_180, density, unrotDw, unrotDh, uiMode, + displayId); + sl = reduceConfigLayout(sl, Surface.ROTATION_270, density, unrotDh, unrotDw, uiMode, + displayId); + outConfig.smallestScreenWidthDp = (int)(displayInfo.smallestNominalAppWidth / density); + outConfig.screenLayout = sl; + } + + private int reduceConfigLayout(int curLayout, int rotation, float density, int dw, int dh, + int uiMode, int displayId) { + // Get the app screen size at this rotation. + int w = mService.mPolicy.getNonDecorDisplayWidth(dw, dh, rotation, uiMode, displayId); + int h = mService.mPolicy.getNonDecorDisplayHeight(dw, dh, rotation, uiMode, displayId); + + // Compute the screen layout size class for this rotation. + int longSize = w; + int shortSize = h; + if (longSize < shortSize) { + int tmp = longSize; + longSize = shortSize; + shortSize = tmp; + } + longSize = (int)(longSize/density); + shortSize = (int)(shortSize/density); + return Configuration.reduceScreenLayout(curLayout, longSize, shortSize); + } + + private void adjustDisplaySizeRanges(DisplayInfo displayInfo, int displayId, int rotation, + int uiMode, int dw, int dh) { + final int width = mService.mPolicy.getConfigDisplayWidth(dw, dh, rotation, uiMode, + displayId); + if (width < displayInfo.smallestNominalAppWidth) { + displayInfo.smallestNominalAppWidth = width; + } + if (width > displayInfo.largestNominalAppWidth) { + displayInfo.largestNominalAppWidth = width; + } + final int height = mService.mPolicy.getConfigDisplayHeight(dw, dh, rotation, uiMode, + displayId); + if (height < displayInfo.smallestNominalAppHeight) { + displayInfo.smallestNominalAppHeight = height; + } + if (height > displayInfo.largestNominalAppHeight) { + displayInfo.largestNominalAppHeight = height; + } + } + DockedStackDividerController getDockedDividerController() { return mDividerControllerLocked; } diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index 0222b3d0d0f9..68d0f2496d5a 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -55,13 +55,11 @@ import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM; import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT; import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER; -import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ADD_REMOVE; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_DISPLAY; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_KEEP_SCREEN_ON; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYOUT_REPEATS; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ORIENTATION; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_POWER; -import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_TOKEN_MOVEMENT; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_VISIBILITY; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WALLPAPER_LIGHT; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WINDOW_TRACE; @@ -740,7 +738,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent> { if (DEBUG_ORIENTATION) Slog.d(TAG, "Performing post-rotate rotation"); // TODO(multi-display): Update rotation for different displays separately. final int displayId = defaultDisplay.getDisplayId(); - if (mService.updateRotationUncheckedLocked(false, displayId)) { + if (defaultDisplay.updateRotationUnchecked(false /* inTransaction */)) { mService.mH.obtainMessage(SEND_NEW_CONFIGURATION, displayId).sendToTarget(); } else { mUpdateRotation = false; diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java index b7a9e663ab0f..30e0dedd7c99 100644 --- a/services/core/java/com/android/server/wm/Session.java +++ b/services/core/java/com/android/server/wm/Session.java @@ -28,7 +28,6 @@ import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; import android.content.ClipData; import android.content.Context; -import android.content.res.Configuration; import android.graphics.Rect; import android.graphics.Region; import android.os.Binder; @@ -39,6 +38,7 @@ import android.os.Process; import android.os.RemoteException; import android.os.ServiceManager; import android.os.UserHandle; +import android.util.MergedConfiguration; import android.util.Slog; import android.view.Display; import android.view.IWindow; @@ -208,13 +208,6 @@ public class Session extends IWindowSession.Stub } @Override - public void repositionChild(IWindow window, int left, int top, int right, int bottom, - long deferTransactionUntilFrame, Rect outFrame) { - mService.repositionChild(this, window, left, top, right, bottom, - deferTransactionUntilFrame, outFrame); - } - - @Override public void prepareToReplaceWindows(IBinder appToken, boolean childrenOnly) { mService.setWillReplaceWindows(appToken, childrenOnly); } @@ -223,22 +216,18 @@ public class Session extends IWindowSession.Stub int requestedWidth, int requestedHeight, int viewFlags, int flags, Rect outFrame, Rect outOverscanInsets, Rect outContentInsets, Rect outVisibleInsets, Rect outStableInsets, Rect outsets, Rect outBackdropFrame, - Configuration outConfig, Surface outSurface) { + MergedConfiguration mergedConfiguration, Surface outSurface) { if (false) Slog.d(TAG_WM, ">>>>>> ENTERED relayout from " + Binder.getCallingPid()); int res = mService.relayoutWindow(this, window, seq, attrs, requestedWidth, requestedHeight, viewFlags, flags, outFrame, outOverscanInsets, outContentInsets, outVisibleInsets, - outStableInsets, outsets, outBackdropFrame, outConfig, outSurface); + outStableInsets, outsets, outBackdropFrame, mergedConfiguration, outSurface); if (false) Slog.d(TAG_WM, "<<<<<< EXITING relayout to " + Binder.getCallingPid()); return res; } - public void performDeferredDestroy(IWindow window) { - mService.performDeferredDestroyWindow(this, window); - } - public boolean outOfMemory(IWindow window) { return mService.outOfMemoryWindow(this, window); } diff --git a/services/core/java/com/android/server/wm/TaskSnapshotSurface.java b/services/core/java/com/android/server/wm/TaskSnapshotSurface.java index c0598ca0238d..04403e2712c1 100644 --- a/services/core/java/com/android/server/wm/TaskSnapshotSurface.java +++ b/services/core/java/com/android/server/wm/TaskSnapshotSurface.java @@ -27,7 +27,6 @@ import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; import android.app.ActivityManager.TaskDescription; -import android.content.res.Configuration; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Color; @@ -38,6 +37,7 @@ import android.os.Handler; import android.os.Looper; import android.os.Message; import android.os.RemoteException; +import android.util.MergedConfiguration; import android.util.Slog; import android.view.IWindowSession; import android.view.Surface; @@ -78,7 +78,7 @@ class TaskSnapshotSurface implements StartingSurface { final Surface surface = new Surface(); final Rect tmpRect = new Rect(); final Rect tmpFrame = new Rect(); - final Configuration tmpConfiguration = new Configuration(); + final MergedConfiguration tmpMergedConfiguration = new MergedConfiguration(); int fillBackgroundColor = Color.WHITE; synchronized (service.mWindowMap) { layoutParams.type = TYPE_APPLICATION_STARTING; @@ -122,7 +122,7 @@ class TaskSnapshotSurface implements StartingSurface { window.setOuter(snapshotSurface); try { session.relayout(window, window.mSeq, layoutParams, -1, -1, View.VISIBLE, 0, tmpFrame, - tmpRect, tmpRect, tmpRect, tmpRect, tmpRect, tmpRect, tmpConfiguration, + tmpRect, tmpRect, tmpRect, tmpRect, tmpRect, tmpRect, tmpMergedConfiguration, surface); } catch (RemoteException e) { // Local call. @@ -221,9 +221,9 @@ class TaskSnapshotSurface implements StartingSurface { @Override public void resized(Rect frame, Rect overscanInsets, Rect contentInsets, Rect visibleInsets, - Rect stableInsets, Rect outsets, boolean reportDraw, Configuration newConfig, - Rect backDropFrame, boolean forceLayout, boolean alwaysConsumeNavBar, - int displayId) { + Rect stableInsets, Rect outsets, boolean reportDraw, + MergedConfiguration mergedConfiguration, Rect backDropFrame, boolean forceLayout, + boolean alwaysConsumeNavBar, int displayId) { if (reportDraw) { sHandler.obtainMessage(MSG_REPORT_DRAW, mOuter).sendToTarget(); } diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 64614fe6e03e..7539cd4b8cab 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -111,7 +111,6 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; -import android.content.res.CompatibilityInfo; import android.content.res.Configuration; import android.database.ContentObserver; import android.graphics.Bitmap; @@ -151,6 +150,7 @@ import android.os.UserHandle; import android.os.WorkSource; import android.provider.Settings; import android.util.ArraySet; +import android.util.MergedConfiguration; import android.util.DisplayMetrics; import android.util.EventLog; import android.util.Log; @@ -592,11 +592,6 @@ public class WindowManagerService extends IWindowManager.Stub boolean mIsTouchDevice; - final DisplayMetrics mDisplayMetrics = new DisplayMetrics(); - final DisplayMetrics mRealDisplayMetrics = new DisplayMetrics(); - final DisplayMetrics mTmpDisplayMetrics = new DisplayMetrics(); - final DisplayMetrics mCompatDisplayMetrics = new DisplayMetrics(); - final H mH = new H(); final Choreographer mChoreographer = Choreographer.getInstance(); @@ -841,9 +836,6 @@ public class WindowManagerService extends IWindowManager.Stub final Configuration mTempConfiguration = new Configuration(); - // The desired scaling factor for compatible apps. - float mCompatibleScreenScale; - // If true, only the core apps and services are being launched because the device // is in a special boot mode, such as being encrypted or waiting for a decryption password. // For example, when this flag is true, there will be no wallpaper service. @@ -1817,70 +1809,12 @@ public class WindowManagerService extends IWindowManager.Stub } } - void repositionChild(Session session, IWindow client, - int left, int top, int right, int bottom, - long frameNumber, Rect outFrame) { - Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "repositionChild"); - long origId = Binder.clearCallingIdentity(); - - try { - synchronized(mWindowMap) { - WindowState win = windowForClientLocked(session, client, false); - if (win == null) { - return; - } - if (!win.isChildWindow()) { - throw new IllegalArgumentException( - "repositionChild called but window is not" - + "attached to a parent win=" + win); - } - - win.mAttrs.x = left; - win.mAttrs.y = top; - win.mAttrs.width = right - left; - win.mAttrs.height = bottom - top; - win.setWindowScale(win.mRequestedWidth, win.mRequestedHeight); - - if (win.mHasSurface) { - if (SHOW_TRANSACTIONS) { - Slog.i(TAG_WM, ">>> OPEN TRANSACTION repositionChild"); - } - - openSurfaceTransaction(); - - try { - - win.applyGravityAndUpdateFrame(win.mContainingFrame, win.mDisplayFrame); - win.mWinAnimator.computeShownFrameLocked(); - - win.mWinAnimator.setSurfaceBoundariesLocked(false); - - if (frameNumber > 0) { - win.mWinAnimator.deferTransactionUntilParentFrame(frameNumber); - } - - } finally { - closeSurfaceTransaction(); - if (SHOW_TRANSACTIONS) { - Slog.i(TAG_WM, "<<< CLOSE TRANSACTION repositionChild"); - } - } - } - - outFrame = win.mCompatFrame; - } - } finally { - Binder.restoreCallingIdentity(origId); - Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER); - } - } - public int relayoutWindow(Session session, IWindow client, int seq, WindowManager.LayoutParams attrs, int requestedWidth, int requestedHeight, int viewVisibility, int flags, Rect outFrame, Rect outOverscanInsets, Rect outContentInsets, Rect outVisibleInsets, Rect outStableInsets, Rect outOutsets, Rect outBackdropFrame, - Configuration outConfig, Surface outSurface) { + MergedConfiguration mergedConfiguration, Surface outSurface) { int result = 0; boolean configChanged; boolean hasStatusBarPermission = @@ -1992,7 +1926,7 @@ public class WindowManagerService extends IWindowManager.Stub if (viewVisibility == View.VISIBLE && (win.mAppToken == null || win.mAttrs.type == TYPE_APPLICATION_STARTING || !win.mAppToken.clientHidden)) { - result = win.relayoutVisibleWindow(outConfig, result, attrChanges, + result = win.relayoutVisibleWindow(mergedConfiguration, result, attrChanges, oldVisibility); try { result = createSurfaceControl(outSurface, result, win, winAnimator); @@ -2219,23 +2153,6 @@ public class WindowManagerService extends IWindowManager.Stub return result; } - public void performDeferredDestroyWindow(Session session, IWindow client) { - long origId = Binder.clearCallingIdentity(); - - try { - synchronized (mWindowMap) { - WindowState win = windowForClientLocked(session, client, false); - if (win == null || win.mWillReplaceWindow) { - return; - } - - win.mWinAnimator.destroyDeferredSurfaceLocked(); - } - } finally { - Binder.restoreCallingIdentity(origId); - } - } - public boolean outOfMemoryWindow(Session session, IWindow client) { final long origId = Binder.clearCallingIdentity(); @@ -2461,10 +2378,10 @@ public class WindowManagerService extends IWindowManager.Stub // to keep override configs clear of non-empty values (e.g. fontSize). mTempConfiguration.unset(); mTempConfiguration.updateFrom(currentConfig); - computeScreenConfigurationLocked(mTempConfiguration, displayId); + final DisplayContent displayContent = mRoot.getDisplayContent(displayId); + displayContent.computeScreenConfiguration(mTempConfiguration); if (currentConfig.diff(mTempConfiguration) != 0) { mWaitingForConfig = true; - final DisplayContent displayContent = mRoot.getDisplayContent(displayId); displayContent.setLayoutNeeded(); int anim[] = new int[2]; if (displayContent.isDimming()) { @@ -2505,7 +2422,7 @@ public class WindowManagerService extends IWindowManager.Stub if (dc.isDefaultDisplay) { mPolicy.setCurrentOrientationLw(req); } - if (updateRotationUncheckedLocked(inTransaction, displayId)) { + if (dc.updateRotationUnchecked(inTransaction)) { // changed return true; } @@ -3843,10 +3760,12 @@ public class WindowManagerService extends IWindowManager.Stub mDeferredRotationPauseCount -= 1; if (mDeferredRotationPauseCount == 0) { // TODO(multi-display): Update rotation for different displays separately. - final int displayId = DEFAULT_DISPLAY; - final boolean changed = updateRotationUncheckedLocked(false, displayId); + final DisplayContent displayContent = getDefaultDisplayContentLocked(); + final boolean changed = displayContent.updateRotationUnchecked( + false /* inTransaction */); if (changed) { - mH.obtainMessage(H.SEND_NEW_CONFIGURATION, displayId).sendToTarget(); + mH.obtainMessage(H.SEND_NEW_CONFIGURATION, displayContent.getDisplayId()) + .sendToTarget(); } } } @@ -3862,9 +3781,10 @@ public class WindowManagerService extends IWindowManager.Stub try { final boolean rotationChanged; // TODO(multi-display): Update rotation for different displays separately. - int displayId = DEFAULT_DISPLAY; + final DisplayContent displayContent = getDefaultDisplayContentLocked(); synchronized (mWindowMap) { - rotationChanged = updateRotationUncheckedLocked(false, displayId); + rotationChanged = displayContent.updateRotationUnchecked( + false /* inTransaction */); if (!rotationChanged || forceRelayout) { getDefaultDisplayContentLocked().setLayoutNeeded(); mWindowPlacerLocked.performSurfacePlacement(); @@ -3872,234 +3792,13 @@ public class WindowManagerService extends IWindowManager.Stub } if (rotationChanged || alwaysSendConfiguration) { - sendNewConfiguration(displayId); + sendNewConfiguration(displayContent.getDisplayId()); } } finally { Binder.restoreCallingIdentity(origId); } } - - // TODO(multidisplay): Rotate any display? Move to DisplayContent - /** - * Updates the current rotation of the specified display. - * - * Returns true if the rotation has been changed. In this case YOU MUST CALL - * {@link #sendNewConfiguration(int)} TO UNFREEZE THE SCREEN. - */ - boolean updateRotationUncheckedLocked(boolean inTransaction, int displayId) { - if (mDeferredRotationPauseCount > 0) { - // Rotation updates have been paused temporarily. Defer the update until - // updates have been resumed. - if (DEBUG_ORIENTATION) Slog.v(TAG_WM, "Deferring rotation, rotation is paused."); - return false; - } - - ScreenRotationAnimation screenRotationAnimation = - mAnimator.getScreenRotationAnimationLocked(displayId); - if (screenRotationAnimation != null && screenRotationAnimation.isAnimating()) { - // Rotation updates cannot be performed while the previous rotation change - // animation is still in progress. Skip this update. We will try updating - // again after the animation is finished and the display is unfrozen. - if (DEBUG_ORIENTATION) Slog.v(TAG_WM, "Deferring rotation, animation in progress."); - return false; - } - if (mDisplayFrozen) { - // Even if the screen rotation animation has finished (e.g. isAnimating - // returns false), there is still some time where we haven't yet unfrozen - // the display. We also need to abort rotation here. - if (DEBUG_ORIENTATION) Slog.v(TAG_WM, - "Deferring rotation, still finishing previous rotation"); - return false; - } - - if (!mDisplayEnabled) { - // No point choosing a rotation if the display is not enabled. - if (DEBUG_ORIENTATION) Slog.v(TAG_WM, "Deferring rotation, display is not enabled."); - return false; - } - - final DisplayContent dc = mRoot.getDisplayContent(displayId); - - final int oldRotation = dc.getRotation(); - final int lastOrientation = dc.getLastOrientation(); - final boolean oldAltOrientation = dc.getAltOrientation(); - int rotation = mPolicy.rotationForOrientationLw(lastOrientation, oldRotation); - final boolean rotateSeamlessly; - - if (mPolicy.shouldRotateSeamlessly(oldRotation, rotation)) { - final WindowState seamlessRotated = dc.getWindow((w) -> w.mSeamlesslyRotated); - if (seamlessRotated != null) { - // We can't rotate (seamlessly or not) while waiting for the last seamless rotation - // to complete (that is, waiting for windows to redraw). It's tempting to check - // w.mSeamlessRotationCount but that could be incorrect in the case of - // window-removal. - return false; - } - - final WindowState cantSeamlesslyRotate = dc.getWindow((w) -> - w.isChildWindow() && w.isVisibleNow() - && !w.mWinAnimator.mSurfaceController.getTransformToDisplayInverse()); - if (cantSeamlesslyRotate != null) { - // In what can only be called an unfortunate workaround we require seamlessly - // rotated child windows to have the TRANSFORM_TO_DISPLAY_INVERSE flag. Due to - // limitations in the client API, there is no way for the client to set this flag in - // a race free fashion. If we seamlessly rotate a window which does not have this - // flag, but then gains it, we will get an incorrect visual result - // (rotated viewfinder). This means if we want to support seamlessly rotating - // windows which could gain this flag, we can't rotate windows without it. This - // limits seamless rotation in N to camera framework users, windows without - // children, and native code. This is unfortunate but having the camera work is our - // primary goal. - rotateSeamlessly = false; - } else { - rotateSeamlessly = true; - } - } else { - rotateSeamlessly = false; - } - - // TODO: Implement forced rotation changes. - // Set mAltOrientation to indicate that the application is receiving - // an orientation that has different metrics than it expected. - // eg. Portrait instead of Landscape. - - boolean altOrientation = !mPolicy.rotationHasCompatibleMetricsLw(lastOrientation, rotation); - - if (DEBUG_ORIENTATION) Slog.v(TAG_WM, "Selected orientation " + lastOrientation - + ", got rotation " + rotation + " which has " - + (altOrientation ? "incompatible" : "compatible") + " metrics"); - - if (oldRotation == rotation && oldAltOrientation == altOrientation) { - // No change. - return false; - } - - if (DEBUG_ORIENTATION) Slog.v(TAG_WM, "Rotation changed to " + rotation - + (altOrientation ? " (alt)" : "") + " from " + oldRotation - + (oldAltOrientation ? " (alt)" : "") + ", lastOrientation=" + lastOrientation); - - if (DisplayContent.deltaRotation(rotation, oldRotation) != 2) { - mWaitingForConfig = true; - } - - dc.setRotation(rotation); - dc.setAltOrientation(altOrientation); - if (dc.isDefaultDisplay) { - mPolicy.setRotationLw(rotation); - } - - mWindowsFreezingScreen = WINDOWS_FREEZING_SCREENS_ACTIVE; - mH.removeMessages(H.WINDOW_FREEZE_TIMEOUT); - mH.sendEmptyMessageDelayed(H.WINDOW_FREEZE_TIMEOUT, WINDOW_FREEZE_TIMEOUT_DURATION); - - dc.setLayoutNeeded(); - final int[] anim = new int[2]; - if (dc.isDimming()) { - anim[0] = anim[1] = 0; - } else { - mPolicy.selectRotationAnimationLw(anim); - } - - if (!rotateSeamlessly) { - startFreezingDisplayLocked(inTransaction, anim[0], anim[1]); - // startFreezingDisplayLocked can reset the ScreenRotationAnimation. - screenRotationAnimation = mAnimator.getScreenRotationAnimationLocked(displayId); - } else { - // The screen rotation animation uses a screenshot to freeze the screen - // while windows resize underneath. - // When we are rotating seamlessly, we allow the elements to transition - // to their rotated state independently and without a freeze required. - screenRotationAnimation = null; - - // We have to reset this in case a window was removed before it - // finished seamless rotation. - mSeamlessRotationCount = 0; - } - - // We need to update our screen size information to match the new rotation. If the rotation - // has actually changed then this method will return true and, according to the comment at - // the top of the method, the caller is obligated to call computeNewConfigurationLocked(). - // By updating the Display info here it will be available to - // computeScreenConfigurationLocked later. - updateDisplayAndOrientationLocked(dc.getConfiguration().uiMode, displayId); - - final DisplayInfo displayInfo = dc.getDisplayInfo(); - if (!inTransaction) { - if (SHOW_TRANSACTIONS) { - Slog.i(TAG_WM, ">>> OPEN TRANSACTION setRotationUnchecked"); - } - openSurfaceTransaction(); - } - try { - // NOTE: We disable the rotation in the emulator because - // it doesn't support hardware OpenGL emulation yet. - if (CUSTOM_SCREEN_ROTATION && screenRotationAnimation != null - && screenRotationAnimation.hasScreenshot()) { - if (screenRotationAnimation.setRotationInTransaction( - rotation, mFxSession, - MAX_ANIMATION_DURATION, getTransitionAnimationScaleLocked(), - displayInfo.logicalWidth, displayInfo.logicalHeight)) { - scheduleAnimationLocked(); - } - } - - if (rotateSeamlessly) { - dc.forAllWindows(w -> { - w.mWinAnimator.seamlesslyRotateWindow(oldRotation, rotation); - }, true /* traverseTopToBottom */); - } - - mDisplayManagerInternal.performTraversalInTransactionFromWindowManager(); - } finally { - if (!inTransaction) { - closeSurfaceTransaction(); - if (SHOW_LIGHT_TRANSACTIONS) { - Slog.i(TAG_WM, "<<< CLOSE TRANSACTION setRotationUnchecked"); - } - } - } - - dc.forAllWindows(w -> { - // Discard surface after orientation change, these can't be reused. - if (w.mAppToken != null) { - w.mAppToken.destroySavedSurfaces(); - } - if (w.mHasSurface && !rotateSeamlessly) { - if (DEBUG_ORIENTATION) Slog.v(TAG_WM, "Set mOrientationChanging of " + w); - w.mOrientationChanging = true; - mRoot.mOrientationChangeComplete = false; - w.mLastFreezeDuration = 0; - } - w.mReportOrientationChanged = true; - }, true /* traverseTopToBottom */); - - if (rotateSeamlessly) { - mH.removeMessages(H.SEAMLESS_ROTATION_TIMEOUT); - mH.sendEmptyMessageDelayed(H.SEAMLESS_ROTATION_TIMEOUT, SEAMLESS_ROTATION_TIMEOUT_DURATION); - } - - for (int i = mRotationWatchers.size() - 1; i >= 0; i--) { - final RotationWatcher rotationWatcher = mRotationWatchers.get(i); - if (rotationWatcher.mDisplayId == displayId) { - try { - rotationWatcher.mWatcher.onRotationChanged(rotation); - } catch (RemoteException e) { - } - } - } - - // TODO (multidisplay): Magnification is supported only for the default display. - // Announce rotation only if we will not animate as we already have the - // windows in final state. Otherwise, we make this call at the rotation end. - if (screenRotationAnimation == null && mAccessibilityController != null - && dc.getDisplayId() == DEFAULT_DISPLAY) { - mAccessibilityController.onRotationChangedLocked(getDefaultDisplayContentLocked()); - } - - return true; - } - @Override public int getDefaultDisplayRotation() { synchronized (mWindowMap) { @@ -4572,7 +4271,7 @@ public class WindowManagerService extends IWindowManager.Stub // Something changed (E.g. device rotation), but no configuration update is needed. // E.g. changing device rotation by 180 degrees. Go ahead and perform surface // placement to unfreeze the display since we froze it when the rotation was updated - // in updateRotationUncheckedLocked. + // in DisplayContent#updateRotationUnchecked. synchronized (mWindowMap) { if (mWaitingForConfig) { mWaitingForConfig = false; @@ -4597,297 +4296,9 @@ public class WindowManagerService extends IWindowManager.Stub return null; } final Configuration config = new Configuration(); - computeScreenConfigurationLocked(config, displayId); - return config; - } - - private void adjustDisplaySizeRanges(DisplayInfo displayInfo, int displayId, int rotation, - int uiMode, int dw, int dh) { - final int width = mPolicy.getConfigDisplayWidth(dw, dh, rotation, uiMode, displayId); - if (width < displayInfo.smallestNominalAppWidth) { - displayInfo.smallestNominalAppWidth = width; - } - if (width > displayInfo.largestNominalAppWidth) { - displayInfo.largestNominalAppWidth = width; - } - final int height = mPolicy.getConfigDisplayHeight(dw, dh, rotation, uiMode, displayId); - if (height < displayInfo.smallestNominalAppHeight) { - displayInfo.smallestNominalAppHeight = height; - } - if (height > displayInfo.largestNominalAppHeight) { - displayInfo.largestNominalAppHeight = height; - } - } - - private int reduceConfigLayout(int curLayout, int rotation, float density, - int dw, int dh, int uiMode, int displayId) { - // Get the app screen size at this rotation. - int w = mPolicy.getNonDecorDisplayWidth(dw, dh, rotation, uiMode, displayId); - int h = mPolicy.getNonDecorDisplayHeight(dw, dh, rotation, uiMode, displayId); - - // Compute the screen layout size class for this rotation. - int longSize = w; - int shortSize = h; - if (longSize < shortSize) { - int tmp = longSize; - longSize = shortSize; - shortSize = tmp; - } - longSize = (int)(longSize/density); - shortSize = (int)(shortSize/density); - return Configuration.reduceScreenLayout(curLayout, longSize, shortSize); - } - - private void computeSizeRangesAndScreenLayout(DisplayInfo displayInfo, int displayId, - boolean rotated, int uiMode, int dw, int dh, float density, Configuration outConfig) { - - // We need to determine the smallest width that will occur under normal - // operation. To this, start with the base screen size and compute the - // width under the different possible rotations. We need to un-rotate - // the current screen dimensions before doing this. - int unrotDw, unrotDh; - if (rotated) { - unrotDw = dh; - unrotDh = dw; - } else { - unrotDw = dw; - unrotDh = dh; - } - displayInfo.smallestNominalAppWidth = 1<<30; - displayInfo.smallestNominalAppHeight = 1<<30; - displayInfo.largestNominalAppWidth = 0; - displayInfo.largestNominalAppHeight = 0; - adjustDisplaySizeRanges(displayInfo, displayId, Surface.ROTATION_0, uiMode, unrotDw, - unrotDh); - adjustDisplaySizeRanges(displayInfo, displayId, Surface.ROTATION_90, uiMode, unrotDh, - unrotDw); - adjustDisplaySizeRanges(displayInfo, displayId, Surface.ROTATION_180, uiMode, unrotDw, - unrotDh); - adjustDisplaySizeRanges(displayInfo, displayId, Surface.ROTATION_270, uiMode, unrotDh, - unrotDw); - int sl = Configuration.resetScreenLayout(outConfig.screenLayout); - sl = reduceConfigLayout(sl, Surface.ROTATION_0, density, unrotDw, unrotDh, uiMode, - displayId); - sl = reduceConfigLayout(sl, Surface.ROTATION_90, density, unrotDh, unrotDw, uiMode, - displayId); - sl = reduceConfigLayout(sl, Surface.ROTATION_180, density, unrotDw, unrotDh, uiMode, - displayId); - sl = reduceConfigLayout(sl, Surface.ROTATION_270, density, unrotDh, unrotDw, uiMode, - displayId); - outConfig.smallestScreenWidthDp = (int)(displayInfo.smallestNominalAppWidth / density); - outConfig.screenLayout = sl; - } - - private int reduceCompatConfigWidthSize(int curSize, int rotation, int uiMode, - DisplayMetrics dm, int dw, int dh, int displayId) { - dm.noncompatWidthPixels = mPolicy.getNonDecorDisplayWidth(dw, dh, rotation, uiMode, - displayId); - dm.noncompatHeightPixels = mPolicy.getNonDecorDisplayHeight(dw, dh, rotation, uiMode, - displayId); - float scale = CompatibilityInfo.computeCompatibleScaling(dm, null); - int size = (int)(((dm.noncompatWidthPixels / scale) / dm.density) + .5f); - if (curSize == 0 || size < curSize) { - curSize = size; - } - return curSize; - } - - private int computeCompatSmallestWidth(boolean rotated, int uiMode, DisplayMetrics dm, int dw, - int dh, int displayId) { - mTmpDisplayMetrics.setTo(dm); - final DisplayMetrics tmpDm = mTmpDisplayMetrics; - final int unrotDw, unrotDh; - if (rotated) { - unrotDw = dh; - unrotDh = dw; - } else { - unrotDw = dw; - unrotDh = dh; - } - int sw = reduceCompatConfigWidthSize(0, Surface.ROTATION_0, uiMode, tmpDm, unrotDw, unrotDh, - displayId); - sw = reduceCompatConfigWidthSize(sw, Surface.ROTATION_90, uiMode, tmpDm, unrotDh, unrotDw, - displayId); - sw = reduceCompatConfigWidthSize(sw, Surface.ROTATION_180, uiMode, tmpDm, unrotDw, unrotDh, - displayId); - sw = reduceCompatConfigWidthSize(sw, Surface.ROTATION_270, uiMode, tmpDm, unrotDh, unrotDw, - displayId); - return sw; - } - - /** Do not call if mDisplayReady == false */ - private DisplayInfo updateDisplayAndOrientationLocked(int uiMode, int displayId) { final DisplayContent displayContent = mRoot.getDisplayContent(displayId); - - // TODO(multi-display): Implement rotation for secondary displays. - final boolean isDefaultDisplay = displayContent.isDefaultDisplay; - final int displayRotation = displayContent.getRotation(); - final boolean altDisplayOrientation = displayContent.getAltOrientation(); - - // Use the effective "visual" dimensions based on current rotation - final boolean rotated = (displayRotation == Surface.ROTATION_90 - || displayRotation == Surface.ROTATION_270); - final int realdw = rotated ? - displayContent.mBaseDisplayHeight : displayContent.mBaseDisplayWidth; - final int realdh = rotated ? - displayContent.mBaseDisplayWidth : displayContent.mBaseDisplayHeight; - int dw = realdw; - int dh = realdh; - - if (altDisplayOrientation) { - if (realdw > realdh) { - // Turn landscape into portrait. - int maxw = (int)(realdh/1.3f); - if (maxw < realdw) { - dw = maxw; - } - } else { - // Turn portrait into landscape. - int maxh = (int)(realdw/1.3f); - if (maxh < realdh) { - dh = maxh; - } - } - } - - // Update application display metrics. - final int appWidth = mPolicy.getNonDecorDisplayWidth(dw, dh, displayRotation, uiMode, - displayId); - final int appHeight = mPolicy.getNonDecorDisplayHeight(dw, dh, displayRotation, uiMode, - displayId); - final DisplayInfo displayInfo = displayContent.getDisplayInfo(); - displayInfo.rotation = displayRotation; - displayInfo.logicalWidth = dw; - displayInfo.logicalHeight = dh; - displayInfo.logicalDensityDpi = displayContent.mBaseDisplayDensity; - displayInfo.appWidth = appWidth; - displayInfo.appHeight = appHeight; - if (isDefaultDisplay) { - displayInfo.getLogicalMetrics(mRealDisplayMetrics, - CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null); - } - displayInfo.getAppMetrics(mDisplayMetrics); - if (displayContent.mDisplayScalingDisabled) { - displayInfo.flags |= Display.FLAG_SCALING_DISABLED; - } else { - displayInfo.flags &= ~Display.FLAG_SCALING_DISABLED; - } - - mDisplayManagerInternal.setDisplayInfoOverrideFromWindowManager( - displayContent.getDisplayId(), displayInfo); - - displayContent.mBaseDisplayRect.set(0, 0, dw, dh); - if (false) { - Slog.i(TAG_WM, "Set app display size: " + appWidth + " x " + appHeight); - } - - if (isDefaultDisplay) { - mCompatibleScreenScale = CompatibilityInfo.computeCompatibleScaling(mDisplayMetrics, - mCompatDisplayMetrics); - } - return displayInfo; - } - - /** Do not call if mDisplayReady == false */ - private void computeScreenConfigurationLocked(Configuration config, int displayId) { - final DisplayInfo displayInfo = updateDisplayAndOrientationLocked(config.uiMode, displayId); - - final int dw = displayInfo.logicalWidth; - final int dh = displayInfo.logicalHeight; - config.orientation = (dw <= dh) ? Configuration.ORIENTATION_PORTRAIT : - Configuration.ORIENTATION_LANDSCAPE; - config.screenWidthDp = - (int)(mPolicy.getConfigDisplayWidth(dw, dh, displayInfo.rotation, config.uiMode, - displayId) / mDisplayMetrics.density); - config.screenHeightDp = - (int)(mPolicy.getConfigDisplayHeight(dw, dh, displayInfo.rotation, config.uiMode, - displayId) / mDisplayMetrics.density); - final boolean rotated = (displayInfo.rotation == Surface.ROTATION_90 - || displayInfo.rotation == Surface.ROTATION_270); - - computeSizeRangesAndScreenLayout(displayInfo, displayId, rotated, config.uiMode, dw, dh, - mDisplayMetrics.density, config); - - config.screenLayout = (config.screenLayout & ~Configuration.SCREENLAYOUT_ROUND_MASK) - | ((displayInfo.flags & Display.FLAG_ROUND) != 0 - ? Configuration.SCREENLAYOUT_ROUND_YES - : Configuration.SCREENLAYOUT_ROUND_NO); - - config.compatScreenWidthDp = (int)(config.screenWidthDp / mCompatibleScreenScale); - config.compatScreenHeightDp = (int)(config.screenHeightDp / mCompatibleScreenScale); - config.compatSmallestScreenWidthDp = computeCompatSmallestWidth(rotated, config.uiMode, - mDisplayMetrics, dw, dh, displayId); - config.densityDpi = displayInfo.logicalDensityDpi; - - config.colorMode = - (displayInfo.isHdr() - ? Configuration.COLOR_MODE_HDR_YES - : Configuration.COLOR_MODE_HDR_NO) - | (displayInfo.isWideColorGamut() - ? Configuration.COLOR_MODE_WIDE_COLOR_GAMUT_YES - : Configuration.COLOR_MODE_WIDE_COLOR_GAMUT_NO); - - // Update the configuration based on available input devices, lid switch, - // and platform configuration. - config.touchscreen = Configuration.TOUCHSCREEN_NOTOUCH; - config.keyboard = Configuration.KEYBOARD_NOKEYS; - config.navigation = Configuration.NAVIGATION_NONAV; - - int keyboardPresence = 0; - int navigationPresence = 0; - final InputDevice[] devices = mInputManager.getInputDevices(); - final int len = devices != null ? devices.length : 0; - for (int i = 0; i < len; i++) { - InputDevice device = devices[i]; - if (!device.isVirtual()) { - final int sources = device.getSources(); - final int presenceFlag = device.isExternal() ? - WindowManagerPolicy.PRESENCE_EXTERNAL : - WindowManagerPolicy.PRESENCE_INTERNAL; - - if (mIsTouchDevice) { - if ((sources & InputDevice.SOURCE_TOUCHSCREEN) == - InputDevice.SOURCE_TOUCHSCREEN) { - config.touchscreen = Configuration.TOUCHSCREEN_FINGER; - } - } else { - config.touchscreen = Configuration.TOUCHSCREEN_NOTOUCH; - } - - if ((sources & InputDevice.SOURCE_TRACKBALL) == InputDevice.SOURCE_TRACKBALL) { - config.navigation = Configuration.NAVIGATION_TRACKBALL; - navigationPresence |= presenceFlag; - } else if ((sources & InputDevice.SOURCE_DPAD) == InputDevice.SOURCE_DPAD - && config.navigation == Configuration.NAVIGATION_NONAV) { - config.navigation = Configuration.NAVIGATION_DPAD; - navigationPresence |= presenceFlag; - } - - if (device.getKeyboardType() == InputDevice.KEYBOARD_TYPE_ALPHABETIC) { - config.keyboard = Configuration.KEYBOARD_QWERTY; - keyboardPresence |= presenceFlag; - } - } - } - - if (config.navigation == Configuration.NAVIGATION_NONAV && mHasPermanentDpad) { - config.navigation = Configuration.NAVIGATION_DPAD; - navigationPresence |= WindowManagerPolicy.PRESENCE_INTERNAL; - } - - // Determine whether a hard keyboard is available and enabled. - boolean hardKeyboardAvailable = config.keyboard != Configuration.KEYBOARD_NOKEYS; - if (hardKeyboardAvailable != mHardKeyboardAvailable) { - mHardKeyboardAvailable = hardKeyboardAvailable; - mH.removeMessages(H.REPORT_HARD_KEYBOARD_STATUS_CHANGE); - mH.sendEmptyMessage(H.REPORT_HARD_KEYBOARD_STATUS_CHANGE); - } - - // Let the policy update hidden states. - config.keyboardHidden = Configuration.KEYBOARDHIDDEN_NO; - config.hardKeyboardHidden = Configuration.HARDKEYBOARDHIDDEN_NO; - config.navigationHidden = Configuration.NAVIGATIONHIDDEN_NO; - mPolicy.adjustConfigurationLw(config, keyboardPresence, navigationPresence); + displayContent.computeScreenConfiguration(config); + return config; } void notifyHardKeyboardStatusChange() { @@ -6099,7 +5510,7 @@ public class WindowManagerService extends IWindowManager.Stub displayId); final Configuration currentDisplayConfig = displayContent.getConfiguration(); mTempConfiguration.setTo(currentDisplayConfig); - computeScreenConfigurationLocked(mTempConfiguration, displayId); + displayContent.computeScreenConfiguration(mTempConfiguration); configChanged |= currentDisplayConfig.diff(mTempConfiguration) != 0; if (configChanged) { @@ -6562,7 +5973,8 @@ public class WindowManagerService extends IWindowManager.Stub if (updateRotation) { if (DEBUG_ORIENTATION) Slog.d(TAG_WM, "Performing post-rotate rotation"); - configChanged |= updateRotationUncheckedLocked(false, displayId); + configChanged |= displayContent.updateRotationUnchecked( + false /* inTransaction */); } if (configChanged) { @@ -6605,8 +6017,9 @@ public class WindowManagerService extends IWindowManager.Stub String[] toks = line.split("%"); if (toks != null && toks.length > 0) { // TODO(multi-display): Show watermarks on secondary displays. - mWatermark = new Watermark(getDefaultDisplayContentLocked().getDisplay(), - mRealDisplayMetrics, mFxSession, toks); + final DisplayContent displayContent = getDefaultDisplayContentLocked(); + mWatermark = new Watermark(displayContent.getDisplay(), + displayContent.mRealDisplayMetrics, mFxSession, toks); } } } catch (FileNotFoundException e) { @@ -7642,9 +7055,10 @@ public class WindowManagerService extends IWindowManager.Stub if (DEBUG_ORIENTATION) { Slog.i(TAG, "Performing post-rotate rotation after seamless rotation"); } - final int displayId = w.getDisplayId(); - if (updateRotationUncheckedLocked(false, displayId)) { - mH.obtainMessage(H.SEND_NEW_CONFIGURATION, displayId).sendToTarget(); + final DisplayContent displayContent = w.getDisplayContent(); + if (displayContent.updateRotationUnchecked(false /* inTransaction */)) { + mH.obtainMessage(H.SEND_NEW_CONFIGURATION, displayContent.getDisplayId()) + .sendToTarget(); } } } @@ -7670,6 +7084,17 @@ public class WindowManagerService extends IWindowManager.Stub } @Override + public void setForceShowMagnifiableBounds(boolean show) { + synchronized (mWindowMap) { + if (mAccessibilityController != null) { + mAccessibilityController.setForceShowMagnifiableBoundsLocked(show); + } else { + throw new IllegalStateException("Magnification callbacks not set!"); + } + } + } + + @Override public void getMagnificationRegion(@NonNull Region magnificationRegion) { synchronized (mWindowMap) { if (mAccessibilityController != null) { diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index ccbc5ba276b0..ca5d551e8e0d 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -114,6 +114,7 @@ import android.os.SystemClock; import android.os.Trace; import android.os.UserHandle; import android.os.WorkSource; +import android.util.MergedConfiguration; import android.util.DisplayMetrics; import android.util.Slog; import android.util.TimeUtils; @@ -1273,8 +1274,8 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP void prelayout() { if (mEnforceSizeCompat) { - mGlobalScale = mService.mCompatibleScreenScale; - mInvGlobalScale = 1/mGlobalScale; + mGlobalScale = getDisplayContent().mCompatibleScreenScale; + mInvGlobalScale = 1 / mGlobalScale; } else { mGlobalScale = mInvGlobalScale = 1; } @@ -2265,7 +2266,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP } } - void prepareWindowToDisplayDuringRelayout(Configuration outConfig) { + void prepareWindowToDisplayDuringRelayout(MergedConfiguration mergedConfiguration) { if ((mAttrs.softInputMode & SOFT_INPUT_MASK_ADJUST) == SOFT_INPUT_ADJUST_RESIZE) { mLayoutNeeded = true; @@ -2278,10 +2279,13 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP mTurnOnScreen = true; } if (isConfigChanged()) { - outConfig.setTo(getConfiguration()); - if (DEBUG_CONFIGURATION) Slog.i(TAG, "Window " + this + " visible with new config: " - + outConfig); - mLastReportedConfiguration.setTo(outConfig); + final Configuration globalConfig = mService.mRoot.getConfiguration(); + final Configuration overrideConfig = getMergedOverrideConfiguration(); + mergedConfiguration.setConfiguration(globalConfig, overrideConfig); + if (DEBUG_CONFIGURATION) Slog.i(TAG, "Window " + this + + " visible with new global config: " + globalConfig + + " merged override config: " + overrideConfig); + mLastReportedConfiguration.setTo(getConfiguration()); } } @@ -3027,12 +3031,13 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP try { if (DEBUG_RESIZE || DEBUG_ORIENTATION) Slog.v(TAG, "Reporting new frame to " + this + ": " + mCompatFrame); - final Configuration newConfig; + final MergedConfiguration mergedConfiguration; if (isConfigChanged()) { - newConfig = new Configuration(getConfiguration()); - mLastReportedConfiguration.setTo(newConfig); + mergedConfiguration = new MergedConfiguration(mService.mRoot.getConfiguration(), + getMergedOverrideConfiguration()); + mLastReportedConfiguration.setTo(mergedConfiguration.getMergedConfiguration()); } else { - newConfig = null; + mergedConfiguration = null; } if (DEBUG_ORIENTATION && mWinAnimator.mDrawState == DRAW_PENDING) Slog.i(TAG, "Resizing " + this + " WITH DRAW PENDING"); @@ -3054,7 +3059,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP public void run() { try { dispatchResized(frame, overscanInsets, contentInsets, visibleInsets, - stableInsets, outsets, reportDraw, newConfig, + stableInsets, outsets, reportDraw, mergedConfiguration, reportOrientation, displayId); } catch (RemoteException e) { // Not a remote call, RemoteException won't be raised. @@ -3063,7 +3068,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP }); } else { dispatchResized(frame, overscanInsets, contentInsets, visibleInsets, stableInsets, - outsets, reportDraw, newConfig, reportOrientation, displayId); + outsets, reportDraw, mergedConfiguration, reportOrientation, displayId); } //TODO (multidisplay): Accessibility supported only for the default display. @@ -3120,14 +3125,14 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP private void dispatchResized(Rect frame, Rect overscanInsets, Rect contentInsets, Rect visibleInsets, Rect stableInsets, Rect outsets, boolean reportDraw, - Configuration newConfig, boolean reportOrientation, int displayId) + MergedConfiguration mergedConfiguration, boolean reportOrientation, int displayId) throws RemoteException { final boolean forceRelayout = isDragResizeChanged() || mResizedWhileNotDragResizing || reportOrientation; mClient.resized(frame, overscanInsets, contentInsets, visibleInsets, stableInsets, outsets, - reportDraw, newConfig, getBackdropFrame(frame), - forceRelayout, mPolicy.isNavBarForcedShownLw(this), displayId); + reportDraw, mergedConfiguration, getBackdropFrame(frame), forceRelayout, + mPolicy.isNavBarForcedShownLw(this), displayId); mDragResizingChangeReported = true; } @@ -4341,8 +4346,8 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP return !mLastSurfaceInsets.equals(mAttrs.surfaceInsets); } - int relayoutVisibleWindow(Configuration outConfig, int result, - int attrChanges, int oldVisibility) { + int relayoutVisibleWindow(MergedConfiguration mergedConfiguration, int result, int attrChanges, + int oldVisibility) { result |= !isVisibleLw() ? RELAYOUT_RES_FIRST_TIME : 0; if (mAnimatingExit) { Slog.d(TAG, "relayoutVisibleWindow: " + this + " mAnimatingExit=true, mRemoveOnExit=" @@ -4363,7 +4368,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP mWinAnimator.mEnteringAnimation = true; if ((result & RELAYOUT_RES_FIRST_TIME) != 0) { - prepareWindowToDisplayDuringRelayout(outConfig); + prepareWindowToDisplayDuringRelayout(mergedConfiguration); } if ((attrChanges & FORMAT_CHANGED) != 0) { // If the format can't be changed in place, preserve the old surface until the app draws diff --git a/services/core/java/com/android/server/wm/WindowSurfaceController.java b/services/core/java/com/android/server/wm/WindowSurfaceController.java index c9863c5c335e..b08bb70ca0b9 100644 --- a/services/core/java/com/android/server/wm/WindowSurfaceController.java +++ b/services/core/java/com/android/server/wm/WindowSurfaceController.java @@ -97,13 +97,7 @@ class WindowSurfaceController { mWindowType = windowType; mWindowSession = win.mSession; - // For opaque child windows placed under parent windows, we use a special SurfaceControl - // which mirrors commands to a black-out layer placed one Z-layer below the surface. - // This prevents holes to whatever app/wallpaper is underneath. - if (win.isChildWindow() && win.mSubLayer < 0 && win.mAppToken != null) { - mSurfaceControl = new SurfaceControlWithBackground( - s, name, w, h, format, flags, win.mAppToken, windowType, ownerUid); - } else if (DEBUG_SURFACE_TRACE) { + if (DEBUG_SURFACE_TRACE) { mSurfaceControl = new SurfaceTrace( s, name, w, h, format, flags, windowType, ownerUid); } else { @@ -834,141 +828,4 @@ class WindowSurfaceController { + " (" + mDsdx + "," + mDtdx + "," + mDsdy + "," + mDtdy + ")"; } } - - class SurfaceControlWithBackground extends SurfaceControl { - private SurfaceControl mBackgroundControl; - private boolean mOpaque = true; - private boolean mAppForcedInvisible = false; - private AppWindowToken mAppToken; - public boolean mVisible = false; - public int mLayer = -1; - - public SurfaceControlWithBackground(SurfaceSession s, String name, int w, int h, int format, - int flags, AppWindowToken token, int windowType, int ownerUid) - throws OutOfResourcesException { - super(s, name, w, h, format, flags, windowType, ownerUid); - mBackgroundControl = new SurfaceControl(s, name, w, h, - PixelFormat.OPAQUE, flags | SurfaceControl.FX_SURFACE_DIM); - mOpaque = (flags & SurfaceControl.OPAQUE) != 0; - mAppToken = token; - - mAppToken.addSurfaceViewBackground(this); - } - - @Override - public void setAlpha(float alpha) { - super.setAlpha(alpha); - mBackgroundControl.setAlpha(alpha); - } - - @Override - public void setLayer(int zorder) { - super.setLayer(zorder); - mBackgroundControl.setLayer(zorder - 1); - if (mLayer != zorder) { - mLayer = zorder; - mAppToken.updateSurfaceViewBackgroundVisibilities(); - } - } - - @Override - public void setPosition(float x, float y) { - super.setPosition(x, y); - mBackgroundControl.setPosition(x, y); - } - - @Override - public void setSize(int w, int h) { - super.setSize(w, h); - mBackgroundControl.setSize(w, h); - } - - @Override - public void setWindowCrop(Rect crop) { - super.setWindowCrop(crop); - mBackgroundControl.setWindowCrop(crop); - } - - @Override - public void setFinalCrop(Rect crop) { - super.setFinalCrop(crop); - mBackgroundControl.setFinalCrop(crop); - } - - @Override - public void setLayerStack(int layerStack) { - super.setLayerStack(layerStack); - mBackgroundControl.setLayerStack(layerStack); - } - - @Override - public void setOpaque(boolean isOpaque) { - super.setOpaque(isOpaque); - mOpaque = isOpaque; - updateBackgroundVisibility(mAppForcedInvisible); - } - - @Override - public void setSecure(boolean isSecure) { - super.setSecure(isSecure); - } - - @Override - public void setMatrix(float dsdx, float dtdx, float dsdy, float dtdy) { - super.setMatrix(dsdx, dtdx, dsdy, dtdy); - mBackgroundControl.setMatrix(dsdx, dtdx, dsdy, dtdy); - } - - @Override - public void hide() { - super.hide(); - if (mVisible) { - mVisible = false; - mAppToken.updateSurfaceViewBackgroundVisibilities(); - } - } - - @Override - public void show() { - super.show(); - if (!mVisible) { - mVisible = true; - mAppToken.updateSurfaceViewBackgroundVisibilities(); - } - } - - @Override - public void destroy() { - super.destroy(); - mBackgroundControl.destroy(); - mAppToken.removeSurfaceViewBackground(this); - } - - @Override - public void release() { - super.release(); - mBackgroundControl.release(); - } - - @Override - public void setTransparentRegionHint(Region region) { - super.setTransparentRegionHint(region); - mBackgroundControl.setTransparentRegionHint(region); - } - - @Override - public void deferTransactionUntil(IBinder handle, long frame) { - super.deferTransactionUntil(handle, frame); - mBackgroundControl.deferTransactionUntil(handle, frame); - } - - void updateBackgroundVisibility(boolean forcedInvisible) { - mAppForcedInvisible = forcedInvisible; - if (mOpaque && mVisible && !mAppForcedInvisible) { - mBackgroundControl.show(); - } else { - mBackgroundControl.hide(); - } - } - } } diff --git a/services/core/jni/Android.mk b/services/core/jni/Android.mk index ac95db5c34b4..b5ed26630b23 100644 --- a/services/core/jni/Android.mk +++ b/services/core/jni/Android.mk @@ -65,6 +65,7 @@ LOCAL_SHARED_LIBRARIES += \ libinputflinger \ libinputservice \ libsensorservice \ + libsensorservicehidl \ libskia \ libgui \ libusbhost \ @@ -88,5 +89,6 @@ LOCAL_SHARED_LIBRARIES += \ android.hardware.tv.input@1.0 \ android.hardware.vibrator@1.0 \ android.hardware.vr@1.0 \ + android.frameworks.sensorservice@1.0 \ -LOCAL_STATIC_LIBRARIES += libscrypt_static
\ No newline at end of file +LOCAL_STATIC_LIBRARIES += libscrypt_static diff --git a/services/core/jni/com_android_server_SystemServer.cpp b/services/core/jni/com_android_server_SystemServer.cpp index 3120af56a1fd..8ad88eddd73b 100644 --- a/services/core/jni/com_android_server_SystemServer.cpp +++ b/services/core/jni/com_android_server_SystemServer.cpp @@ -17,7 +17,10 @@ #include <jni.h> #include <JNIHelp.h> +#include <hidl/HidlTransportSupport.h> + #include <sensorservice/SensorService.h> +#include <sensorservicehidl/SensorManager.h> #include <cutils/properties.h> #include <utils/Log.h> @@ -32,6 +35,21 @@ static void android_server_SystemServer_startSensorService(JNIEnv* /* env */, jo if (strcmp(propBuf, "1") == 0) { SensorService::instantiate(); } + +} + +static void android_server_SystemServer_startHidlServices(JNIEnv* /* env */, jobject /* clazz */) { + using ::android::frameworks::sensorservice::V1_0::ISensorManager; + using ::android::frameworks::sensorservice::V1_0::implementation::SensorManager; + using ::android::hardware::configureRpcThreadpool; + + configureRpcThreadpool(1, false /* callerWillJoin */); + sp<ISensorManager> sensorService = new SensorManager(); + status_t err = sensorService->registerAsService(); + if (err != OK) { + ALOGE("Cannot register ::android::frameworks::sensorservice::V1_0::" + "implementation::SensorManager: %d", err); + } } /* @@ -40,6 +58,7 @@ static void android_server_SystemServer_startSensorService(JNIEnv* /* env */, jo static const JNINativeMethod gMethods[] = { /* name, signature, funcPtr */ { "startSensorService", "()V", (void*) android_server_SystemServer_startSensorService }, + { "startHidlServices", "()V", (void*) android_server_SystemServer_startHidlServices }, }; int register_android_server_SystemServer(JNIEnv* env) diff --git a/services/core/jni/com_android_server_tv_TvInputHal.cpp b/services/core/jni/com_android_server_tv_TvInputHal.cpp index 9f528b1bec6a..b4333508a84a 100644 --- a/services/core/jni/com_android_server_tv_TvInputHal.cpp +++ b/services/core/jni/com_android_server_tv_TvInputHal.cpp @@ -81,6 +81,7 @@ static struct { jmethodID deviceId; jmethodID type; jmethodID hdmiPortId; + jmethodID cableConnectionStatus; jmethodID audioType; jmethodID audioAddress; jmethodID build; @@ -469,6 +470,9 @@ void JTvInputHal::onDeviceAvailable(const TvInputDeviceInfo& info) { builder, gTvInputHardwareInfoBuilderClassInfo.hdmiPortId, info.portId); } env->CallObjectMethod( + builder, gTvInputHardwareInfoBuilderClassInfo.cableConnectionStatus, + info.cableConnectionStatus); + env->CallObjectMethod( builder, gTvInputHardwareInfoBuilderClassInfo.audioType, info.audioType); if (info.audioType != AudioDevice::NONE) { uint8_t buffer[info.audioAddress.size() + 1]; @@ -743,6 +747,10 @@ int register_android_server_tv_TvInputHal(JNIEnv* env) { gTvInputHardwareInfoBuilderClassInfo.clazz, "hdmiPortId", "(I)Landroid/media/tv/TvInputHardwareInfo$Builder;"); GET_METHOD_ID( + gTvInputHardwareInfoBuilderClassInfo.cableConnectionStatus, + gTvInputHardwareInfoBuilderClassInfo.clazz, + "cableConnectionStatus", "(I)Landroid/media/tv/TvInputHardwareInfo$Builder;"); + GET_METHOD_ID( gTvInputHardwareInfoBuilderClassInfo.audioType, gTvInputHardwareInfoBuilderClassInfo.clazz, "audioType", "(I)Landroid/media/tv/TvInputHardwareInfo$Builder;"); diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 7ad02927d7a5..0e6a54217e79 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -3141,6 +3141,10 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { throw new IllegalArgumentException("Only apps in internal storage can be active admin: " + adminReceiver); } + if (info.getActivityInfo().applicationInfo.isInstantApp()) { + throw new IllegalArgumentException("Instant apps cannot be device admins: " + + adminReceiver); + } synchronized (this) { long ident = mInjector.binderClearCallingIdentity(); try { diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index a8423e2e8a11..e6e02428ae63 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -86,6 +86,7 @@ import com.android.server.notification.NotificationManagerService; import com.android.server.om.OverlayManagerService; import com.android.server.os.DeviceIdentifiersPolicyService; import com.android.server.os.SchedulingPolicyService; +import com.android.server.pm.BackgroundDexOptService; import com.android.server.pm.Installer; import com.android.server.pm.LauncherAppsService; import com.android.server.pm.OtaDexoptService; @@ -235,16 +236,24 @@ public final class SystemServer { private final boolean mRuntimeRestart; private static final String START_SENSOR_SERVICE = "StartSensorService"; + private static final String START_HIDL_SERVICES = "StartHidlServices"; + + private Future<?> mSensorServiceStart; private Future<?> mZygotePreload; - /** * Start the sensor service. This is a blocking call and can take time. */ private static native void startSensorService(); /** + * Start all HIDL services that are run inside the system server. This + * may take some time. + */ + private static native void startHidlServices(); + + /** * The main entry point from zygote. */ public static void main(String[] args) { @@ -610,6 +619,7 @@ public final class SystemServer { startSensorService(); traceLog.traceEnd(); }, START_SENSOR_SERVICE); + } /** @@ -637,6 +647,14 @@ public final class SystemServer { traceBeginAndSlog("StartWebViewUpdateService"); mWebViewUpdateService = mSystemServiceManager.startService(WebViewUpdateService.class); traceEnd(); + + // Start receiving calls from HIDL services. Start in in a separate thread + // because it need to connect to SensorManager. + SystemServerInitThreadPool.get().submit(() -> { + traceBeginAndSlog(START_HIDL_SERVICES); + startHidlServices(); + traceEnd(); + }, START_HIDL_SERVICES); } /** @@ -1428,11 +1446,11 @@ public final class SystemServer { traceEnd(); } - traceBeginAndSlog("StartBackgroundDexOptJobService"); + traceBeginAndSlog("StartBackgroundDexOptService"); try { - BackgroundDexOptJobService.schedule(context); + BackgroundDexOptService.schedule(context); } catch (Throwable e) { - reportWtf("starting StartBackgroundDexOptJobService", e); + reportWtf("starting StartBackgroundDexOptService", e); } traceEnd(); diff --git a/services/net/java/android/net/apf/ApfFilter.java b/services/net/java/android/net/apf/ApfFilter.java index 0a907496ebdd..a0a9310af28c 100644 --- a/services/net/java/android/net/apf/ApfFilter.java +++ b/services/net/java/android/net/apf/ApfFilter.java @@ -93,17 +93,10 @@ public class ApfFilter { class ReceiveThread extends Thread { private final byte[] mPacket = new byte[1514]; private final FileDescriptor mSocket; - private volatile boolean mStopped; - - // Starting time of the RA receiver thread. private final long mStart = SystemClock.elapsedRealtime(); + private final ApfStats mStats = new ApfStats(); - private int mReceivedRas; // Number of received RAs - private int mMatchingRas; // Number of received RAs matching a known RA - private int mDroppedRas; // Number of received RAs ignored due to the MAX_RAS limit - private int mParseErrors; // Number of received RAs that could not be parsed - private int mZeroLifetimeRas; // Number of received RAs with a 0 lifetime - private int mProgramUpdates; // Number of APF program updates triggered by receiving a RA + private volatile boolean mStopped; public ReceiveThread(FileDescriptor socket) { mSocket = socket; @@ -134,35 +127,40 @@ public class ApfFilter { } private void updateStats(ProcessRaResult result) { - mReceivedRas++; + mStats.receivedRas++; switch(result) { case MATCH: - mMatchingRas++; + mStats.matchingRas++; return; case DROPPED: - mDroppedRas++; + mStats.droppedRas++; return; case PARSE_ERROR: - mParseErrors++; + mStats.parseErrors++; return; case ZERO_LIFETIME: - mZeroLifetimeRas++; + mStats.zeroLifetimeRas++; return; case UPDATE_EXPIRY: - mMatchingRas++; - mProgramUpdates++; + mStats.matchingRas++; + mStats.programUpdates++; return; case UPDATE_NEW_RA: - mProgramUpdates++; + mStats.programUpdates++; return; } } private void logStats() { - long durationMs = SystemClock.elapsedRealtime() - mStart; - int maxSize = mApfCapabilities.maximumApfProgramSize; - mMetricsLog.log(new ApfStats(durationMs, mReceivedRas, mMatchingRas, mDroppedRas, - mZeroLifetimeRas, mParseErrors, mProgramUpdates, maxSize)); + final long nowMs = SystemClock.elapsedRealtime(); + synchronized (this) { + mStats.durationMs = nowMs - mStart; + mStats.maxProgramSize = mApfCapabilities.maximumApfProgramSize; + mStats.programUpdatesAll = mNumProgramUpdates; + mStats.programUpdatesAllowingMulticast = mNumProgramUpdatesAllowingMulticast; + mMetricsLog.log(mStats); + logApfProgramEventLocked(nowMs / DateUtils.SECOND_IN_MILLIS); + } } } @@ -218,6 +216,8 @@ public class ApfFilter { 4, // Protocol size: 4 }; private static final int ARP_TARGET_IP_ADDRESS_OFFSET = ETH_HEADER_LEN + 24; + // Do not log ApfProgramEvents whose actual lifetimes was less than this. + private static final int APF_PROGRAM_EVENT_LIFETIME_THRESHOLD = 2; private final ApfCapabilities mApfCapabilities; private final IpManager.Callback mIpManagerCallback; @@ -247,6 +247,7 @@ public class ApfFilter { mMulticastFilter = multicastFilter; mMetricsLog = log; + // TODO: ApfFilter should not generate programs until IpManager sends provisioning success. maybeStartFilter(); } @@ -661,14 +662,19 @@ public class ApfFilter { // How long should the last installed filter program live for? In seconds. @GuardedBy("this") private long mLastInstalledProgramMinLifetime; + @GuardedBy("this") + private ApfProgramEvent mLastInstallEvent; // For debugging only. The last program installed. @GuardedBy("this") private byte[] mLastInstalledProgram; - // For debugging only. How many times the program was updated since we started. + // How many times the program was updated since we started. + @GuardedBy("this") + private int mNumProgramUpdates = 0; + // How many times the program was updated since we started for allowing multicast traffic. @GuardedBy("this") - private int mNumProgramUpdates; + private int mNumProgramUpdatesAllowingMulticast = 0; /** * Generate filter code to process ARP packets. Execution of this code ends in either the @@ -947,7 +953,8 @@ public class ApfFilter { Log.e(TAG, "Failed to generate APF program.", e); return; } - mLastTimeInstalledProgram = currentTimeSeconds(); + final long now = currentTimeSeconds(); + mLastTimeInstalledProgram = now; mLastInstalledProgramMinLifetime = programMinLifetime; mLastInstalledProgram = program; mNumProgramUpdates++; @@ -956,9 +963,26 @@ public class ApfFilter { hexDump("Installing filter: ", program, program.length); } mIpManagerCallback.installPacketFilter(program); - int flags = ApfProgramEvent.flagsFor(mIPv4Address != null, mMulticastFilter); - mMetricsLog.log(new ApfProgramEvent( - programMinLifetime, rasToFilter.size(), mRas.size(), program.length, flags)); + logApfProgramEventLocked(now); + mLastInstallEvent = new ApfProgramEvent(); + mLastInstallEvent.lifetime = programMinLifetime; + mLastInstallEvent.filteredRas = rasToFilter.size(); + mLastInstallEvent.currentRas = mRas.size(); + mLastInstallEvent.programLength = program.length; + mLastInstallEvent.flags = ApfProgramEvent.flagsFor(mIPv4Address != null, mMulticastFilter); + } + + private void logApfProgramEventLocked(long now) { + if (mLastInstallEvent == null) { + return; + } + ApfProgramEvent ev = mLastInstallEvent; + mLastInstallEvent = null; + ev.actualLifetime = now - mLastTimeInstalledProgram; + if (ev.actualLifetime < APF_PROGRAM_EVENT_LIFETIME_THRESHOLD) { + return; + } + mMetricsLog.log(ev); } /** @@ -1078,11 +1102,15 @@ public class ApfFilter { mRas.clear(); } - public synchronized void setMulticastFilter(boolean enabled) { - if (mMulticastFilter != enabled) { - mMulticastFilter = enabled; - installNewProgramLocked(); + public synchronized void setMulticastFilter(boolean isEnabled) { + if (mMulticastFilter == isEnabled) { + return; + } + mMulticastFilter = isEnabled; + if (!isEnabled) { + mNumProgramUpdatesAllowingMulticast++; } + installNewProgramLocked(); } /** Find the single IPv4 LinkAddress if there is one, otherwise return null. */ diff --git a/services/net/java/android/net/ip/IpManager.java b/services/net/java/android/net/ip/IpManager.java index 76b1c90642ec..c670782b443e 100644 --- a/services/net/java/android/net/ip/IpManager.java +++ b/services/net/java/android/net/ip/IpManager.java @@ -16,6 +16,8 @@ package android.net.ip; +import static android.net.util.NetworkConstants.RFC7421_PREFIX_LENGTH; + import com.android.internal.util.MessageUtils; import com.android.internal.util.WakeupMessage; @@ -23,6 +25,7 @@ import android.content.Context; import android.net.apf.ApfCapabilities; import android.net.apf.ApfFilter; import android.net.DhcpResults; +import android.net.INetd; import android.net.InterfaceConfiguration; import android.net.LinkAddress; import android.net.LinkProperties; @@ -34,11 +37,14 @@ import android.net.dhcp.DhcpClient; import android.net.metrics.IpConnectivityLog; import android.net.metrics.IpManagerEvent; import android.net.util.MultinetworkPolicyTracker; +import android.net.util.NetdService; import android.os.INetworkManagementService; import android.os.Message; import android.os.RemoteException; import android.os.ServiceManager; +import android.os.ServiceSpecificException; import android.os.SystemClock; +import android.system.OsConstants; import android.text.TextUtils; import android.util.LocalLog; import android.util.Log; @@ -631,6 +637,13 @@ public class IpManager extends StateMachine { pw.println(); pw.println(mTag + " connectivity packet log:"); + pw.println(); + pw.println("Debug with python and scapy via:"); + pw.println("shell$ python"); + pw.println(">>> from scapy import all as scapy"); + pw.println(">>> scapy.Ether(\"<paste_hex_string>\".decode(\"hex\")).show2()"); + pw.println(); + pw.increaseIndent(); mConnectivityPacketLog.readOnlyLocalLog().dump(fd, pw, args); pw.decreaseIndent(); @@ -1018,16 +1031,39 @@ public class IpManager extends StateMachine { return true; } + private void enableInterfaceIPv6PrivacyExtensions() { + final String PREFER_TEMPADDRS = "2"; + NetdService.run((INetd netd) -> { + netd.setProcSysNet( + INetd.IPV6, INetd.CONF, mInterfaceName, "use_tempaddr", PREFER_TEMPADDRS); + }); + } + + private void setInterfaceIPv6RaRtInfoMaxPlen(int plen) { + // Setting RIO max plen is best effort. Catch and ignore most exceptions. + try { + NetdService.run((INetd netd) -> { + netd.setProcSysNet( + INetd.IPV6, INetd.CONF, mInterfaceName, "accept_ra_rt_info_max_plen", + Integer.toString(plen)); + }); + } catch (ServiceSpecificException e) { + // Old kernel versions without support for RIOs do not export accept_ra_rt_info_max_plen + // in the /proc filesystem. If the kernel supports RIOs we should never see any other + // type of error. + if (e.errorCode != OsConstants.ENOENT) { + logError("unexpected error setting accept_ra_rt_info_max_plen %s", e); + } + } + } + private boolean startIPv6() { - // Set privacy extensions. try { - mNwService.setInterfaceIpv6PrivacyExtensions(mInterfaceName, true); + enableInterfaceIPv6PrivacyExtensions(); + setInterfaceIPv6RaRtInfoMaxPlen(RFC7421_PREFIX_LENGTH); mNwService.enableIpv6(mInterfaceName); - } catch (RemoteException re) { - logError("Unable to change interface settings: %s", re); - return false; - } catch (IllegalStateException ie) { - logError("Unable to change interface settings: %s", ie); + } catch (IllegalStateException|RemoteException|ServiceSpecificException e) { + logError("Unable to change interface settings: %s", e); return false; } diff --git a/services/net/java/android/net/util/NetdService.java b/services/net/java/android/net/util/NetdService.java index 153cb502afca..6e69ff56c723 100644 --- a/services/net/java/android/net/util/NetdService.java +++ b/services/net/java/android/net/util/NetdService.java @@ -17,7 +17,10 @@ package android.net.util; import android.net.INetd; +import android.os.RemoteException; import android.os.ServiceManager; +import android.os.ServiceSpecificException; +import android.os.SystemClock; import android.util.Log; @@ -27,15 +30,24 @@ import android.util.Log; public class NetdService { private static final String TAG = NetdService.class.getSimpleName(); private static final String NETD_SERVICE_NAME = "netd"; + private static final long BASE_TIMEOUT_MS = 100; + private static final long MAX_TIMEOUT_MS = 1000; + /** + * Return an INetd instance, or null if not available. + * * It is the caller's responsibility to check for a null return value * and to handle RemoteException errors from invocations on the returned * interface if, for example, netd dies and is restarted. * + * Returned instances of INetd should not be cached. + * * @return an INetd instance or null. */ public static INetd getInstance() { + // NOTE: ServiceManager does no caching for the netd service, + // because netd is not one of the defined common services. final INetd netdInstance = INetd.Stub.asInterface( ServiceManager.getService(NETD_SERVICE_NAME)); if (netdInstance == null) { @@ -43,4 +55,82 @@ public class NetdService { } return netdInstance; } + + /** + * Blocks for a specified time until an INetd instance is available. + * + * It is the caller's responsibility to handle RemoteException errors + * from invocations on the returned interface if, for example, netd + * dies after this interface was returned. + * + * Returned instances of INetd should not be cached. + * + * Special values of maxTimeoutMs include: 0, meaning try to obtain an + * INetd instance only once, and -1 (or any value less than 0), meaning + * try to obtain an INetd instance indefinitely. + * + * @param maxTimeoutMs the maximum time to spend getting an INetd instance + * @return an INetd instance or null if no instance is available + * within |maxTimeoutMs| milliseconds. + */ + public static INetd get(long maxTimeoutMs) { + if (maxTimeoutMs == 0) return getInstance(); + + final long stop = (maxTimeoutMs > 0) + ? SystemClock.elapsedRealtime() + maxTimeoutMs + : Long.MAX_VALUE; + + long timeoutMs = 0; + while (true) { + final INetd netdInstance = getInstance(); + if (netdInstance != null) { + return netdInstance; + } + + final long remaining = stop - SystemClock.elapsedRealtime(); + if (remaining <= 0) break; + + // No netdInstance was received; sleep and retry. + timeoutMs = Math.min(timeoutMs + BASE_TIMEOUT_MS, MAX_TIMEOUT_MS); + timeoutMs = Math.min(timeoutMs, remaining); + try { + Thread.sleep(timeoutMs); + } catch (InterruptedException e) {} + } + return null; + } + + /** + * Blocks until an INetd instance is available. + * + * It is the caller's responsibility to handle RemoteException errors + * from invocations on the returned interface if, for example, netd + * dies after this interface was returned. + * + * Returned instances of INetd should not be cached. + * + * @return an INetd instance. + */ + public static INetd get() { + return get(-1); + } + + public static interface NetdCommand { + void run(INetd netd) throws RemoteException; + } + + /** + * Blocks until an INetd instance is availabe, and retries until either + * the command succeeds or a runtime exception is thrown. + */ + public static void run(NetdCommand cmd) { + while (true) { + try { + cmd.run(get()); + return; + } catch (RemoteException re) { + Log.e(TAG, "error communicating with netd: " + re); + } + } + } } diff --git a/services/net/java/android/net/util/NetworkConstants.java b/services/net/java/android/net/util/NetworkConstants.java index 362f7570c124..26f3050468dd 100644 --- a/services/net/java/android/net/util/NetworkConstants.java +++ b/services/net/java/android/net/util/NetworkConstants.java @@ -97,6 +97,7 @@ public final class NetworkConstants { public static final int IPV6_SRC_ADDR_OFFSET = 8; public static final int IPV6_DST_ADDR_OFFSET = 24; public static final int IPV6_ADDR_LEN = 16; + public static final int RFC7421_PREFIX_LENGTH = 64; /** * ICMPv6 constants. diff --git a/services/tests/notification/Android.mk b/services/tests/notification/Android.mk index de9553a46eef..a5d557048232 100644 --- a/services/tests/notification/Android.mk +++ b/services/tests/notification/Android.mk @@ -30,6 +30,7 @@ LOCAL_JAVA_LIBRARIES := android.test.runner LOCAL_JACK_FLAGS := --multi-dex native LOCAL_PACKAGE_NAME := FrameworksNotificationTests +LOCAL_COMPATIBILITY_SUITE := device-tests LOCAL_CERTIFICATE := platform diff --git a/services/tests/notification/src/com/android/server/notification/RankingHelperTest.java b/services/tests/notification/src/com/android/server/notification/RankingHelperTest.java index 5f215f937a97..27b9a880c4ac 100644 --- a/services/tests/notification/src/com/android/server/notification/RankingHelperTest.java +++ b/services/tests/notification/src/com/android/server/notification/RankingHelperTest.java @@ -203,7 +203,6 @@ public class RankingHelperTest { private void compareChannels(NotificationChannel expected, NotificationChannel actual) { assertEquals(expected.getId(), actual.getId()); assertEquals(expected.getName(), actual.getName()); - assertEquals(expected.getNameResId(), actual.getNameResId()); assertEquals(expected.shouldVibrate(), actual.shouldVibrate()); assertEquals(expected.shouldShowLights(), actual.shouldShowLights()); assertEquals(expected.getImportance(), actual.getImportance()); @@ -219,7 +218,6 @@ public class RankingHelperTest { private void compareGroups(NotificationChannelGroup expected, NotificationChannelGroup actual) { assertEquals(expected.getId(), actual.getId()); assertEquals(expected.getName(), actual.getName()); - assertEquals(expected.getNameResId(), actual.getNameResId()); } @Test @@ -274,15 +272,12 @@ public class RankingHelperTest { @Test public void testChannelXml() throws Exception { - int nameResId = 924896; - int groupNameResId = 426272; - - NotificationChannelGroup ncg = new NotificationChannelGroup("1", groupNameResId); + NotificationChannelGroup ncg = new NotificationChannelGroup("1", "bye"); NotificationChannelGroup ncg2 = new NotificationChannelGroup("2", "hello"); NotificationChannel channel1 = new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH); NotificationChannel channel2 = - new NotificationChannel("id2", nameResId, IMPORTANCE_LOW); + new NotificationChannel("id2", "name2", IMPORTANCE_LOW); channel2.setSound(new Uri.Builder().scheme("test").build(), mAudioAttributes); channel2.enableLights(true); channel2.setBypassDnd(true); @@ -728,6 +723,25 @@ public class RankingHelperTest { } @Test + public void testGetDeletedChannelCount() throws Exception { + NotificationChannel channel = + new NotificationChannel("id2", "name2", IMPORTANCE_LOW); + NotificationChannel channel2 = + new NotificationChannel("id4", "a", NotificationManager.IMPORTANCE_HIGH); + NotificationChannel channel3 = + new NotificationChannel("id4", "a", NotificationManager.IMPORTANCE_HIGH); + mHelper.createNotificationChannel(pkg, uid, channel, true); + mHelper.createNotificationChannel(pkg, uid, channel2, true); + mHelper.createNotificationChannel(pkg, uid, channel3, true); + + mHelper.deleteNotificationChannel(pkg, uid, channel.getId()); + mHelper.deleteNotificationChannel(pkg, uid, channel3.getId()); + + assertEquals(2, mHelper.getDeletedChannelCount(pkg, uid)); + assertEquals(0, mHelper.getDeletedChannelCount(pkg2, uid2)); + } + + @Test public void testUpdateDeletedChannels() throws Exception { NotificationChannel channel = new NotificationChannel("id2", "name2", IMPORTANCE_LOW); @@ -840,6 +854,43 @@ public class RankingHelperTest { } @Test + public void testDeleteGroup() throws Exception { + NotificationChannelGroup notDeleted = new NotificationChannelGroup("not", "deleted"); + NotificationChannelGroup deleted = new NotificationChannelGroup("totally", "deleted"); + NotificationChannel nonGroupedNonDeletedChannel = + new NotificationChannel("no group", "so not deleted", IMPORTANCE_HIGH); + NotificationChannel groupedButNotDeleted = + new NotificationChannel("not deleted", "belongs to notDeleted", IMPORTANCE_DEFAULT); + groupedButNotDeleted.setGroup("not"); + NotificationChannel groupedAndDeleted = + new NotificationChannel("deleted", "belongs to deleted", IMPORTANCE_DEFAULT); + groupedAndDeleted.setGroup("totally"); + + mHelper.createNotificationChannelGroup(pkg, uid, notDeleted, true); + mHelper.createNotificationChannelGroup(pkg, uid, deleted, true); + mHelper.createNotificationChannel(pkg, uid, nonGroupedNonDeletedChannel, true); + mHelper.createNotificationChannel(pkg, uid, groupedAndDeleted, true); + mHelper.createNotificationChannel(pkg, uid, groupedButNotDeleted, true); + + mHelper.deleteNotificationChannelGroup(pkg, uid, deleted.getId()); + + assertNull(mHelper.getNotificationChannelGroup(deleted.getId(), pkg, uid)); + assertNotNull(mHelper.getNotificationChannelGroup(notDeleted.getId(), pkg, uid)); + + assertNull(mHelper.getNotificationChannel(pkg, uid, groupedAndDeleted.getId(), false)); + compareChannels(groupedAndDeleted, + mHelper.getNotificationChannel(pkg, uid, groupedAndDeleted.getId(), true)); + + compareChannels(groupedButNotDeleted, + mHelper.getNotificationChannel(pkg, uid, groupedButNotDeleted.getId(), false)); + compareChannels(nonGroupedNonDeletedChannel, mHelper.getNotificationChannel( + pkg, uid, nonGroupedNonDeletedChannel.getId(), false)); + + // notDeleted + assertEquals(1, mHelper.getNotificationChannelGroups(pkg, uid).size()); + } + + @Test public void testOnPackageChanged_packageRemoval() throws Exception { // Deleted NotificationChannel channel1 = @@ -875,7 +926,7 @@ public class RankingHelperTest { mHelper.onPackagesChanged(true, UserHandle.USER_SYSTEM, new String[]{pkg}, new int[]{uid}); - assertEquals(0, mHelper.getNotificationChannelGroups(pkg, uid, true).getList().size()); + assertEquals(0, mHelper.getNotificationChannelGroups(pkg, uid).size()); } @Test @@ -994,14 +1045,18 @@ public class RankingHelperTest { } @Test - public void testCreateChannel_updateNameResId() throws Exception { - NotificationChannel nc = new NotificationChannel("id", 1, IMPORTANCE_DEFAULT); + public void testCreateChannel_updateName() throws Exception { + NotificationChannel nc = new NotificationChannel("id", "hello", IMPORTANCE_DEFAULT); mHelper.createNotificationChannel(pkg, uid, nc, true); + NotificationChannel actual = mHelper.getNotificationChannel(pkg, uid, "id", false); + assertEquals("hello", actual.getName()); - nc = new NotificationChannel("id", 2, IMPORTANCE_DEFAULT); + nc = new NotificationChannel("id", "goodbye", IMPORTANCE_HIGH); mHelper.createNotificationChannel(pkg, uid, nc, true); - assertEquals(2, mHelper.getNotificationChannel(pkg, uid, "id", false).getNameResId()); + actual = mHelper.getNotificationChannel(pkg, uid, "id", false); + assertEquals("goodbye", actual.getName()); + assertEquals(IMPORTANCE_DEFAULT, actual.getImportance()); } @Test diff --git a/services/tests/servicestests/Android.mk b/services/tests/servicestests/Android.mk index 15c61f6f228b..2a8f4a34f158 100644 --- a/services/tests/servicestests/Android.mk +++ b/services/tests/servicestests/Android.mk @@ -30,6 +30,7 @@ LOCAL_STATIC_JAVA_LIBRARIES := \ LOCAL_JAVA_LIBRARIES := android.test.runner LOCAL_PACKAGE_NAME := FrameworksServicesTests +LOCAL_COMPATIBILITY_SUITE := device-tests LOCAL_CERTIFICATE := platform diff --git a/services/tests/servicestests/src/com/android/server/BaseLockSettingsServiceTests.java b/services/tests/servicestests/src/com/android/server/BaseLockSettingsServiceTests.java index c6265bc768f3..a2a4019c93ad 100644 --- a/services/tests/servicestests/src/com/android/server/BaseLockSettingsServiceTests.java +++ b/services/tests/servicestests/src/com/android/server/BaseLockSettingsServiceTests.java @@ -25,6 +25,8 @@ import static org.mockito.Mockito.when; import android.app.IActivityManager; import android.app.NotificationManager; +import android.app.admin.DevicePolicyManager; +import android.content.ComponentName; import android.content.Context; import android.content.pm.UserInfo; import android.database.sqlite.SQLiteDatabase; @@ -76,7 +78,7 @@ public class BaseLockSettingsServiceTests extends AndroidTestCase { UserManager mUserManager; MockStorageManager mStorageManager; IActivityManager mActivityManager; - + DevicePolicyManager mDevicePolicyManager; KeyStore mKeyStore; @Override @@ -89,7 +91,9 @@ public class BaseLockSettingsServiceTests extends AndroidTestCase { mUserManager = mock(UserManager.class); mStorageManager = new MockStorageManager(); mActivityManager = mock(IActivityManager.class); - mContext = new MockLockSettingsContext(getContext(), mUserManager, mNotificationManager); + mDevicePolicyManager = mock(DevicePolicyManager.class); + mContext = new MockLockSettingsContext(getContext(), mUserManager, mNotificationManager, + mDevicePolicyManager); mStorage = new LockSettingsStorageTestable(mContext, new File(getContext().getFilesDir(), "locksettings")); File storageDir = mStorage.mStorageDir; @@ -122,6 +126,10 @@ public class BaseLockSettingsServiceTests extends AndroidTestCase { }); when(mLockPatternUtils.getLockSettings()).thenReturn(mService); + + // Adding a fake Device Owner app which will enable escrow token support in LSS. + when(mDevicePolicyManager.getDeviceOwnerComponentOnAnyUser()).thenReturn( + new ComponentName("com.dummy.package", ".FakeDeviceOwner")); } @Override diff --git a/services/tests/servicestests/src/com/android/server/LockSettingsStorageTests.java b/services/tests/servicestests/src/com/android/server/LockSettingsStorageTests.java index c68fbdc0a2ac..46779048e9db 100644 --- a/services/tests/servicestests/src/com/android/server/LockSettingsStorageTests.java +++ b/services/tests/servicestests/src/com/android/server/LockSettingsStorageTests.java @@ -21,6 +21,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import android.app.NotificationManager; +import android.app.admin.DevicePolicyManager; import android.content.Context; import android.content.ContextWrapper; import android.content.pm.UserInfo; @@ -68,7 +69,7 @@ public class LockSettingsStorageTests extends AndroidTestCase { when(mockUserManager.getProfileParent(eq(3))).thenReturn(new UserInfo(0, "name", 0)); MockLockSettingsContext context = new MockLockSettingsContext(getContext(), mockUserManager, - mock(NotificationManager.class)); + mock(NotificationManager.class), mock(DevicePolicyManager.class)); mStorage = new LockSettingsStorageTestable(context, new File(getContext().getFilesDir(), "locksettings")); mStorage.setDatabaseOnCreateCallback(new LockSettingsStorage.Callback() { diff --git a/services/tests/servicestests/src/com/android/server/MockLockSettingsContext.java b/services/tests/servicestests/src/com/android/server/MockLockSettingsContext.java index b63936fdffdf..8bceed45ab5f 100644 --- a/services/tests/servicestests/src/com/android/server/MockLockSettingsContext.java +++ b/services/tests/servicestests/src/com/android/server/MockLockSettingsContext.java @@ -17,6 +17,7 @@ package com.android.server; import android.app.NotificationManager; +import android.app.admin.DevicePolicyManager; import android.content.Context; import android.content.ContextWrapper; import android.os.UserManager; @@ -25,12 +26,14 @@ public class MockLockSettingsContext extends ContextWrapper { private UserManager mUserManager; private NotificationManager mNotificationManager; + private DevicePolicyManager mDevicePolicyManager; public MockLockSettingsContext(Context base, UserManager userManager, - NotificationManager notificationManager) { + NotificationManager notificationManager, DevicePolicyManager devicePolicyManager) { super(base); mUserManager = userManager; mNotificationManager = notificationManager; + mDevicePolicyManager = devicePolicyManager; } @Override @@ -39,6 +42,8 @@ public class MockLockSettingsContext extends ContextWrapper { return mUserManager; } else if (NOTIFICATION_SERVICE.equals(name)) { return mNotificationManager; + } else if (DEVICE_POLICY_SERVICE.equals(name)) { + return mDevicePolicyManager; } else { throw new RuntimeException("System service not mocked: " + name); } diff --git a/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java b/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java index fa0bd392f75d..72fb78e89ea2 100644 --- a/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java +++ b/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java @@ -61,7 +61,6 @@ public class DexManagerTests { @Before public void setup() { - mUser0 = 0; mUser1 = 1; @@ -352,6 +351,15 @@ public class DexManagerTests { assertNull(pui); } + @Test + public void testNotifyFrameworkLoad() { + String frameworkDex = "/system/framework/com.android.location.provider.jar"; + // Load a dex file from framework. + notifyDexLoad(mFooUser0, Arrays.asList(frameworkDex), mUser0); + // The dex file should not be recognized as a package. + assertNull(mDexManager.getPackageUseInfo(frameworkDex)); + } + private void assertSecondaryUse(TestData testData, PackageUseInfo pui, List<String> secondaries, boolean isUsedByOtherApps, int ownerUserId) { for (String dex : secondaries) { diff --git a/services/tests/servicestests/src/com/android/server/wm/AppWindowTokenTests.java b/services/tests/servicestests/src/com/android/server/wm/AppWindowTokenTests.java index 9f50a2c30aae..921e0e3deeb4 100644 --- a/services/tests/servicestests/src/com/android/server/wm/AppWindowTokenTests.java +++ b/services/tests/servicestests/src/com/android/server/wm/AppWindowTokenTests.java @@ -126,8 +126,6 @@ public class AppWindowTokenTests extends WindowTestsBase { } @Test - @Ignore - // TODO(b/35034729): Need to fix before re-enabling public void testLandscapeSeascapeRotationByPolicy() throws Exception { // Some plumbing to get the service ready for rotation updates. sWm.mDisplayReady = true; @@ -145,15 +143,20 @@ public class AppWindowTokenTests extends WindowTestsBase { appWindowToken.addWindow(appWindow); // Set initial orientation and update. - ((TestWindowManagerPolicy) sWm.mPolicy).rotationToReport = Surface.ROTATION_90; - sWm.updateRotation(false, false); + performRotation(Surface.ROTATION_90); appWindow.resizeReported = false; // Update the rotation to perform 180 degree rotation and check that resize was reported. - ((TestWindowManagerPolicy) sWm.mPolicy).rotationToReport = Surface.ROTATION_270; - sWm.updateRotation(false, false); - sWm.mRoot.performSurfacePlacement(false /* recoveringMemory */); + performRotation(Surface.ROTATION_270); assertTrue(appWindow.resizeReported); appWindow.removeImmediately(); } + + private void performRotation(int rotationToReport) { + ((TestWindowManagerPolicy) sWm.mPolicy).rotationToReport = rotationToReport; + sWm.updateRotation(false, false); + // Simulate animator finishing orientation change + sWm.mRoot.mOrientationChangeComplete = true; + sWm.mRoot.performSurfacePlacement(false /* recoveringMemory */); + } } diff --git a/services/tests/servicestests/src/com/android/server/wm/TestIWindow.java b/services/tests/servicestests/src/com/android/server/wm/TestIWindow.java index b6dc9a5b6e34..0a644b60d224 100644 --- a/services/tests/servicestests/src/com/android/server/wm/TestIWindow.java +++ b/services/tests/servicestests/src/com/android/server/wm/TestIWindow.java @@ -18,11 +18,11 @@ package com.android.server.wm; import com.android.internal.os.IResultReceiver; -import android.content.res.Configuration; import android.graphics.Rect; import android.os.Bundle; import android.os.ParcelFileDescriptor; import android.os.RemoteException; +import android.util.MergedConfiguration; import android.view.DragEvent; import android.view.IWindow; @@ -36,7 +36,7 @@ public class TestIWindow extends IWindow.Stub { @Override public void resized(Rect frame, Rect overscanInsets, Rect contentInsets, Rect visibleInsets, - Rect stableInsets, Rect outsets, boolean reportDraw, Configuration newConfig, + Rect stableInsets, Rect outsets, boolean reportDraw, MergedConfiguration mergedConfig, Rect backDropFrame, boolean forceLayout, boolean alwaysConsumeNavBar, int displayId) throws RemoteException { diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java index 911050a27a83..65efd9cd23ae 100644 --- a/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java +++ b/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java @@ -71,7 +71,10 @@ class WindowTestsBase { static TestWindowManagerPolicy sPolicy = null; private final static IWindow sIWindow = new TestIWindow(); private final static Session sMockSession = mock(Session.class); - private static int sNextDisplayId = Display.DEFAULT_DISPLAY + 1; + // The default display is removed in {@link #setUp} and then we iterate over all displays to + // make sure we don't collide with any existing display. If we run into no other display, the + // added display should be treated as default. + private static int sNextDisplayId = Display.DEFAULT_DISPLAY; static int sNextStackId = FIRST_DYNAMIC_STACK_ID; private static int sNextTaskId = 0; diff --git a/services/usage/java/com/android/server/usage/StorageStatsService.java b/services/usage/java/com/android/server/usage/StorageStatsService.java index ebd4b019790a..169cf4d750d5 100644 --- a/services/usage/java/com/android/server/usage/StorageStatsService.java +++ b/services/usage/java/com/android/server/usage/StorageStatsService.java @@ -105,7 +105,7 @@ public class StorageStatsService extends IStorageStatsManager.Stub { invalidateMounts(); mHandler = new H(IoThread.get().getLooper()); - mHandler.sendEmptyMessageDelayed(H.MSG_LOAD_CACHED_QUOTAS_FROM_FILE, DELAY_IN_MILLIS); + mHandler.sendEmptyMessage(H.MSG_LOAD_CACHED_QUOTAS_FROM_FILE); mStorage.registerListener(new StorageEventListener() { @Override @@ -137,7 +137,8 @@ public class StorageStatsService extends IStorageStatsManager.Stub { android.Manifest.permission.PACKAGE_USAGE_STATS, TAG); return; default: - throw new SecurityException("Blocked by mode " + mode); + throw new SecurityException("Package " + callingPackage + " from UID " + callingUid + + " blocked by mode " + mode); } } @@ -169,16 +170,21 @@ public class StorageStatsService extends IStorageStatsManager.Stub { enforcePermission(Binder.getCallingUid(), callingPackage); long cacheBytes = 0; - for (UserInfo user : mUser.getUsers()) { - final StorageStats stats = queryStatsForUser(volumeUuid, user.id, null); - cacheBytes += stats.cacheBytes; + final long token = Binder.clearCallingIdentity(); + try { + for (UserInfo user : mUser.getUsers()) { + final StorageStats stats = queryStatsForUser(volumeUuid, user.id, null); + cacheBytes += stats.cacheBytes; + } + } finally { + Binder.restoreCallingIdentity(token); } if (volumeUuid == StorageManager.UUID_PRIVATE_INTERNAL) { - return Environment.getDataDirectory().getFreeSpace() + cacheBytes; + return Environment.getDataDirectory().getUsableSpace() + cacheBytes; } else { final VolumeInfo vol = mStorage.findVolumeByUuid(volumeUuid); - return vol.getPath().getFreeSpace() + cacheBytes; + return vol.getPath().getUsableSpace() + cacheBytes; } } @@ -208,7 +214,8 @@ public class StorageStatsService extends IStorageStatsManager.Stub { final ApplicationInfo appInfo; try { - appInfo = mPackage.getApplicationInfoAsUser(packageName, 0, userId); + appInfo = mPackage.getApplicationInfoAsUser(packageName, + PackageManager.MATCH_UNINSTALLED_PACKAGES, userId); } catch (NameNotFoundException e) { throw new IllegalStateException(e); } @@ -251,8 +258,8 @@ public class StorageStatsService extends IStorageStatsManager.Stub { for (int i = 0; i < packageNames.length; i++) { try { - codePaths[i] = mPackage.getApplicationInfoAsUser(packageNames[i], 0, - userId).getCodePath(); + codePaths[i] = mPackage.getApplicationInfoAsUser(packageNames[i], + PackageManager.MATCH_UNINSTALLED_PACKAGES, userId).getCodePath(); } catch (NameNotFoundException e) { throw new IllegalStateException(e); } @@ -284,7 +291,8 @@ public class StorageStatsService extends IStorageStatsManager.Stub { } int[] appIds = null; - for (ApplicationInfo app : mPackage.getInstalledApplicationsAsUser(0, userId)) { + for (ApplicationInfo app : mPackage.getInstalledApplicationsAsUser( + PackageManager.MATCH_UNINSTALLED_PACKAGES, userId)) { final int appId = UserHandle.getAppId(app.uid); if (!ArrayUtils.contains(appIds, appId)) { appIds = ArrayUtils.appendInt(appIds, appId); diff --git a/services/usb/java/com/android/server/usb/UsbAlsaManager.java b/services/usb/java/com/android/server/usb/UsbAlsaManager.java index 40bdaa58a864..03d82412a5c5 100644 --- a/services/usb/java/com/android/server/usb/UsbAlsaManager.java +++ b/services/usb/java/com/android/server/usb/UsbAlsaManager.java @@ -25,8 +25,8 @@ import android.hardware.usb.UsbInterface; import android.media.AudioSystem; import android.media.IAudioService; import android.media.midi.MidiDeviceInfo; -import android.os.FileObserver; import android.os.Bundle; +import android.os.FileObserver; import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemClock; @@ -41,10 +41,7 @@ import com.android.server.audio.AudioService; import libcore.io.IoUtils; import java.io.File; -import java.io.FileDescriptor; -import java.io.PrintWriter; import java.util.HashMap; -import java.util.ArrayList; /** * UsbAlsaManager manages USB audio and MIDI devices. @@ -219,23 +216,23 @@ public final class UsbAlsaManager { AlsaDevice testDevice = new AlsaDevice(type, card, device); // This value was empirically determined. - final int kWaitTime = 2500; // ms + final int kWaitTimeMs = 2500; synchronized(mAlsaDevices) { - long timeout = SystemClock.elapsedRealtime() + kWaitTime; + long timeoutMs = SystemClock.elapsedRealtime() + kWaitTimeMs; do { if (mAlsaDevices.values().contains(testDevice)) { return testDevice; } - long waitTime = timeout - SystemClock.elapsedRealtime(); - if (waitTime > 0) { + long waitTimeMs = timeoutMs - SystemClock.elapsedRealtime(); + if (waitTimeMs > 0) { try { - mAlsaDevices.wait(waitTime); + mAlsaDevices.wait(waitTimeMs); } catch (InterruptedException e) { Slog.d(TAG, "usb: InterruptedException while waiting for ALSA file."); } } - } while (timeout > SystemClock.elapsedRealtime()); + } while (timeoutMs > SystemClock.elapsedRealtime()); } Slog.e(TAG, "waitForAlsaDevice failed for " + testDevice); @@ -498,6 +495,7 @@ public final class UsbAlsaManager { // Devices List // /* + //import java.util.ArrayList; public ArrayList<UsbAudioDevice> getConnectedDevices() { ArrayList<UsbAudioDevice> devices = new ArrayList<UsbAudioDevice>(mAudioDevices.size()); for (HashMap.Entry<UsbDevice,UsbAudioDevice> entry : mAudioDevices.entrySet()) { diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index 632a1d63d99e..7a226a04d7eb 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -1312,6 +1312,84 @@ public class CarrierConfigManager { public static final String KEY_CALL_FORWARDING_BLOCKS_WHILE_ROAMING_STRING_ARRAY = "call_forwarding_blocks_while_roaming_string_array"; + /** + * The day of the month (1-31) on which the data cycle rolls over. + * <p> + * If the current month does not have this day, the cycle will roll over at the start of the + * next month. + * <p> + * This setting may be still overridden by explicit user choice. By default, the platform value + * will be used. + */ + public static final String KEY_MONTHLY_DATA_CYCLE_DAY_INT = + "monthly_data_cycle_day_int"; + + /** + * When {@link #KEY_MONTHLY_DATA_CYCLE_DAY_INT}, {@link #KEY_DATA_LIMIT_THRESHOLD_BYTES_LONG}, + * or {@link #KEY_DATA_WARNING_THRESHOLD_BYTES_LONG} are set to this value, the platform default + * value will be used for that key. + * + * @hide + */ + public static final int DATA_CYCLE_USE_PLATFORM_DEFAULT = -1; + + /** + * Flag indicating that a data cycle threshold should be disabled. + * <p> + * If {@link #KEY_DATA_WARNING_THRESHOLD_BYTES_LONG} is set to this value, the platform's + * default data warning, if one exists, will be disabled. A user selected data warning will not + * be overridden. + * <p> + * If {@link #KEY_DATA_LIMIT_THRESHOLD_BYTES_LONG} is set to this value, the platform's + * default data limit, if one exists, will be disabled. A user selected data limit will not be + * overridden. + */ + public static final int DATA_CYCLE_THRESHOLD_DISABLED = -2; + + /** + * Controls the data usage warning. + * <p> + * If the user uses more than this amount of data in their billing cycle, as defined by + * {@link #KEY_MONTHLY_DATA_CYCLE_DAY_INT}, the user will be alerted about the usage. + * If the value is set to {@link #DATA_CYCLE_THRESHOLD_DISABLED}, the data usage warning will + * be disabled. + * <p> + * This setting may be overridden by explicit user choice. By default, the platform value + * will be used. + */ + public static final String KEY_DATA_WARNING_THRESHOLD_BYTES_LONG = + "data_warning_threshold_bytes_long"; + + /** + * Controls the cellular data limit. + * <p> + * If the user uses more than this amount of data in their billing cycle, as defined by + * {@link #KEY_MONTHLY_DATA_CYCLE_DAY_INT}, cellular data will be turned off by the user's + * phone. If the value is set to {@link #DATA_CYCLE_THRESHOLD_DISABLED}, the data limit will be + * disabled. + * <p> + * This setting may be overridden by explicit user choice. By default, the platform value + * will be used. + */ + public static final String KEY_DATA_LIMIT_THRESHOLD_BYTES_LONG = + "data_limit_threshold_bytes_long"; + + /** + * Offset to be reduced from rsrp threshold while calculating signal strength level. + * @hide + */ + public static final String KEY_LTE_EARFCNS_RSRP_BOOST_INT = "lte_earfcns_rsrp_boost_int"; + + /** + * List of EARFCN (E-UTRA Absolute Radio Frequency Channel Number, + * Reference: 3GPP TS 36.104 5.4.3) inclusive ranges on which lte_rsrp_boost_int + * will be applied. Format of the String array is expected to be {"erafcn1_start-earfcn1_end", + * "earfcn2_start-earfcn2_end" ... } + * @hide + */ + public static final String KEY_BOOSTED_LTE_EARFCNS_STRING_ARRAY = + "boosted_lte_earfcns_string_array"; + /** The default value for every variable. */ private final static PersistableBundle sDefaults; @@ -1515,6 +1593,10 @@ public class CarrierConfigManager { }); sDefaults.putStringArray(KEY_CARRIER_DEFAULT_REDIRECTION_URL_STRING_ARRAY, null); + sDefaults.putInt(KEY_MONTHLY_DATA_CYCLE_DAY_INT, DATA_CYCLE_USE_PLATFORM_DEFAULT); + sDefaults.putLong(KEY_DATA_WARNING_THRESHOLD_BYTES_LONG, DATA_CYCLE_USE_PLATFORM_DEFAULT); + sDefaults.putLong(KEY_DATA_LIMIT_THRESHOLD_BYTES_LONG, DATA_CYCLE_USE_PLATFORM_DEFAULT); + // Rat families: {GPRS, EDGE}, {EVDO, EVDO_A, EVDO_B}, {UMTS, HSPA, HSDPA, HSUPA, HSPAP}, // {LTE, LTE_CA} // Order is important - lowest precidence first @@ -1541,6 +1623,8 @@ public class CarrierConfigManager { sDefaults.putBoolean(KEY_EDITABLE_TETHER_APN_BOOL, false); sDefaults.putStringArray(KEY_CALL_FORWARDING_BLOCKS_WHILE_ROAMING_STRING_ARRAY, null); + sDefaults.putInt(KEY_LTE_EARFCNS_RSRP_BOOST_INT, 0); + sDefaults.putStringArray(KEY_BOOSTED_LTE_EARFCNS_STRING_ARRAY, null); } /** diff --git a/telephony/java/android/telephony/PhoneNumberUtils.java b/telephony/java/android/telephony/PhoneNumberUtils.java index a3e11c8c99f8..199a12ae70df 100644 --- a/telephony/java/android/telephony/PhoneNumberUtils.java +++ b/telephony/java/android/telephony/PhoneNumberUtils.java @@ -1620,7 +1620,7 @@ public class PhoneNumberUtils // // Australia: Short codes are six or eight digits in length, starting with the prefix "19" // followed by an additional four or six digits and two. - // Czech Republic: Codes are seven digits in length for MO and five (not billed) or + // Czechia: Codes are seven digits in length for MO and five (not billed) or // eight (billed) for MT direction // // see http://en.wikipedia.org/wiki/Short_code#Regional_differences for reference diff --git a/telephony/java/android/telephony/ServiceState.java b/telephony/java/android/telephony/ServiceState.java index 7a83979ef61a..5fb83ab42866 100644 --- a/telephony/java/android/telephony/ServiceState.java +++ b/telephony/java/android/telephony/ServiceState.java @@ -243,6 +243,10 @@ public class ServiceState implements Parcelable { private boolean mIsUsingCarrierAggregation; + /* EARFCN stands for E-UTRA Absolute Radio Frequency Channel Number, + * Reference: 3GPP TS 36.104 5.4.3 */ + private int mLteEarfcnRsrpBoost = 0; + /** * get String description of roaming type * @hide @@ -322,6 +326,7 @@ public class ServiceState implements Parcelable { mIsEmergencyOnly = s.mIsEmergencyOnly; mIsDataRoamingFromRegistration = s.mIsDataRoamingFromRegistration; mIsUsingCarrierAggregation = s.mIsUsingCarrierAggregation; + mLteEarfcnRsrpBoost = s.mLteEarfcnRsrpBoost; } /** @@ -351,6 +356,7 @@ public class ServiceState implements Parcelable { mIsEmergencyOnly = in.readInt() != 0; mIsDataRoamingFromRegistration = in.readInt() != 0; mIsUsingCarrierAggregation = in.readInt() != 0; + mLteEarfcnRsrpBoost = in.readInt(); } public void writeToParcel(Parcel out, int flags) { @@ -377,6 +383,7 @@ public class ServiceState implements Parcelable { out.writeInt(mIsEmergencyOnly ? 1 : 0); out.writeInt(mIsDataRoamingFromRegistration ? 1 : 0); out.writeInt(mIsUsingCarrierAggregation ? 1 : 0); + out.writeInt(mLteEarfcnRsrpBoost); } public int describeContents() { @@ -814,7 +821,8 @@ public class ServiceState implements Parcelable { + " DefRoamInd=" + mCdmaDefaultRoamingIndicator + " EmergOnly=" + mIsEmergencyOnly + " IsDataRoamingFromRegistration=" + mIsDataRoamingFromRegistration - + " IsUsingCarrierAggregation=" + mIsUsingCarrierAggregation); + + " IsUsingCarrierAggregation=" + mIsUsingCarrierAggregation + + " mLteEarfcnRsrpBoost=" + mLteEarfcnRsrpBoost); } private void setNullState(int state) { @@ -842,6 +850,7 @@ public class ServiceState implements Parcelable { mIsEmergencyOnly = false; mIsDataRoamingFromRegistration = false; mIsUsingCarrierAggregation = false; + mLteEarfcnRsrpBoost = 0; } public void setStateOutOfService() { @@ -1016,6 +1025,7 @@ public class ServiceState implements Parcelable { mIsEmergencyOnly = m.getBoolean("emergencyOnly"); mIsDataRoamingFromRegistration = m.getBoolean("isDataRoamingFromRegistration"); mIsUsingCarrierAggregation = m.getBoolean("isUsingCarrierAggregation"); + mLteEarfcnRsrpBoost = m.getInt("LteEarfcnRsrpBoost"); } /** @@ -1046,6 +1056,7 @@ public class ServiceState implements Parcelable { m.putBoolean("emergencyOnly", mIsEmergencyOnly); m.putBoolean("isDataRoamingFromRegistration", mIsDataRoamingFromRegistration); m.putBoolean("isUsingCarrierAggregation", mIsUsingCarrierAggregation); + m.putInt("LteEarfcnRsrpBoost", mLteEarfcnRsrpBoost); } /** @hide */ @@ -1081,6 +1092,16 @@ public class ServiceState implements Parcelable { } /** @hide */ + public int getLteEarfcnRsrpBoost() { + return mLteEarfcnRsrpBoost; + } + + /** @hide */ + public void setLteEarfcnRsrpBoost(int LteEarfcnRsrpBoost) { + mLteEarfcnRsrpBoost = LteEarfcnRsrpBoost; + } + + /** @hide */ public void setCssIndicator(int css) { this.mCssIndicator = (css != 0); } diff --git a/telephony/java/android/telephony/SignalStrength.java b/telephony/java/android/telephony/SignalStrength.java index c484fd31c467..9e02399338fc 100644 --- a/telephony/java/android/telephony/SignalStrength.java +++ b/telephony/java/android/telephony/SignalStrength.java @@ -64,6 +64,8 @@ public class SignalStrength implements Parcelable { private int mLteRsrq; private int mLteRssnr; private int mLteCqi; + private int mLteRsrpBoost; // offset to be reduced from the rsrp threshold while calculating + // signal strength level private int mTdScdmaRscp; private boolean isGsm; // This value is set by the ServiceStateTracker onSignalStrengthResult @@ -104,6 +106,7 @@ public class SignalStrength implements Parcelable { mLteRsrq = INVALID; mLteRssnr = INVALID; mLteCqi = INVALID; + mLteRsrpBoost = 0; mTdScdmaRscp = INVALID; isGsm = true; } @@ -129,6 +132,7 @@ public class SignalStrength implements Parcelable { mLteRsrq = INVALID; mLteRssnr = INVALID; mLteCqi = INVALID; + mLteRsrpBoost = 0; mTdScdmaRscp = INVALID; isGsm = gsmFlag; } @@ -142,10 +146,26 @@ public class SignalStrength implements Parcelable { int cdmaDbm, int cdmaEcio, int evdoDbm, int evdoEcio, int evdoSnr, int lteSignalStrength, int lteRsrp, int lteRsrq, int lteRssnr, int lteCqi, + int lteRsrpBoost, int tdScdmaRscp, boolean gsmFlag) { + initialize(gsmSignalStrength, gsmBitErrorRate, cdmaDbm, cdmaEcio, + evdoDbm, evdoEcio, evdoSnr, lteSignalStrength, lteRsrp, + lteRsrq, lteRssnr, lteCqi, lteRsrpBoost, gsmFlag); + mTdScdmaRscp = tdScdmaRscp; + } + + /** + * Constructor + * + * @hide + */ + public SignalStrength(int gsmSignalStrength, int gsmBitErrorRate, + int cdmaDbm, int cdmaEcio, + int evdoDbm, int evdoEcio, int evdoSnr, + int lteSignalStrength, int lteRsrp, int lteRsrq, int lteRssnr, int lteCqi, int tdScdmaRscp, boolean gsmFlag) { initialize(gsmSignalStrength, gsmBitErrorRate, cdmaDbm, cdmaEcio, evdoDbm, evdoEcio, evdoSnr, lteSignalStrength, lteRsrp, - lteRsrq, lteRssnr, lteCqi, gsmFlag); + lteRsrq, lteRssnr, lteCqi, 0, gsmFlag); mTdScdmaRscp = tdScdmaRscp; } @@ -161,7 +181,7 @@ public class SignalStrength implements Parcelable { boolean gsmFlag) { initialize(gsmSignalStrength, gsmBitErrorRate, cdmaDbm, cdmaEcio, evdoDbm, evdoEcio, evdoSnr, lteSignalStrength, lteRsrp, - lteRsrq, lteRssnr, lteCqi, gsmFlag); + lteRsrq, lteRssnr, lteCqi, 0, gsmFlag); } /** @@ -175,7 +195,7 @@ public class SignalStrength implements Parcelable { boolean gsmFlag) { initialize(gsmSignalStrength, gsmBitErrorRate, cdmaDbm, cdmaEcio, evdoDbm, evdoEcio, evdoSnr, 99, INVALID, - INVALID, INVALID, INVALID, gsmFlag); + INVALID, INVALID, INVALID, 0, gsmFlag); } /** @@ -209,7 +229,7 @@ public class SignalStrength implements Parcelable { boolean gsm) { initialize(gsmSignalStrength, gsmBitErrorRate, cdmaDbm, cdmaEcio, evdoDbm, evdoEcio, evdoSnr, 99, INVALID, - INVALID, INVALID, INVALID, gsm); + INVALID, INVALID, INVALID, 0, gsm); } /** @@ -227,6 +247,7 @@ public class SignalStrength implements Parcelable { * @param lteRsrq * @param lteRssnr * @param lteCqi + * @param lteRsrpBoost * @param gsm * * @hide @@ -235,7 +256,7 @@ public class SignalStrength implements Parcelable { int cdmaDbm, int cdmaEcio, int evdoDbm, int evdoEcio, int evdoSnr, int lteSignalStrength, int lteRsrp, int lteRsrq, int lteRssnr, int lteCqi, - boolean gsm) { + int lteRsrpBoost, boolean gsm) { mGsmSignalStrength = gsmSignalStrength; mGsmBitErrorRate = gsmBitErrorRate; mCdmaDbm = cdmaDbm; @@ -248,6 +269,7 @@ public class SignalStrength implements Parcelable { mLteRsrq = lteRsrq; mLteRssnr = lteRssnr; mLteCqi = lteCqi; + mLteRsrpBoost = lteRsrpBoost; mTdScdmaRscp = INVALID; isGsm = gsm; if (DBG) log("initialize: " + toString()); @@ -269,6 +291,7 @@ public class SignalStrength implements Parcelable { mLteRsrq = s.mLteRsrq; mLteRssnr = s.mLteRssnr; mLteCqi = s.mLteCqi; + mLteRsrpBoost = s.mLteRsrpBoost; mTdScdmaRscp = s.mTdScdmaRscp; isGsm = s.isGsm; } @@ -293,6 +316,7 @@ public class SignalStrength implements Parcelable { mLteRsrq = in.readInt(); mLteRssnr = in.readInt(); mLteCqi = in.readInt(); + mLteRsrpBoost = in.readInt(); mTdScdmaRscp = in.readInt(); isGsm = (in.readInt() != 0); } @@ -340,6 +364,7 @@ public class SignalStrength implements Parcelable { out.writeInt(mLteRsrq); out.writeInt(mLteRssnr); out.writeInt(mLteCqi); + out.writeInt(mLteRsrpBoost); out.writeInt(mTdScdmaRscp); out.writeInt(isGsm ? 1 : 0); } @@ -416,6 +441,18 @@ public class SignalStrength implements Parcelable { } /** + * @param lteRsrpBoost - signal strength offset + * + * Used by phone to set the lte signal strength offset which will be + * reduced from rsrp threshold while calculating signal strength level + * + * @hide + */ + public void setLteRsrpBoost(int lteRsrpBoost) { + mLteRsrpBoost = lteRsrpBoost; + } + + /** * Get the GSM Signal Strength, valid values are (0-31, 99) as defined in TS * 27.007 8.5 */ @@ -490,6 +527,11 @@ public class SignalStrength implements Parcelable { return mLteCqi; } + /** @hide */ + public int getLteRsrpBoost() { + return mLteRsrpBoost; + } + /** * Retrieve an abstract level value for the overall signal strength. * @@ -793,12 +835,19 @@ public class SignalStrength implements Parcelable { Log.wtf(LOG_TAG, "getLteLevel - config_lteDbmThresholds has invalid num of elements." + " Cannot evaluate RSRP signal."); } else { - if (mLteRsrp > threshRsrp[5]) rsrpIconLevel = -1; - else if (mLteRsrp >= threshRsrp[4]) rsrpIconLevel = SIGNAL_STRENGTH_GREAT; - else if (mLteRsrp >= threshRsrp[3]) rsrpIconLevel = SIGNAL_STRENGTH_GOOD; - else if (mLteRsrp >= threshRsrp[2]) rsrpIconLevel = SIGNAL_STRENGTH_MODERATE; - else if (mLteRsrp >= threshRsrp[1]) rsrpIconLevel = SIGNAL_STRENGTH_POOR; - else if (mLteRsrp >= threshRsrp[0]) rsrpIconLevel = SIGNAL_STRENGTH_NONE_OR_UNKNOWN; + if (mLteRsrp > threshRsrp[5]) { + rsrpIconLevel = -1; + } else if (mLteRsrp >= (threshRsrp[4] - mLteRsrpBoost)) { + rsrpIconLevel = SIGNAL_STRENGTH_GREAT; + } else if (mLteRsrp >= (threshRsrp[3] - mLteRsrpBoost)) { + rsrpIconLevel = SIGNAL_STRENGTH_GOOD; + } else if (mLteRsrp >= (threshRsrp[2] - mLteRsrpBoost)) { + rsrpIconLevel = SIGNAL_STRENGTH_MODERATE; + } else if (mLteRsrp >= (threshRsrp[1] - mLteRsrpBoost)) { + rsrpIconLevel = SIGNAL_STRENGTH_POOR; + } else if (mLteRsrp >= threshRsrp[0]) { + rsrpIconLevel = SIGNAL_STRENGTH_NONE_OR_UNKNOWN; + } } /* @@ -816,7 +865,8 @@ public class SignalStrength implements Parcelable { snrIconLevel = SIGNAL_STRENGTH_NONE_OR_UNKNOWN; if (DBG) log("getLTELevel - rsrp:" + mLteRsrp + " snr:" + mLteRssnr + " rsrpIconLevel:" - + rsrpIconLevel + " snrIconLevel:" + snrIconLevel); + + rsrpIconLevel + " snrIconLevel:" + snrIconLevel + + " lteRsrpBoost:" + mLteRsrpBoost); /* Choose a measurement type to use for notification */ if (snrIconLevel != -1 && rsrpIconLevel != -1) { @@ -939,7 +989,7 @@ public class SignalStrength implements Parcelable { + (mEvdoDbm * primeNum) + (mEvdoEcio * primeNum) + (mEvdoSnr * primeNum) + (mLteSignalStrength * primeNum) + (mLteRsrp * primeNum) + (mLteRsrq * primeNum) + (mLteRssnr * primeNum) + (mLteCqi * primeNum) - + (mTdScdmaRscp * primeNum) + (isGsm ? 1 : 0)); + + (mLteRsrpBoost * primeNum) + (mTdScdmaRscp * primeNum) + (isGsm ? 1 : 0)); } /** @@ -971,6 +1021,7 @@ public class SignalStrength implements Parcelable { && mLteRsrq == s.mLteRsrq && mLteRssnr == s.mLteRssnr && mLteCqi == s.mLteCqi + && mLteRsrpBoost == s.mLteRsrpBoost && mTdScdmaRscp == s.mTdScdmaRscp && isGsm == s.isGsm); } @@ -993,6 +1044,7 @@ public class SignalStrength implements Parcelable { + " " + mLteRsrq + " " + mLteRssnr + " " + mLteCqi + + " " + mLteRsrpBoost + " " + mTdScdmaRscp + " " + (isGsm ? "gsm|lte" : "cdma")); } @@ -1016,6 +1068,7 @@ public class SignalStrength implements Parcelable { mLteRsrq = m.getInt("LteRsrq"); mLteRssnr = m.getInt("LteRssnr"); mLteCqi = m.getInt("LteCqi"); + mLteRsrpBoost = m.getInt("lteRsrpBoost"); mTdScdmaRscp = m.getInt("TdScdma"); isGsm = m.getBoolean("isGsm"); } @@ -1039,6 +1092,7 @@ public class SignalStrength implements Parcelable { m.putInt("LteRsrq", mLteRsrq); m.putInt("LteRssnr", mLteRssnr); m.putInt("LteCqi", mLteCqi); + m.putInt("lteRsrpBoost", mLteRsrpBoost); m.putInt("TdScdma", mTdScdmaRscp); m.putBoolean("isGsm", isGsm); } diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index 51b91f4c7999..786252318023 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -5276,23 +5276,42 @@ public class TelephonyManager { } } + + /** + * @deprecated use {@link #isDataEnabled()} instead. + * @hide + */ + @SystemApi + @Deprecated + public boolean getDataEnabled() { + return isDataEnabled(); + } + /** * Returns whether mobile data is enabled or not. * - * <p>Requires Permission: - * {@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. + * <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. + * + * <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 */ - public boolean getDataEnabled() { + @SuppressWarnings("deprecation") + public boolean isDataEnabled() { return getDataEnabled(getSubId()); } - /** @hide */ + /** + * @deprecated use {@link #isDataEnabled(int)} instead. + * @hide + */ @SystemApi public boolean getDataEnabled(int subId) { boolean retVal = false; diff --git a/test-runner/src/android/test/mock/MockPackageManager.java b/test-runner/src/android/test/mock/MockPackageManager.java index 29ba77663fd0..506f406c333f 100644 --- a/test-runner/src/android/test/mock/MockPackageManager.java +++ b/test-runner/src/android/test/mock/MockPackageManager.java @@ -643,6 +643,12 @@ public class MockPackageManager extends PackageManager { throw new UnsupportedOperationException(); } + /** @hide */ + @Override + public void setUpdateAvailable(String packageName, boolean updateAvailable) { + throw new UnsupportedOperationException(); + } + @Override public String getInstallerPackageName(String packageName) { throw new UnsupportedOperationException(); diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/ColorFiltersMutateActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/ColorFiltersMutateActivity.java index 808b5d39b44e..a7bdabd64684 100644 --- a/tests/HwAccelerationTest/src/com/android/test/hwui/ColorFiltersMutateActivity.java +++ b/tests/HwAccelerationTest/src/com/android/test/hwui/ColorFiltersMutateActivity.java @@ -138,7 +138,7 @@ public class ColorFiltersMutateActivity extends Activity { mSaturation = saturation; final ColorMatrixColorFilter filter = (ColorMatrixColorFilter) mColorMatrixPaint.getColorFilter(); - final ColorMatrix m = filter.getColorMatrix(); + final ColorMatrix m = new ColorMatrix(); m.setSaturation(saturation); filter.setColorMatrix(m); invalidate(); diff --git a/tests/net/Android.mk b/tests/net/Android.mk index 79f6e4d2f000..504d54eeb3a9 100644 --- a/tests/net/Android.mk +++ b/tests/net/Android.mk @@ -24,6 +24,7 @@ LOCAL_JAVA_LIBRARIES := \ android.test.runner LOCAL_PACKAGE_NAME := FrameworksNetTests +LOCAL_COMPATIBILITY_SUITE := device-tests LOCAL_CERTIFICATE := platform diff --git a/tests/net/java/com/android/server/connectivity/IpConnectivityEventBuilderTest.java b/tests/net/java/com/android/server/connectivity/IpConnectivityEventBuilderTest.java index 11105d6de75b..48861bde77b3 100644 --- a/tests/net/java/com/android/server/connectivity/IpConnectivityEventBuilderTest.java +++ b/tests/net/java/com/android/server/connectivity/IpConnectivityEventBuilderTest.java @@ -304,6 +304,7 @@ public class IpConnectivityEventBuilderTest extends TestCase { ConnectivityMetricsEvent ev = describeIpEvent( aType(ApfProgramEvent.class), aLong(200), + aLong(18), anInt(7), anInt(9), anInt(2048), @@ -320,7 +321,7 @@ public class IpConnectivityEventBuilderTest extends TestCase { " apf_program_event <", " current_ras: 9", " drop_multicast: true", - " effective_lifetime: 0", + " effective_lifetime: 18", " filtered_ras: 7", " has_ipv4_addr: true", " lifetime: 200", @@ -343,6 +344,8 @@ public class IpConnectivityEventBuilderTest extends TestCase { anInt(1), anInt(2), anInt(4), + anInt(7), + anInt(3), anInt(2048)); String want = joinLines( @@ -360,8 +363,8 @@ public class IpConnectivityEventBuilderTest extends TestCase { " max_program_size: 2048", " parse_errors: 2", " program_updates: 4", - " program_updates_all: 0", - " program_updates_allowing_multicast: 0", + " program_updates_all: 7", + " program_updates_allowing_multicast: 3", " received_ras: 10", " zero_lifetime_ras: 1", " >", diff --git a/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java b/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java index 1f7c5f418d74..785e1ce20ab5 100644 --- a/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java +++ b/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java @@ -85,7 +85,7 @@ public class IpConnectivityMetricsTest extends TestCase { new Thread() { public void run() { for (int j = 0; j < nEvents; j++) { - assertTrue(logger.log(i * 100 + j, FAKE_EV)); + assertTrue(logger.log(1 + i * 100 + j, FAKE_EV)); } } }.start(); @@ -96,7 +96,7 @@ public class IpConnectivityMetricsTest extends TestCase { Iterator<ConnectivityMetricsEvent> iter = got.iterator(); for (int i = 0; i < nCallers; i++) { for (int j = 0; j < nEvents; j++) { - int expectedTimestamp = i * 100 + j; + int expectedTimestamp = 1 + i * 100 + j; assertEventsEqual(expectedEvent(expectedTimestamp), iter.next()); } } @@ -118,7 +118,7 @@ public class IpConnectivityMetricsTest extends TestCase { @SmallTest public void testRateLimiting() { final IpConnectivityLog logger = new IpConnectivityLog(mService.impl); - final ApfProgramEvent ev = new ApfProgramEvent(0, 0, 0, 0, 0); + final ApfProgramEvent ev = new ApfProgramEvent(); final long fakeTimestamp = 1; int attempt = 100; // More than burst quota, but less than buffer size. @@ -142,13 +142,24 @@ public class IpConnectivityMetricsTest extends TestCase { // TODO: instead of comparing textpb to textpb, parse textpb and compare proto to proto. IpConnectivityLog logger = new IpConnectivityLog(mService.impl); + ApfStats apfStats = new ApfStats(); + apfStats.durationMs = 45000; + apfStats.receivedRas = 10; + apfStats.matchingRas = 2; + apfStats.droppedRas = 2; + apfStats.parseErrors = 2; + apfStats.zeroLifetimeRas = 1; + apfStats.programUpdates = 4; + apfStats.programUpdatesAll = 7; + apfStats.programUpdatesAllowingMulticast = 3; + apfStats.maxProgramSize = 2048; Parcelable[] events = { new IpReachabilityEvent("wlan0", IpReachabilityEvent.NUD_FAILED), new DhcpClientEvent("wlan0", "SomeState", 192), new DefaultNetworkEvent(102, new int[]{1,2,3}, 101, true, false), new IpManagerEvent("wlan0", IpManagerEvent.PROVISIONING_OK, 5678), new ValidationProbeEvent(120, 40730, ValidationProbeEvent.PROBE_HTTP, 204), - new ApfStats(45000, 10, 2, 2, 1, 2, 4, 2048), + apfStats, new RaEvent(2000, 400, 300, -1, 1000, -1) }; @@ -240,8 +251,8 @@ public class IpConnectivityMetricsTest extends TestCase { " max_program_size: 2048", " parse_errors: 2", " program_updates: 4", - " program_updates_all: 0", - " program_updates_allowing_multicast: 0", + " program_updates_all: 7", + " program_updates_allowing_multicast: 3", " received_ras: 10", " zero_lifetime_ras: 1", " >", @@ -304,14 +315,15 @@ public class IpConnectivityMetricsTest extends TestCase { } static ConnectivityMetricsEvent expectedEvent(int timestamp) { - return new ConnectivityMetricsEvent((long)timestamp, 0, 0, FAKE_EV); + ConnectivityMetricsEvent ev = new ConnectivityMetricsEvent(); + ev.timestamp = timestamp; + ev.data = FAKE_EV; + return ev; } /** Outer equality for ConnectivityMetricsEvent to avoid overriding equals() and hashCode(). */ static void assertEventsEqual(ConnectivityMetricsEvent expected, ConnectivityMetricsEvent got) { assertEquals(expected.timestamp, got.timestamp); - assertEquals(expected.componentTag, got.componentTag); - assertEquals(expected.eventTag, got.eventTag); assertEquals(expected.data, got.data); } diff --git a/tests/net/java/com/android/server/connectivity/MetricsTestUtil.java b/tests/net/java/com/android/server/connectivity/MetricsTestUtil.java index c5965e8ea833..5064b9bd91b9 100644 --- a/tests/net/java/com/android/server/connectivity/MetricsTestUtil.java +++ b/tests/net/java/com/android/server/connectivity/MetricsTestUtil.java @@ -28,7 +28,10 @@ abstract public class MetricsTestUtil { } static ConnectivityMetricsEvent ev(Parcelable p) { - return new ConnectivityMetricsEvent(1L, 0, 0, p); + ConnectivityMetricsEvent ev = new ConnectivityMetricsEvent(); + ev.timestamp = 1L; + ev.data = p; + return ev; } static ConnectivityMetricsEvent describeIpEvent(Consumer<Parcel>... fs) { diff --git a/tests/net/java/com/android/server/connectivity/NetdEventListenerServiceTest.java b/tests/net/java/com/android/server/connectivity/NetdEventListenerServiceTest.java index 9e0f32137e89..0ab440641d53 100644 --- a/tests/net/java/com/android/server/connectivity/NetdEventListenerServiceTest.java +++ b/tests/net/java/com/android/server/connectivity/NetdEventListenerServiceTest.java @@ -23,6 +23,7 @@ import android.net.metrics.ConnectStats; import android.net.metrics.DnsEvent; import android.net.metrics.INetdEventListener; import android.net.metrics.IpConnectivityLog; +import android.os.Parcelable; import android.os.RemoteException; import android.system.OsConstants; import android.test.suitebuilder.annotation.SmallTest; @@ -165,8 +166,8 @@ public class NetdEventListenerServiceTest extends TestCase { // call onLost() asynchronously to logDnsAsync's onDnsEvent() calls. mCallbackCaptor.getValue().onLost(new Network(105)); - // do not verify unpredictable batch - verify(mLog, timeout(500).times(1)).log(any()); + // do not verify batch with unpredictable length + verify(mLog, timeout(500).times(1)).log(any(Parcelable.class)); } @SmallTest @@ -279,11 +280,7 @@ public class NetdEventListenerServiceTest extends TestCase { } void logDnsAsync(int netId, int[] latencies) { - new Thread() { - public void run() { - log(netId, latencies); - } - }.start(); + new Thread(() -> log(netId, latencies)).start(); } void verifyLoggedDnsEvents(DnsEvent... expected) { diff --git a/tests/testables/Android.mk b/tests/testables/Android.mk new file mode 100644 index 000000000000..759bc3555fa1 --- /dev/null +++ b/tests/testables/Android.mk @@ -0,0 +1,33 @@ +# +# Copyright (C) 2017 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) + +LOCAL_MODULE := testables +LOCAL_MODULE_TAG := tests + +LOCAL_SRC_FILES := $(call all-java-files-under,src) + +LOCAL_STATIC_JAVA_LIBRARIES := \ + android-support-test \ + mockito-target-minus-junit4 \ + legacy-android-test + +LOCAL_JAVA_LIBRARIES := android.test.runner + +include $(BUILD_STATIC_JAVA_LIBRARY) diff --git a/packages/SystemUI/tests/src/com/android/systemui/SysUIRunner.java b/tests/testables/src/android/testing/AndroidTestingRunner.java index fd99d1d46db3..816ed033a3e2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/SysUIRunner.java +++ b/tests/testables/src/android/testing/AndroidTestingRunner.java @@ -12,14 +12,14 @@ * permissions and limitations under the License. */ -package com.android.systemui; +package android.testing; import android.support.test.internal.runner.junit4.statement.RunAfters; import android.support.test.internal.runner.junit4.statement.RunBefores; import android.support.test.internal.runner.junit4.statement.UiThreadStatement; -import com.android.systemui.utils.TestableLooper.LooperStatement; -import com.android.systemui.utils.TestableLooper.RunWithLooper; +import android.testing.TestableLooper.LooperStatement; +import android.testing.TestableLooper.RunWithLooper; import org.junit.After; import org.junit.Before; @@ -32,12 +32,15 @@ import org.junit.runners.model.Statement; import java.util.List; -public class SysUIRunner extends BlockJUnit4ClassRunner { +/** + * A runner with support for extra annotations provided by the Testables library. + */ +public class AndroidTestingRunner extends BlockJUnit4ClassRunner { private final long mTimeout; private final Class<?> mKlass; - public SysUIRunner(Class<?> klass) throws InitializationError { + public AndroidTestingRunner(Class<?> klass) throws InitializationError { super(klass); mKlass = klass; // Can't seem to get reference to timeout parameter from here, so set default to 10 mins. diff --git a/packages/SystemUI/tests/src/com/android/systemui/FragmentTestCase.java b/tests/testables/src/android/testing/BaseFragmentTest.java index 1678d9232652..53841d597e26 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/FragmentTestCase.java +++ b/tests/testables/src/android/testing/BaseFragmentTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016 The Android Open Source Project + * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of the License at @@ -12,7 +12,9 @@ * permissions and limitations under the License. */ -package com.android.systemui; +package android.testing; + +import static org.junit.Assert.assertNotNull; import android.annotation.Nullable; import android.app.Fragment; @@ -21,20 +23,17 @@ import android.app.FragmentHostCallback; import android.app.FragmentManagerNonConfig; import android.graphics.PixelFormat; import android.os.Handler; -import android.os.Looper; import android.os.Parcelable; +import android.support.test.InstrumentationRegistry; import android.view.LayoutInflater; import android.view.View; import android.view.WindowManager; import android.view.WindowManager.LayoutParams; import android.widget.FrameLayout; -import com.android.systemui.utils.TestableLooper; -import com.android.systemui.utils.ViewUtils; -import com.android.systemui.utils.leaks.LeakCheckedTest; - import org.junit.After; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import java.io.FileDescriptor; @@ -46,7 +45,7 @@ import java.io.PrintWriter; * the host for subclasses, so they can push it into desired states and do any unit testing * required. */ -public abstract class FragmentTestCase extends LeakCheckedTest { +public abstract class BaseFragmentTest { private static final int VIEW_ID = 42; private final Class<? extends Fragment> mCls; @@ -55,7 +54,10 @@ public abstract class FragmentTestCase extends LeakCheckedTest { protected FragmentController mFragments; protected Fragment mFragment; - public FragmentTestCase(Class<? extends Fragment> cls) { + @Rule + public final TestableContext mContext = getContext(); + + public BaseFragmentTest(Class<? extends Fragment> cls) { mCls = cls; } @@ -64,6 +66,8 @@ public abstract class FragmentTestCase extends LeakCheckedTest { mView = new FrameLayout(mContext); mView.setId(VIEW_ID); + assertNotNull("BaseFragmentTest must be tagged with @RunWithLooper", + TestableLooper.get(this)); TestableLooper.get(this).runWithLooper(() -> { mHandler = new Handler(); @@ -76,8 +80,8 @@ public abstract class FragmentTestCase extends LeakCheckedTest { }); } - private String hex(Looper looper) { - return Integer.toHexString(System.identityHashCode(looper)); + protected TestableContext getContext() { + return new TestableContext(InstrumentationRegistry.getContext()); } @After @@ -174,14 +178,14 @@ public abstract class FragmentTestCase extends LeakCheckedTest { return mView.findViewById(id); } - private class HostCallbacks extends FragmentHostCallback<FragmentTestCase> { + private class HostCallbacks extends FragmentHostCallback<BaseFragmentTest> { public HostCallbacks() { - super(mContext, FragmentTestCase.this.mHandler, 0); + super(mContext, BaseFragmentTest.this.mHandler, 0); } @Override - public FragmentTestCase onGetHost() { - return FragmentTestCase.this; + public BaseFragmentTest onGetHost() { + return BaseFragmentTest.this; } @Override @@ -220,7 +224,7 @@ public abstract class FragmentTestCase extends LeakCheckedTest { @Nullable @Override public View onFindViewById(int id) { - return FragmentTestCase.this.findViewById(id); + return BaseFragmentTest.this.findViewById(id); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/util/LayoutInflaterBuilder.java b/tests/testables/src/android/testing/LayoutInflaterBuilder.java index 5cfe677efd57..098302e1a0a1 100644 --- a/packages/SystemUI/src/com/android/systemui/util/LayoutInflaterBuilder.java +++ b/tests/testables/src/android/testing/LayoutInflaterBuilder.java @@ -1,20 +1,18 @@ /* - * Copyright (C) 2016 The Android Open Source Project + * Copyright (C) 2017 The Android Open Source Project * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * 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. + * 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.util; +package android.testing; import android.annotation.NonNull; import android.content.Context; diff --git a/tests/testables/src/android/testing/LeakCheck.java b/tests/testables/src/android/testing/LeakCheck.java new file mode 100644 index 000000000000..8daaa8f16664 --- /dev/null +++ b/tests/testables/src/android/testing/LeakCheck.java @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package android.testing; + +import android.util.ArrayMap; +import android.util.Log; + +import org.junit.Assert; +import org.junit.rules.TestWatcher; +import org.junit.runner.Description; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class LeakCheck extends TestWatcher { + + private final Map<String, Tracker> mTrackers = new HashMap<>(); + + public LeakCheck() { + } + + @Override + protected void succeeded(Description description) { + verify(); + } + + public Tracker getTracker(String tag) { + Tracker t = mTrackers.get(tag); + if (t == null) { + t = new Tracker(); + mTrackers.put(tag, t); + } + return t; + } + + public void verify() { + mTrackers.values().forEach(Tracker::verify); + } + + public static class LeakInfo { + private static final String TAG = "LeakInfo"; + private List<Throwable> mThrowables = new ArrayList<>(); + + LeakInfo() { + } + + public void addAllocation(Throwable t) { + // TODO: Drop off the first element in the stack trace here to have a cleaner stack. + mThrowables.add(t); + } + + public void clearAllocations() { + mThrowables.clear(); + } + + void verify() { + if (mThrowables.size() == 0) return; + Log.e(TAG, "Listener or binding not properly released"); + for (Throwable t : mThrowables) { + Log.e(TAG, "Allocation found", t); + } + StringWriter writer = new StringWriter(); + mThrowables.get(0).printStackTrace(new PrintWriter(writer)); + Assert.fail("Listener or binding not properly released\n" + + writer.toString()); + } + } + + public static class Tracker { + private Map<Object, LeakInfo> mObjects = new ArrayMap<>(); + + public LeakInfo getLeakInfo(Object object) { + LeakInfo leakInfo = mObjects.get(object); + if (leakInfo == null) { + leakInfo = new LeakInfo(); + mObjects.put(object, leakInfo); + } + return leakInfo; + } + + void verify() { + mObjects.values().forEach(LeakInfo::verify); + } + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/FakeContentResolver.java b/tests/testables/src/android/testing/TestableContentResolver.java index 34f2e019761d..bfafbe043ffb 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/utils/FakeContentResolver.java +++ b/tests/testables/src/android/testing/TestableContentResolver.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016 The Android Open Source Project + * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of the License at @@ -12,7 +12,7 @@ * permissions and limitations under the License. */ -package com.android.systemui.utils; +package android.testing; import android.content.ContentProvider; import android.content.ContentResolver; @@ -29,14 +29,14 @@ import java.util.Map; /** * Alternative to a MockContentResolver that falls back to real providers. */ -public class FakeContentResolver extends ContentResolver { +public class TestableContentResolver extends ContentResolver { private final Map<String, ContentProvider> mProviders = Maps.newHashMap(); private final ContentResolver mParent; private final ArraySet<ContentProvider> mInUse = new ArraySet<>(); private boolean mFallbackToExisting; - public FakeContentResolver(Context context) { + public TestableContentResolver(Context context) { super(context); mParent = context.getContentResolver(); mFallbackToExisting = true; diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/TestableContext.java b/tests/testables/src/android/testing/TestableContext.java index 1429390d193a..cb5d4cb8242f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/utils/TestableContext.java +++ b/tests/testables/src/android/testing/TestableContext.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016 The Android Open Source Project + * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of the License at @@ -12,7 +12,7 @@ * permissions and limitations under the License. */ -package com.android.systemui.utils; +package android.testing; import android.content.BroadcastReceiver; import android.content.ComponentCallbacks; @@ -32,36 +32,59 @@ import android.provider.Settings; import android.util.ArrayMap; import android.view.LayoutInflater; -import com.android.systemui.SysUiServiceProvider; -import com.android.systemui.SysuiTestCase; -import com.android.systemui.utils.leaks.Tracker; +import org.junit.rules.TestRule; +import org.junit.rules.TestWatcher; +import org.junit.runner.Description; +import org.junit.runners.model.Statement; -public class TestableContext extends ContextWrapper implements SysUiServiceProvider { +/** + * A ContextWrapper with utilities specifically designed to make Testing easier. + * + * <ul> + * <li>System services can be mocked out with {@link #addMockSystemService}</li> + * <li>Service binding can be mocked out with {@link #addMockService}</li> + * <li>Settings support {@link TestableSettings}</li> + * <li>Has support for {@link LeakCheck} for services and receivers</li> + * </ul> + * + * <p>TestableContext should be defined as a rule on your test so it can clean up after itself. + * Like the following:</p> + * <pre class="prettyprint"> + * {@literal + * @Rule + * private final TestableContext mContext = new TestableContext(InstrumentationRegister.getContext()); + * } + * </pre> + */ +public class TestableContext extends ContextWrapper implements TestRule { - private final FakeContentResolver mFakeContentResolver; - private final FakeSettingsProvider mSettingsProvider; + private final TestableContentResolver mTestableContentResolver; + private final TestableSettings mSettingsProvider; private ArrayMap<String, Object> mMockSystemServices; private ArrayMap<ComponentName, IBinder> mMockServices; private ArrayMap<ServiceConnection, ComponentName> mActiveServices; - private ArrayMap<Class<?>, Object> mComponents; private PackageManager mMockPackageManager; - private Tracker mReceiver; - private Tracker mService; - private Tracker mComponent; + private LeakCheck.Tracker mReceiver; + private LeakCheck.Tracker mService; + private LeakCheck.Tracker mComponent; + + public TestableContext(Context base) { + this(base, null); + } - public TestableContext(Context base, SysuiTestCase test) { + public TestableContext(Context base, LeakCheck check) { super(base); - mFakeContentResolver = new FakeContentResolver(base); + mTestableContentResolver = new TestableContentResolver(base); ContentProviderClient settings = base.getContentResolver() .acquireContentProviderClient(Settings.AUTHORITY); - mSettingsProvider = FakeSettingsProvider.getFakeSettingsProvider(settings, - mFakeContentResolver); - mFakeContentResolver.addProvider(Settings.AUTHORITY, mSettingsProvider); - mReceiver = test.getTracker("receiver"); - mService = test.getTracker("service"); - mComponent = test.getTracker("component"); + mSettingsProvider = TestableSettings.getFakeSettingsProvider(settings, + mTestableContentResolver); + mTestableContentResolver.addProvider(Settings.AUTHORITY, mSettingsProvider.getProvider()); + mReceiver = check != null ? check.getTracker("receiver") : null; + mService = check != null ? check.getTracker("service") : null; + mComponent = check != null ? check.getTracker("component") : null; } public void setMockPackageManager(PackageManager mock) { @@ -86,19 +109,15 @@ public class TestableContext extends ContextWrapper implements SysUiServiceProvi } public void addMockSystemService(String name, Object service) { - mMockSystemServices = lazyInit(mMockSystemServices); + if (mMockSystemServices == null) mMockSystemServices = new ArrayMap<>(); mMockSystemServices.put(name, service); } public void addMockService(ComponentName component, IBinder service) { - mMockServices = lazyInit(mMockServices); + if (mMockServices == null) mMockServices = new ArrayMap<>(); mMockServices.put(component, service); } - private <T, V> ArrayMap<T, V> lazyInit(ArrayMap<T, V> services) { - return services != null ? services : new ArrayMap<T, V>(); - } - @Override public Object getSystemService(String name) { if (mMockSystemServices != null && mMockSystemServices.containsKey(name)) { @@ -110,13 +129,13 @@ public class TestableContext extends ContextWrapper implements SysUiServiceProvi return super.getSystemService(name); } - public FakeSettingsProvider getSettingsProvider() { + public TestableSettings getSettingsProvider() { return mSettingsProvider; } @Override - public FakeContentResolver getContentResolver() { - return mFakeContentResolver; + public TestableContentResolver getContentResolver() { + return mTestableContentResolver; } @Override @@ -177,7 +196,7 @@ public class TestableContext extends ContextWrapper implements SysUiServiceProvi private boolean checkMocks(ComponentName component, ServiceConnection conn) { if (mMockServices != null && component != null && mMockServices.containsKey(component)) { - mActiveServices = lazyInit(mActiveServices); + if (mActiveServices == null) mActiveServices = new ArrayMap<>(); mActiveServices.put(conn, component); conn.onServiceConnected(component, mMockServices.get(component)); return true; @@ -212,13 +231,18 @@ public class TestableContext extends ContextWrapper implements SysUiServiceProvi super.unregisterComponentCallbacks(callback); } - @SuppressWarnings("unchecked") - public <T> T getComponent(Class<T> interfaceType) { - return (T) (mComponents != null ? mComponents.get(interfaceType) : null); - } - - public <T, C extends T> void putComponent(Class<T> interfaceType, C component) { - mComponents = lazyInit(mComponents); - mComponents.put(interfaceType, component); + @Override + public Statement apply(Statement base, Description description) { + return new TestWatcher() { + @Override + protected void succeeded(Description description) { + mSettingsProvider.clearOverrides(); + } + + @Override + protected void failed(Throwable e, Description description) { + mSettingsProvider.clearOverrides(); + } + }.apply(base, description); } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/TestableImageView.java b/tests/testables/src/android/testing/TestableImageView.java index b131460e1eff..901e25b143b8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/utils/TestableImageView.java +++ b/tests/testables/src/android/testing/TestableImageView.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016 The Android Open Source Project + * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of the License at @@ -12,7 +12,7 @@ * permissions and limitations under the License. */ -package com.android.systemui.utils; +package android.testing; import android.annotation.DrawableRes; import android.annotation.Nullable; diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/TestableLooper.java b/tests/testables/src/android/testing/TestableLooper.java index 8902e0c23b5f..8a33cf918646 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/utils/TestableLooper.java +++ b/tests/testables/src/android/testing/TestableLooper.java @@ -12,7 +12,7 @@ * permissions and limitations under the License. */ -package com.android.systemui.utils; +package android.testing; import android.os.Handler; import android.os.Looper; diff --git a/tests/testables/src/android/testing/TestableSettings.java b/tests/testables/src/android/testing/TestableSettings.java new file mode 100644 index 000000000000..d19f1ef60b2e --- /dev/null +++ b/tests/testables/src/android/testing/TestableSettings.java @@ -0,0 +1,318 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package android.testing; + +import android.content.ContentProvider; +import android.content.ContentProviderClient; +import android.content.ContentResolver; +import android.os.Bundle; +import android.os.RemoteException; +import android.provider.Settings; +import android.support.annotation.VisibleForTesting; +import android.test.mock.MockContentProvider; +import android.testing.TestableSettings.SettingOverrider.Builder; +import android.util.ArrayMap; +import android.util.ArraySet; +import android.util.Log; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * Allows calls to android.provider.Settings to be tested easier. A SettingOverride + * can be acquired and a set of specific settings can be set to a value (and not changed + * in the system when set), so that they can be tested without breaking the test device. + * <p> + * To use, in the before method acquire the override add all settings that will affect if + * your test passes or not. + * + * <pre class="prettyprint"> + * {@literal + * mSettingOverride = mTestableContext.getSettingsProvider().acquireOverridesBuilder() + * .addSetting("secure", Secure.USER_SETUP_COMPLETE, "0") + * .build(); + * } + * </pre> + * + * Then in the after free up the settings. + * + * <pre class="prettyprint"> + * {@literal + * mSettingOverride.release(); + * } + * </pre> + */ +public class TestableSettings { + + private static final String TAG = "TestableSettings"; + private static final boolean DEBUG = false; + + // Number of times to try to acquire a setting if in use. + private static final int MAX_TRIES = 10; + // Time to wait for each setting. WAIT_TIMEOUT * MAX_TRIES will be the maximum wait time + // for a setting. + private static final long WAIT_TIMEOUT = 1000; + + private static TestableSettingsProvider sInstance; + + private final TestableSettingsProvider mProvider; + + private TestableSettings(TestableSettingsProvider provider) { + mProvider = provider; + } + + public Builder acquireOverridesBuilder() { + return new Builder(this); + } + + public void clearOverrides() { + List<SettingOverrider> overrides = mProvider.mOwners.remove(this); + if (overrides != null) { + overrides.forEach(override -> override.ensureReleased()); + } + } + + private void acquireSettings(SettingOverrider overridder, Set<String> keys) + throws AcquireTimeoutException { + mProvider.acquireSettings(overridder, keys, this); + } + + ContentProvider getProvider() { + return mProvider; + } + + @VisibleForTesting + Object getLock() { + return mProvider.mOverrideMap; + } + + public static class SettingOverrider { + private final Set<String> mValidKeys; + private final Map<String, String> mValueMap = new ArrayMap<>(); + private final TestableSettings mSettings; + private boolean mReleased; + public Throwable mObtain; + + private SettingOverrider(Set<String> keys, TestableSettings provider) { + mValidKeys = new ArraySet<>(keys); + mSettings = provider; + } + + private void ensureReleased() { + if (!mReleased) { + release(); + } + } + + public void release() { + mSettings.mProvider.releaseSettings(mValidKeys); + mReleased = true; + } + + private void putDirect(String key, String value) { + mValueMap.put(key, value); + } + + public void put(String table, String key, String value) { + if (!mValidKeys.contains(key(table, key))) { + throw new IllegalArgumentException("Key " + table + " " + key + + " not acquired for this overrider"); + } + mValueMap.put(key(table, key), value); + } + + public void remove(String table, String key) { + if (!mValidKeys.contains(key(table, key))) { + throw new IllegalArgumentException("Key " + table + " " + key + + " not acquired for this overrider"); + } + mValueMap.remove(key(table, key)); + } + + public String get(String table, String key) { + if (!mValidKeys.contains(key(table, key))) { + throw new IllegalArgumentException("Key " + table + " " + key + + " not acquired for this overrider"); + } + Log.d(TAG, "Get " + table + " " + key + " " + mValueMap.get(key(table, key))); + return mValueMap.get(key(table, key)); + } + + public static class Builder { + private final TestableSettings mProvider; + private Set<String> mKeys = new ArraySet<>(); + private Map<String, String> mValues = new ArrayMap<>(); + + private Builder(TestableSettings provider) { + mProvider = provider; + } + + public Builder addSetting(String table, String key) { + mKeys.add(key(table, key)); + return this; + } + + public Builder addSetting(String table, String key, String value) { + addSetting(table, key); + mValues.put(key(table, key), value); + return this; + } + + public SettingOverrider build() throws AcquireTimeoutException { + SettingOverrider overrider = new SettingOverrider(mKeys, mProvider); + mProvider.acquireSettings(overrider, mKeys); + mValues.forEach((key, value) -> overrider.putDirect(key, value)); + return overrider; + } + } + } + + private static class TestableSettingsProvider extends MockContentProvider { + + private final Map<String, SettingOverrider> mOverrideMap = new ArrayMap<>(); + private final Map<Object, List<SettingOverrider>> mOwners = new ArrayMap<>(); + + private final ContentProviderClient mSettings; + private final ContentResolver mResolver; + + public TestableSettingsProvider(ContentProviderClient settings, ContentResolver resolver) { + mSettings = settings; + mResolver = resolver; + } + + private void releaseSettings(Set<String> keys) { + synchronized (mOverrideMap) { + for (String key : keys) { + if (DEBUG) Log.d(TAG, "Releasing " + key); + mOverrideMap.remove(key); + } + if (DEBUG) Log.d(TAG, "Notifying"); + mOverrideMap.notify(); + } + } + + private boolean checkKeysLocked(Set<String> keys, boolean shouldThrow) + throws AcquireTimeoutException { + for (String key : keys) { + if (mOverrideMap.containsKey(key)) { + if (shouldThrow) { + if (DEBUG) Log.e(TAG, "Lock obtained at", + mOverrideMap.get(key).mObtain); + throw new AcquireTimeoutException("Could not acquire " + key, + mOverrideMap.get(key).mObtain); + } + return false; + } + } + return true; + } + + private void acquireSettings(SettingOverrider overridder, Set<String> keys, + Object owner) throws AcquireTimeoutException { + synchronized (mOwners) { + List<SettingOverrider> list = mOwners.get(owner); + if (list == null) { + list = new ArrayList<>(); + mOwners.put(owner, list); + } + list.add(overridder); + } + synchronized (mOverrideMap) { + for (int i = 0; i < MAX_TRIES; i++) { + if (checkKeysLocked(keys, false)) break; + try { + if (DEBUG) Log.d(TAG, "Waiting for contention to finish"); + mOverrideMap.wait(WAIT_TIMEOUT); + } catch (InterruptedException e) { + } + } + overridder.mObtain = new Throwable(); + checkKeysLocked(keys, true); + for (String key : keys) { + if (DEBUG) Log.d(TAG, "Acquiring " + key); + mOverrideMap.put(key, overridder); + } + } + } + + public Bundle call(String method, String arg, Bundle extras) { + // Methods are "GET_system", "GET_global", "PUT_secure", etc. + final String[] commands = method.split("_", 2); + final String op = commands[0]; + final String table = commands[1]; + + synchronized (mOverrideMap) { + SettingOverrider overrider = mOverrideMap.get(key(table, arg)); + if (overrider == null) { + // Fall through to real settings. + try { + if (DEBUG) Log.d(TAG, "Falling through to real settings " + method); + // TODO: Add our own version of caching to handle this. + Bundle call = mSettings.call(method, arg, extras); + call.remove(Settings.CALL_METHOD_TRACK_GENERATION_KEY); + return call; + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + String value; + Bundle out = new Bundle(); + switch (op) { + case "GET": + value = overrider.get(table, arg); + if (value != null) { + out.putString(Settings.NameValueTable.VALUE, value); + } + break; + case "PUT": + value = extras.getString(Settings.NameValueTable.VALUE, null); + if (value != null) { + overrider.put(table, arg, value); + } else { + overrider.remove(table, arg); + } + break; + default: + throw new UnsupportedOperationException("Unknown command " + method); + } + return out; + } + } + } + + public static class AcquireTimeoutException extends Exception { + public AcquireTimeoutException(String str, Throwable cause) { + super(str, cause); + } + } + + private static String key(String table, String key) { + return table + "_" + key; + } + + /** + * Since the settings provider is cached inside android.provider.Settings, this must + * be gotten statically to ensure there is only one instance referenced. + */ + public static TestableSettings getFakeSettingsProvider(ContentProviderClient settings, + ContentResolver resolver) { + if (sInstance == null) { + sInstance = new TestableSettingsProvider(settings, resolver); + } + return new TestableSettings(sInstance); + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/UiThreadTest.java b/tests/testables/src/android/testing/UiThreadTest.java index 58369b119c49..e40e1d741f28 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/UiThreadTest.java +++ b/tests/testables/src/android/testing/UiThreadTest.java @@ -12,7 +12,7 @@ * permissions and limitations under the License. */ -package com.android.systemui; +package android.testing; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/ViewUtils.java b/tests/testables/src/android/testing/ViewUtils.java index 678b9f407bcd..5a651aacc74f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/utils/ViewUtils.java +++ b/tests/testables/src/android/testing/ViewUtils.java @@ -12,19 +12,14 @@ * permissions and limitations under the License. */ -package com.android.systemui.utils; +package android.testing; import android.content.pm.ApplicationInfo; import android.graphics.PixelFormat; -import android.os.Handler; -import android.os.Looper; -import android.util.Log; +import android.support.test.InstrumentationRegistry; import android.view.View; import android.view.WindowManager; import android.view.WindowManager.LayoutParams; -import android.support.test.InstrumentationRegistry; - -import com.android.systemui.SysuiTestCase; public class ViewUtils { diff --git a/tests/testables/tests/Android.mk b/tests/testables/tests/Android.mk new file mode 100644 index 000000000000..a123d801ed5a --- /dev/null +++ b/tests/testables/tests/Android.mk @@ -0,0 +1,39 @@ +# Copyright (C) 2017 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +LOCAL_PATH := $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_USE_AAPT2 := true +LOCAL_MODULE_TAGS := tests + +LOCAL_PACKAGE_NAME := TestablesTest + +LOCAL_SRC_FILES := $(call all-java-files-under, src) \ + $(call all-Iaidl-files-under, src) + +LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res + +LOCAL_STATIC_JAVA_LIBRARIES := \ + android-support-test \ + mockito-target-minus-junit4 \ + legacy-android-test \ + testables + +LOCAL_JAVA_LIBRARIES := android.test.runner + +LOCAL_CERTIFICATE := platform + +include $(BUILD_PACKAGE) + diff --git a/tests/testables/tests/AndroidManifest.xml b/tests/testables/tests/AndroidManifest.xml new file mode 100644 index 000000000000..f6006b0a7fa9 --- /dev/null +++ b/tests/testables/tests/AndroidManifest.xml @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2017 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.testables"> + + <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" /> + + <application> + <uses-library android:name="android.test.runner" /> + </application> + + <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner" + android:targetPackage="com.android.testables" + android:label="Tests for Testables"> + </instrumentation> +</manifest> diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/TestableLooperTest.java b/tests/testables/tests/src/android/testing/TestableLooperTest.java index 2416e1d8438f..18e5fffef992 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/utils/TestableLooperTest.java +++ b/tests/testables/tests/src/android/testing/TestableLooperTest.java @@ -12,7 +12,7 @@ * permissions and limitations under the License. */ -package com.android.systemui.utils; +package android.testing; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; @@ -27,20 +27,17 @@ import static org.mockito.Mockito.when; import android.os.Handler; import android.os.Looper; import android.os.Message; - -import com.android.systemui.SysUIRunner; -import com.android.systemui.SysuiTestCase; -import com.android.systemui.utils.TestableLooper.MessageHandler; -import com.android.systemui.utils.TestableLooper.RunWithLooper; +import android.testing.TestableLooper.MessageHandler; +import android.testing.TestableLooper.RunWithLooper; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -@RunWith(SysUIRunner.class) +@RunWith(AndroidTestingRunner.class) @RunWithLooper -public class TestableLooperTest extends SysuiTestCase { +public class TestableLooperTest { private TestableLooper mTestableLooper; diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/FakeSettingsProviderTest.java b/tests/testables/tests/src/android/testing/TestableSettingsTest.java index 63bb5e7f0e4a..1b01542a8a64 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/utils/FakeSettingsProviderTest.java +++ b/tests/testables/tests/src/android/testing/TestableSettingsTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016 The Android Open Source Project + * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of the License at @@ -12,7 +12,7 @@ * permissions and limitations under the License. */ -package com.android.systemui.utils; +package android.testing; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; @@ -25,29 +25,32 @@ import android.os.HandlerThread; import android.provider.Settings; import android.provider.Settings.Global; import android.provider.Settings.Secure; +import android.support.test.InstrumentationRegistry; import android.support.test.runner.AndroidJUnit4; +import android.testing.TestableSettings.AcquireTimeoutException; +import android.testing.TestableSettings.SettingOverrider; import android.util.Log; -import com.android.systemui.SysuiTestCase; -import com.android.systemui.utils.FakeSettingsProvider.AcquireTimeoutException; -import com.android.systemui.utils.FakeSettingsProvider.SettingOverrider; - import org.junit.After; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @RunWith(AndroidJUnit4.class) -public class FakeSettingsProviderTest extends SysuiTestCase { +public class TestableSettingsTest { public static final String NONEXISTENT_SETTING = "nonexistent_setting"; - private static final String TAG = "FakeSettingsProviderTest"; + private static final String TAG = "TestableSettingsTest"; private SettingOverrider mOverrider; private ContentResolver mContentResolver; + @Rule + public final TestableContext mContext = + new TestableContext(InstrumentationRegistry.getContext()); @Before public void setup() throws AcquireTimeoutException { - mOverrider = mContext.getSettingsProvider().acquireOverridesBuilder(this) + mOverrider = mContext.getSettingsProvider().acquireOverridesBuilder() .addSetting("secure", NONEXISTENT_SETTING) .addSetting("global", NONEXISTENT_SETTING, "initial value") .addSetting("global", Global.DEVICE_PROVISIONED) @@ -55,13 +58,6 @@ public class FakeSettingsProviderTest extends SysuiTestCase { mContentResolver = mContext.getContentResolver(); } - @After - public void teardown() { - if (mOverrider != null) { - mOverrider.release(); - } - } - @Test public void testInitialValueSecure() { String value = Secure.getString(mContentResolver, NONEXISTENT_SETTING); @@ -109,8 +105,9 @@ public class FakeSettingsProviderTest extends SysuiTestCase { @Test public void testAutoRelease() throws Exception { - super.cleanup(); - mContext.getSettingsProvider().acquireOverridesBuilder(this) + mOverrider.release(); + mOverrider = null; + mContext.getSettingsProvider().acquireOverridesBuilder() .addSetting("global", Global.DEVICE_PROVISIONED) .build(); } @@ -122,7 +119,7 @@ public class FakeSettingsProviderTest extends SysuiTestCase { String secure = "secure"; String key = "something shared"; String[] result = new String[1]; - overriders[0] = mContext.getSettingsProvider().acquireOverridesBuilder(this) + overriders[0] = mContext.getSettingsProvider().acquireOverridesBuilder() .addSetting(secure, key, "Some craziness") .build(); synchronized (lock) { @@ -137,7 +134,7 @@ public class FakeSettingsProviderTest extends SysuiTestCase { lock.notify(); } overriders[1] = mContext.getSettingsProvider() - .acquireOverridesBuilder(FakeSettingsProviderTest.this) + .acquireOverridesBuilder() .addSetting(secure, key, "default value") .build(); // Ensure that the default is the one we set, and not left over from diff --git a/tools/aapt2/integration-tests/AppOne/Android.mk b/tools/aapt2/integration-tests/AppOne/Android.mk index a6f32d4b9fdc..38bd5b5e3275 100644 --- a/tools/aapt2/integration-tests/AppOne/Android.mk +++ b/tools/aapt2/integration-tests/AppOne/Android.mk @@ -21,6 +21,7 @@ LOCAL_USE_AAPT2 := true LOCAL_PACKAGE_NAME := AaptTestAppOne LOCAL_MODULE_TAGS := tests LOCAL_SRC_FILES := $(call all-java-files-under,src) +LOCAL_ASSET_DIR := $(LOCAL_PATH)/assets $(LOCAL_PATH)/assets2 LOCAL_STATIC_ANDROID_LIBRARIES := \ AaptTestStaticLibOne \ AaptTestStaticLibTwo diff --git a/tools/aapt2/integration-tests/AppOne/assets/subdir/subsubdir/test.txt b/tools/aapt2/integration-tests/AppOne/assets/subdir/subsubdir/test.txt new file mode 100644 index 000000000000..125194943ec8 --- /dev/null +++ b/tools/aapt2/integration-tests/AppOne/assets/subdir/subsubdir/test.txt @@ -0,0 +1 @@ +subdir/subsubdir/test.txt comes from assets diff --git a/tools/aapt2/integration-tests/AppOne/assets/test.txt b/tools/aapt2/integration-tests/AppOne/assets/test.txt new file mode 100644 index 000000000000..88266de2b4d4 --- /dev/null +++ b/tools/aapt2/integration-tests/AppOne/assets/test.txt @@ -0,0 +1 @@ +test.txt came from assets diff --git a/tools/aapt2/integration-tests/AppOne/assets2/new.txt b/tools/aapt2/integration-tests/AppOne/assets2/new.txt new file mode 100644 index 000000000000..f4963a95503a --- /dev/null +++ b/tools/aapt2/integration-tests/AppOne/assets2/new.txt @@ -0,0 +1 @@ +new.txt came from assets2 diff --git a/tools/aapt2/integration-tests/AppOne/assets2/test.txt b/tools/aapt2/integration-tests/AppOne/assets2/test.txt new file mode 100644 index 000000000000..5d8b36c0d52d --- /dev/null +++ b/tools/aapt2/integration-tests/AppOne/assets2/test.txt @@ -0,0 +1 @@ +test.txt came from assets2 diff --git a/tools/aapt2/link/Link.cpp b/tools/aapt2/link/Link.cpp index c8f02171bfd3..10421115b629 100644 --- a/tools/aapt2/link/Link.cpp +++ b/tools/aapt2/link/Link.cpp @@ -77,6 +77,7 @@ struct LinkOptions { std::string manifest_path; std::vector<std::string> include_paths; std::vector<std::string> overlay_files; + std::vector<std::string> assets_dirs; bool output_to_directory = false; bool auto_add_overlay = false; @@ -1412,6 +1413,46 @@ class LinkCommand { return doc; } + bool CopyAssetsDirsToApk(IArchiveWriter* writer) { + std::map<std::string, std::unique_ptr<io::RegularFile>> merged_assets; + for (const std::string& assets_dir : options_.assets_dirs) { + Maybe<std::vector<std::string>> files = + file::FindFiles(assets_dir, context_->GetDiagnostics(), nullptr); + if (!files) { + return false; + } + + for (const std::string& file : files.value()) { + std::string full_key = "assets/" + file; + std::string full_path = assets_dir; + file::AppendPath(&full_path, file); + + auto iter = merged_assets.find(full_key); + if (iter == merged_assets.end()) { + merged_assets.emplace(std::move(full_key), + util::make_unique<io::RegularFile>(Source(std::move(full_path)))); + } else if (context_->IsVerbose()) { + context_->GetDiagnostics()->Warn(DiagMessage(iter->second->GetSource()) + << "asset file overrides '" << full_path << "'"); + } + } + } + + for (auto& entry : merged_assets) { + uint32_t compression_flags = ArchiveEntry::kCompress; + std::string extension = file::GetExtension(entry.first).to_string(); + if (options_.extensions_to_not_compress.count(extension) > 0) { + compression_flags = 0u; + } + + if (!CopyFileToArchive(entry.second.get(), entry.first, compression_flags, writer, + context_)) { + return false; + } + } + return true; + } + /** * Writes the AndroidManifest, ResourceTable, and all XML files referenced by * the ResourceTable to the IArchiveWriter. @@ -1724,11 +1765,9 @@ class LinkCommand { } // Start writing the base APK. - std::unique_ptr<IArchiveWriter> archive_writer = - MakeArchiveWriter(options_.output_path); + std::unique_ptr<IArchiveWriter> archive_writer = MakeArchiveWriter(options_.output_path); if (!archive_writer) { - context_->GetDiagnostics()->Error(DiagMessage() - << "failed to create archive"); + context_->GetDiagnostics()->Error(DiagMessage() << "failed to create archive"); return 1; } @@ -1743,16 +1782,15 @@ class LinkCommand { XmlReferenceLinker manifest_linker; if (manifest_linker.Consume(context_, manifest_xml.get())) { if (options_.generate_proguard_rules_path && - !proguard::CollectProguardRulesForManifest( - Source(options_.manifest_path), manifest_xml.get(), - &proguard_keep_set)) { + !proguard::CollectProguardRulesForManifest(Source(options_.manifest_path), + manifest_xml.get(), &proguard_keep_set)) { error = true; } if (options_.generate_main_dex_proguard_rules_path && - !proguard::CollectProguardRulesForManifest( - Source(options_.manifest_path), manifest_xml.get(), - &proguard_main_dex_keep_set, true)) { + !proguard::CollectProguardRulesForManifest(Source(options_.manifest_path), + manifest_xml.get(), + &proguard_main_dex_keep_set, true)) { error = true; } @@ -1776,13 +1814,15 @@ class LinkCommand { } if (error) { - context_->GetDiagnostics()->Error(DiagMessage() - << "failed processing manifest"); + context_->GetDiagnostics()->Error(DiagMessage() << "failed processing manifest"); return 1; } - if (!WriteApk(archive_writer.get(), &proguard_keep_set, manifest_xml.get(), - &final_table_)) { + if (!WriteApk(archive_writer.get(), &proguard_keep_set, manifest_xml.get(), &final_table_)) { + return 1; + } + + if (!CopyAssetsDirsToApk(archive_writer.get())) { return 1; } @@ -1863,12 +1903,6 @@ class LinkCommand { proguard_main_dex_keep_set)) { return 1; } - - if (context_->IsVerbose()) { - DebugPrintTableOptions debug_print_table_options; - debug_print_table_options.show_sources = true; - Debug::PrintTable(&final_table_, debug_print_table_options); - } return 0; } @@ -1916,6 +1950,9 @@ int Link(const std::vector<StringPiece>& args) { .RequiredFlag("--manifest", "Path to the Android manifest to build", &options.manifest_path) .OptionalFlagList("-I", "Adds an Android APK to link against", &options.include_paths) + .OptionalFlagList("-A", + "An assets directory to include in the APK. These are unprocessed.", + &options.assets_dirs) .OptionalFlagList("-R", "Compilation unit to link, using `overlay` semantics.\n" "The last conflicting resource given takes precedence.", diff --git a/tools/aapt2/util/Files.cpp b/tools/aapt2/util/Files.cpp index aa840e2e3c8d..d10351b48eb3 100644 --- a/tools/aapt2/util/Files.cpp +++ b/tools/aapt2/util/Files.cpp @@ -264,5 +264,57 @@ bool FileFilter::operator()(const std::string& filename, FileType type) const { return true; } +Maybe<std::vector<std::string>> FindFiles(const android::StringPiece& path, IDiagnostics* diag, + const FileFilter* filter) { + const std::string root_dir = path.to_string(); + std::unique_ptr<DIR, decltype(closedir)*> d(opendir(root_dir.data()), closedir); + if (!d) { + diag->Error(DiagMessage() << android::base::SystemErrorCodeToString(errno)); + return {}; + } + + std::vector<std::string> files; + std::vector<std::string> subdirs; + while (struct dirent* entry = readdir(d.get())) { + if (util::StartsWith(entry->d_name, ".")) { + continue; + } + + std::string file_name = entry->d_name; + std::string full_path = root_dir; + AppendPath(&full_path, file_name); + const FileType file_type = GetFileType(full_path); + + if (filter != nullptr) { + if (!(*filter)(file_name, file_type)) { + continue; + } + } + + if (file_type == file::FileType::kDirectory) { + subdirs.push_back(std::move(file_name)); + } else { + files.push_back(std::move(file_name)); + } + } + + // Now process subdirs. + for (const std::string& subdir : subdirs) { + std::string full_subdir = root_dir; + AppendPath(&full_subdir, subdir); + Maybe<std::vector<std::string>> subfiles = FindFiles(full_subdir, diag, filter); + if (!subfiles) { + return {}; + } + + for (const std::string& subfile : subfiles.value()) { + std::string new_file = subdir; + AppendPath(&new_file, subfile); + files.push_back(new_file); + } + } + return files; +} + } // namespace file } // namespace aapt diff --git a/tools/aapt2/util/Files.h b/tools/aapt2/util/Files.h index 95c492f3b3af..b3b1e484d27b 100644 --- a/tools/aapt2/util/Files.h +++ b/tools/aapt2/util/Files.h @@ -132,6 +132,11 @@ class FileFilter { std::vector<std::string> pattern_tokens_; }; +// Returns a list of files relative to the directory identified by `path`. +// An optional FileFilter filters out any files that don't pass. +Maybe<std::vector<std::string>> FindFiles(const android::StringPiece& path, IDiagnostics* diag, + const FileFilter* filter = nullptr); + } // namespace file } // namespace aapt diff --git a/tools/layoutlib/bridge/src/android/graphics/ColorFilter_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/ColorFilter_Delegate.java index bd934d028aff..cb013b60a06c 100644 --- a/tools/layoutlib/bridge/src/android/graphics/ColorFilter_Delegate.java +++ b/tools/layoutlib/bridge/src/android/graphics/ColorFilter_Delegate.java @@ -66,7 +66,7 @@ public abstract class ColorFilter_Delegate { // ---- native methods ---- @LayoutlibDelegate - /*package*/ static void destroyFilter(long native_instance) { + /*package*/ static void nSafeUnref(long native_instance) { sManager.removeJavaReferenceFor(native_instance); } diff --git a/tools/layoutlib/bridge/src/android/graphics/FontFamily_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/FontFamily_Delegate.java index ff5a5e9541df..aaff5d594920 100644 --- a/tools/layoutlib/bridge/src/android/graphics/FontFamily_Delegate.java +++ b/tools/layoutlib/bridge/src/android/graphics/FontFamily_Delegate.java @@ -16,7 +16,6 @@ package android.graphics; -import android.text.FontConfig; import com.android.ide.common.rendering.api.AssetRepository; import com.android.ide.common.rendering.api.LayoutLog; import com.android.layoutlib.bridge.Bridge; @@ -293,12 +292,16 @@ public class FontFamily_Delegate { @LayoutlibDelegate /*package*/ static boolean nAddFontWeightStyle(long builderPtr, ByteBuffer font, - int ttcIndex, List<FontConfig.Axis> listOfAxis, - int weight, boolean isItalic) { + int ttcIndex, int weight, boolean isItalic) { assert false : "The only client of this method has been overriden."; return false; } + @LayoutlibDelegate + /*package*/ static void nAddAxisValue(long builderPtr, int tag, float value) { + assert false : "The only client of this method has been overriden."; + } + static boolean addFont(long builderPtr, final String path, final int weight, final boolean isItalic) { final FontFamily_Delegate delegate = getDelegate(builderPtr); diff --git a/tools/layoutlib/bridge/src/android/graphics/Paint_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Paint_Delegate.java index aa1f00dee42e..1bb56e3be415 100644 --- a/tools/layoutlib/bridge/src/android/graphics/Paint_Delegate.java +++ b/tools/layoutlib/bridge/src/android/graphics/Paint_Delegate.java @@ -964,8 +964,9 @@ public class Paint_Delegate { } @LayoutlibDelegate - /*package*/ static int nGetTextRunCursor(Paint paint, long native_object, char[] text, - int contextStart, int contextLength, int flags, int offset, int cursorOpt) { + /*package*/ static int nGetTextRunCursor(Paint paint, long native_object, long typefacePtr, + char[] text, int contextStart, int contextLength, int flags, int offset, + int cursorOpt) { // FIXME Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, "Paint.getTextRunCursor is not supported.", null, null /*data*/); @@ -973,8 +974,8 @@ public class Paint_Delegate { } @LayoutlibDelegate - /*package*/ static int nGetTextRunCursor(Paint paint, long native_object, String text, - int contextStart, int contextEnd, int flags, int offset, int cursorOpt) { + /*package*/ static int nGetTextRunCursor(Paint paint, long native_object, long typefacePtr, + String text, int contextStart, int contextEnd, int flags, int offset, int cursorOpt) { // FIXME Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, "Paint.getTextRunCursor is not supported.", null, null /*data*/); diff --git a/tools/layoutlib/bridge/src/android/graphics/drawable/AdaptiveIconDrawable_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/drawable/AdaptiveIconDrawable_Delegate.java new file mode 100644 index 000000000000..7e9432dd71ef --- /dev/null +++ b/tools/layoutlib/bridge/src/android/graphics/drawable/AdaptiveIconDrawable_Delegate.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.graphics.drawable; + +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.PorterDuff.Mode; +import android.graphics.PorterDuffXfermode; +import android.graphics.Rect; +import android.graphics.drawable.AdaptiveIconDrawable.LayerState; + +/** + * Delegate used to provide new implementation of a select few methods of {@link + * AdaptiveIconDrawable} + * <p> + * Through the layoutlib_create tool, the original methods of AdaptiveIconDrawable have been + * replaced by calls to methods of the same name in this delegate class. + */ +@SuppressWarnings("unused") +public class AdaptiveIconDrawable_Delegate { + @LayoutlibDelegate + /*package*/ static void draw(AdaptiveIconDrawable thisDrawable, Canvas canvas) { + // This is a workaround for the broken BitmapShader in layoutlib. This new draw methods + // avoids the use of the shader. + + for (int i = 0; i < LayerState.N_CHILDREN; i++) { + if (thisDrawable.mLayerState.mChildren[i] == null) { + continue; + } + final Drawable dr = thisDrawable.mLayerState.mChildren[i].mDrawable; + if (dr != null) { + dr.draw(canvas); + } + } + + if (thisDrawable.mMaskBitmap != null) { + Rect bounds = thisDrawable.getBounds(); + Paint paint = new Paint(); + paint.setXfermode(new PorterDuffXfermode(Mode.DST_IN)); + canvas.drawBitmap(thisDrawable.mMaskBitmap, bounds.left, bounds.top, paint); + } + } +} diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgePackageManager.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgePackageManager.java index 2274b90da49e..906ebb1fe39e 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgePackageManager.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgePackageManager.java @@ -657,6 +657,10 @@ public class BridgePackageManager extends PackageManager { } @Override + public void setUpdateAvailable(String packageName, boolean updateAvailable) { + } + + @Override public void deletePackage(String packageName, IPackageDeleteObserver observer, int flags) { } diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindow.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindow.java index 468949117635..ffbe7c43ceab 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindow.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindow.java @@ -18,12 +18,12 @@ package com.android.layoutlib.bridge.android; import com.android.internal.os.IResultReceiver; -import android.content.res.Configuration; import android.graphics.Rect; import android.os.Bundle; import android.os.IBinder; import android.os.ParcelFileDescriptor; import android.os.RemoteException; +import android.util.MergedConfiguration; import android.view.DragEvent; import android.view.IWindow; @@ -50,7 +50,7 @@ public final class BridgeWindow implements IWindow { @Override public void resized(Rect rect, Rect rect2, Rect rect3, Rect rect4, Rect rect5, Rect rect6, - boolean b, Configuration configuration, Rect rect7, boolean b2, boolean b3, int i0) + boolean b, MergedConfiguration mergedConfig, Rect rect7, boolean b2, boolean b3, int i0) throws RemoteException { // pass for now. } diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java index 7582fda6a0fb..2c883940510e 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java @@ -17,12 +17,12 @@ package com.android.layoutlib.bridge.android; import android.content.ClipData; -import android.content.res.Configuration; import android.graphics.Rect; import android.graphics.Region; import android.os.Bundle; import android.os.IBinder; import android.os.RemoteException; +import android.util.MergedConfiguration; import android.view.IWindow; import android.view.IWindowId; import android.view.IWindowSession; @@ -89,25 +89,13 @@ public final class BridgeWindowSession implements IWindowSession { @Override public int relayout(IWindow iWindow, int i, LayoutParams layoutParams, int i2, int i3, int i4, int i5, Rect rect, Rect rect2, Rect rect3, Rect rect4, Rect rect5, - Rect rect6, Rect rect7, Configuration configuration, Surface surface) + Rect rect6, Rect rect7, MergedConfiguration mergedConfig, Surface surface) throws RemoteException { // pass for now. return 0; } @Override - public void repositionChild(IWindow window, int left, int top, int right, int bottom, - long deferTransactionUntilFrame, Rect outFrame) { - // pass for now. - return; - } - - @Override - public void performDeferredDestroy(IWindow window) { - // pass for now. - } - - @Override public boolean outOfMemory(IWindow window) throws RemoteException { return false; } diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/ResourceHelper.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/ResourceHelper.java index f1e7b5177e13..45337742998f 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/ResourceHelper.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/ResourceHelper.java @@ -415,9 +415,9 @@ public final class ResourceHelper { BridgeXmlBlockParser blockParser = new BridgeXmlBlockParser( parser, context, isFramework); try { - FontConfig config = FontResourcesParser.parse(blockParser, context - .getResources()); - typeface = Typeface.createFromResources(config, context.getAssets(), + FontResourcesParser.FamilyResourceEntry entry = + FontResourcesParser.parse(blockParser, context.getResources()); + typeface = Typeface.createFromResources(entry, context.getAssets(), fontName); } catch (XmlPullParserException | IOException e) { Bridge.getLog().error(null, "Failed to parse file " + fontName, diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/adaptive_icon.png b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/adaptive_icon.png Binary files differnew file mode 100644 index 000000000000..7014ddbbb7da --- /dev/null +++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/adaptive_icon.png diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/drawable/adaptive.xml b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/drawable/adaptive.xml new file mode 100644 index 000000000000..8f862c86873c --- /dev/null +++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/drawable/adaptive.xml @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="utf-8"?> + +<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> + <background android:drawable="@android:color/red" /> + <foreground android:drawable="@drawable/headset" /> +</adaptive-icon>
\ No newline at end of file diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/adaptive_icon.xml b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/adaptive_icon.xml new file mode 100644 index 000000000000..ca9fa55d5424 --- /dev/null +++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/adaptive_icon.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="utf-8"?> + +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:padding="16dp" + android:orientation="horizontal" + android:layout_width="fill_parent" + android:layout_height="fill_parent"> + <ImageView + android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:src="@drawable/adaptive" /> + +</LinearLayout> + diff --git a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/RenderTests.java b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/RenderTests.java index 7199781349a6..2b5e0f9dd5fa 100644 --- a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/RenderTests.java +++ b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/RenderTests.java @@ -396,6 +396,22 @@ public class RenderTests extends RenderTestBase { } @Test + public void testAdaptiveIcon() throws ClassNotFoundException { + // Create the layout pull parser. + LayoutPullParser parser = createLayoutPullParser("adaptive_icon.xml"); + // Create LayoutLibCallback. + LayoutLibTestCallback layoutLibCallback = + new LayoutLibTestCallback(getLogger(), mDefaultClassLoader); + layoutLibCallback.initResources(); + + SessionParams params = getSessionParams(parser, ConfigGenerator.NEXUS_5, + layoutLibCallback, "Theme.Material.NoActionBar.Fullscreen", false, + RenderingMode.V_SCROLL, 22); + + renderAndVerify(params, "adaptive_icon.png"); + } + + @Test public void testColorTypedValue() throws Exception { // Setup // Create the layout pull parser for our resources (empty.xml can not be part of the test diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java index b0aa3c2989a5..4f226cbad01f 100644 --- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java +++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java @@ -163,6 +163,7 @@ public final class CreateInfo implements ICreateInfo { "android.content.res.TypedArray#obtain", "android.graphics.BitmapFactory#finishDecode", "android.graphics.BitmapFactory#setDensityFromOptions", + "android.graphics.drawable.AdaptiveIconDrawable#draw", "android.graphics.drawable.AnimatedVectorDrawable$VectorDrawableAnimatorRT#useLastSeenTarget", "android.graphics.drawable.AnimatedVectorDrawable$VectorDrawableAnimatorRT#onDraw", "android.graphics.drawable.GradientDrawable#buildRing", @@ -332,6 +333,8 @@ public final class CreateInfo implements ICreateInfo { * needed when access from the delegate classes is needed. */ private final static String[] PROMOTED_FIELDS = new String[] { + "android.graphics.drawable.AdaptiveIconDrawable#mMaskBitmap", + "android.graphics.drawable.AdaptiveIconDrawable#mPaint", "android.graphics.drawable.VectorDrawable#mVectorState", "android.view.Choreographer#mLastFrameTimeNanos", "android.graphics.FontFamily#mBuilderPtr" diff --git a/wifi/java/android/net/wifi/IWifiManager.aidl b/wifi/java/android/net/wifi/IWifiManager.aidl index 18c124503136..af48d0a2b6ca 100644 --- a/wifi/java/android/net/wifi/IWifiManager.aidl +++ b/wifi/java/android/net/wifi/IWifiManager.aidl @@ -16,7 +16,11 @@ package android.net.wifi; + +import android.content.pm.ParceledListSlice; + import android.net.wifi.hotspot2.PasspointConfiguration; + import android.net.wifi.WifiConfiguration; import android.net.wifi.WifiInfo; import android.net.wifi.ScanSettings; @@ -51,9 +55,9 @@ interface IWifiManager */ oneway void requestActivityInfo(in ResultReceiver result); - List<WifiConfiguration> getConfiguredNetworks(); + ParceledListSlice getConfiguredNetworks(); - List<WifiConfiguration> getPrivilegedConfiguredNetworks(); + ParceledListSlice getPrivilegedConfiguredNetworks(); WifiConfiguration getMatchingWifiConfig(in ScanResult scanResult); diff --git a/wifi/java/android/net/wifi/WifiConfiguration.java b/wifi/java/android/net/wifi/WifiConfiguration.java index a1099f8045c5..04f9059fb544 100644 --- a/wifi/java/android/net/wifi/WifiConfiguration.java +++ b/wifi/java/android/net/wifi/WifiConfiguration.java @@ -281,7 +281,9 @@ public class WifiConfiguration implements Parcelable { public int apChannel = 0; /** - * Pre-shared key for use with WPA-PSK. + * Pre-shared key for use with WPA-PSK. Either an ASCII string enclosed in + * double quotation marks (e.g., {@code "abcdefghij"} for PSK passphrase or + * a string of 64 hex digits for raw PSK. * <p/> * When the value of this key is read, the actual key is * not returned, just a "*" if the key has a value, or the null @@ -305,7 +307,7 @@ public class WifiConfiguration implements Parcelable { /** * Priority determines the preference given to a network by {@code wpa_supplicant} * when choosing an access point with which to associate. - * @deprecated Priority is no longer used. + * @deprecated This field does not exist anymore. */ @Deprecated public int priority; @@ -434,6 +436,13 @@ public class WifiConfiguration implements Parcelable { public int dtimInterval = 0; /** + * Flag indicating if this configuration represents a legacy Passpoint configuration + * (Release N or older). This is used for migrating Passpoint configuration from N to O. + * This will no longer be needed after O. + * @hide + */ + public boolean isLegacyPasspointConfig = false; + /** * @hide * Uid of app creating the configuration */ @@ -1961,6 +1970,7 @@ public class WifiConfiguration implements Parcelable { mCachedConfigKey = null; //force null configKey selfAdded = source.selfAdded; validatedInternetAccess = source.validatedInternetAccess; + isLegacyPasspointConfig = source.isLegacyPasspointConfig; ephemeral = source.ephemeral; meteredHint = source.meteredHint; meteredOverride = source.meteredOverride; @@ -2037,6 +2047,7 @@ public class WifiConfiguration implements Parcelable { dest.writeInt(selfAdded ? 1 : 0); dest.writeInt(didSelfAdd ? 1 : 0); dest.writeInt(validatedInternetAccess ? 1 : 0); + dest.writeInt(isLegacyPasspointConfig ? 1 : 0); dest.writeInt(ephemeral ? 1 : 0); dest.writeInt(meteredHint ? 1 : 0); dest.writeInt(meteredOverride ? 1 : 0); @@ -2103,6 +2114,7 @@ public class WifiConfiguration implements Parcelable { config.selfAdded = in.readInt() != 0; config.didSelfAdd = in.readInt() != 0; config.validatedInternetAccess = in.readInt() != 0; + config.isLegacyPasspointConfig = in.readInt() != 0; config.ephemeral = in.readInt() != 0; config.meteredHint = in.readInt() != 0; config.meteredOverride = in.readInt() != 0; diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java index bbe96a70f823..4f2881b87616 100644 --- a/wifi/java/android/net/wifi/WifiManager.java +++ b/wifi/java/android/net/wifi/WifiManager.java @@ -21,6 +21,7 @@ import android.annotation.SdkConstant.SdkConstantType; import android.annotation.SystemApi; import android.bluetooth.BluetoothAdapter; import android.content.Context; +import android.content.pm.ParceledListSlice; import android.net.ConnectivityManager; import android.net.DhcpInfo; import android.net.Network; @@ -46,6 +47,7 @@ import com.android.server.net.NetworkPinner; import java.net.InetAddress; import java.util.List; import java.util.concurrent.CountDownLatch; +import java.util.Collections; /** * This class provides the primary API for managing all aspects of Wi-Fi @@ -811,7 +813,12 @@ public class WifiManager { */ public List<WifiConfiguration> getConfiguredNetworks() { try { - return mService.getConfiguredNetworks(); + ParceledListSlice<WifiConfiguration> parceledList = + mService.getConfiguredNetworks(); + if (parceledList == null) { + return Collections.emptyList(); + } + return parceledList.getList(); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -821,7 +828,12 @@ public class WifiManager { @SystemApi public List<WifiConfiguration> getPrivilegedConfiguredNetworks() { try { - return mService.getPrivilegedConfiguredNetworks(); + ParceledListSlice<WifiConfiguration> parceledList = + mService.getPrivilegedConfiguredNetworks(); + if (parceledList == null) { + return Collections.emptyList(); + } + return parceledList.getList(); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -971,10 +983,13 @@ public class WifiManager { } /** - * Query for a Hotspot 2.0 release 2 OSU icon file. + * Query for a Hotspot 2.0 release 2 OSU icon file. An {@link #ACTION_PASSPOINT_ICON} intent + * will be broadcasted once the request is completed. The return value of + * {@link IconInfo#getData} from the intent extra will indicate the result of the request. + * A value of {@code null} will indicate a failure. * * @param bssid The BSSID of the AP - * @param fileName File name of the icon to query + * @param fileName Name of the icon file (remote file) to query from the AP */ public void queryPasspointIcon(long bssid, String fileName) { try { diff --git a/wifi/tests/Android.mk b/wifi/tests/Android.mk index eac49d20b88d..8dc244f086cc 100644 --- a/wifi/tests/Android.mk +++ b/wifi/tests/Android.mk @@ -58,5 +58,6 @@ LOCAL_JAVA_LIBRARIES := \ android.test.runner \ LOCAL_PACKAGE_NAME := FrameworksWifiApiTests +LOCAL_COMPATIBILITY_SUITE := device-tests include $(BUILD_PACKAGE) |