Merge "Delete some SurfaceView support code."
diff --git a/api/current.txt b/api/current.txt
index 4981c43..a4893f9 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -751,6 +751,7 @@
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 @@
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
@@ -6566,7 +6568,7 @@
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();
@@ -21826,7 +21828,7 @@
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 +22380,7 @@
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 +22635,69 @@
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 +22738,7 @@
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 +22906,7 @@
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 +24717,7 @@
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 +25258,8 @@
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 +33942,7 @@
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 +36807,7 @@
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 +36862,11 @@
}
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[]);
}
}
@@ -39178,6 +39244,7 @@
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 +39287,8 @@
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 +39344,7 @@
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 +39877,6 @@
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 +39917,7 @@
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 +40955,10 @@
}
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 +40983,22 @@
}
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();
@@ -45098,7 +45167,7 @@
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 +45488,7 @@
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 +45631,19 @@
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 +46305,7 @@
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 +46316,7 @@
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 +46327,6 @@
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 +47571,7 @@
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 +47579,12 @@
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 {
@@ -47533,6 +47605,10 @@
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 fbfee83..cf4902c 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -864,6 +864,7 @@
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 @@
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
@@ -6816,7 +6818,7 @@
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();
@@ -11241,6 +11243,7 @@
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>);
@@ -23621,7 +23624,7 @@
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 +24176,7 @@
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 +24431,69 @@
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 +24534,7 @@
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 +24702,7 @@
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 +26682,15 @@
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 +26709,7 @@
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 +26717,7 @@
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 +27383,8 @@
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 +36822,7 @@
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 +39845,7 @@
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 +39900,11 @@
}
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[]);
}
}
@@ -42536,6 +42608,7 @@
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 +42651,8 @@
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 +42708,7 @@
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 +43281,8 @@
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 +43329,7 @@
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 +44215,7 @@
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 +44421,10 @@
}
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 +44449,22 @@
}
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();
@@ -48557,7 +48634,7 @@
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 +48955,7 @@
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 +49098,19 @@
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 +49772,7 @@
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 +49783,7 @@
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 +49794,6 @@
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 +51041,7 @@
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 +51049,12 @@
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 {
@@ -50995,6 +51075,10 @@
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 3be7f67..bb8b454 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -751,6 +751,7 @@
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 @@
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
@@ -6593,7 +6595,7 @@
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();
@@ -21927,7 +21929,7 @@
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 +22481,7 @@
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 +22736,69 @@
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 +22839,7 @@
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 +23007,7 @@
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 +24818,7 @@
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 +25359,8 @@
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 +34069,7 @@
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 +36951,7 @@
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 +37006,11 @@
}
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[]);
}
}
@@ -39367,6 +39433,7 @@
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 +39476,8 @@
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 +39533,7 @@
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 +40066,6 @@
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 +40106,7 @@
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 +41148,10 @@
}
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 +41176,22 @@
}
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();
@@ -45458,7 +45527,7 @@
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 +45851,7 @@
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 +45994,19 @@
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 +46672,7 @@
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 +46683,7 @@
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 +46694,6 @@
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 +47940,7 @@
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 +47948,12 @@
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 {
@@ -47902,6 +47974,10 @@
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 50ccb07..aeb8a0c 100644
--- a/cmds/idmap/Android.mk
+++ b/cmds/idmap/Android.mk
@@ -17,7 +17,7 @@
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 3ab1915..3a237ff 100644
--- a/cmds/idmap/idmap.cpp
+++ b/cmds/idmap/idmap.cpp
@@ -49,8 +49,8 @@
--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 8122395..67874a8 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 @@
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 @@
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 @@
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 @@
}
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/app/ActivityTransitionCoordinator.java b/core/java/android/app/ActivityTransitionCoordinator.java
index a512350..21a7ca7 100644
--- a/core/java/android/app/ActivityTransitionCoordinator.java
+++ b/core/java/android/app/ActivityTransitionCoordinator.java
@@ -28,6 +28,7 @@
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 @@
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/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index 97992ca..55407e6 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -1814,6 +1814,15 @@
}
@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/EnterTransitionCoordinator.java b/core/java/android/app/EnterTransitionCoordinator.java
index 445b687..ab847fd 100644
--- a/core/java/android/app/EnterTransitionCoordinator.java
+++ b/core/java/android/app/EnterTransitionCoordinator.java
@@ -132,7 +132,9 @@
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 29e10d8..df31da9 100644
--- a/core/java/android/app/ExitTransitionCoordinator.java
+++ b/core/java/android/app/ExitTransitionCoordinator.java
@@ -321,6 +321,10 @@
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/LoadedApk.java b/core/java/android/app/LoadedApk.java
index be38f42..77c4c7e 100644
--- a/core/java/android/app/LoadedApk.java
+++ b/core/java/android/app/LoadedApk.java
@@ -597,8 +597,7 @@
// 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);
diff --git a/core/java/android/app/TimePickerDialog.java b/core/java/android/app/TimePickerDialog.java
index b219f2a..8a4f8a6 100644
--- a/core/java/android/app/TimePickerDialog.java
+++ b/core/java/android/app/TimePickerDialog.java
@@ -164,6 +164,15 @@
@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/assist/AssistStructure.java b/core/java/android/app/assist/AssistStructure.java
index 1f2ed00..b1fbc8f 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 @@
// 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 @@
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 @@
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 @@
*
* @return The hint for this view
*/
- @View.AutofillHint public int getAutoFillHint() {
+ @Nullable public String[] getAutoFillHint() {
return mAutofillHint;
}
@@ -1012,9 +1013,8 @@
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 @@
}
@Override
- public void setAutofillHint(@View.AutofillHint int hint) {
+ public void setAutofillHint(@Nullable String[] hint) {
mNode.mAutofillHint = hint;
}
@@ -1683,8 +1683,8 @@
}
@Override
- public void setSanitized(boolean sanitized) {
- mNode.mSanitized = sanitized;
+ public void setDataIsSensitive(boolean sensitive) {
+ mNode.mSanitized = !sensitive;
}
@Override
@@ -1812,7 +1812,7 @@
+ ", 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 8276229..b808c2b 100644
--- a/core/java/android/app/usage/StorageStatsManager.java
+++ b/core/java/android/app/usage/StorageStatsManager.java
@@ -81,9 +81,9 @@
/**
* 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/content/ContentProviderNative.java b/core/java/android/content/ContentProviderNative.java
index d428a3a..2f87633 100644
--- a/core/java/android/content/ContentProviderNative.java
+++ b/core/java/android/content/ContentProviderNative.java
@@ -24,6 +24,7 @@
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 @@
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/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index 147b3e1..4de64c4 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -608,6 +608,12 @@
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 5d5696b..8ff2f35 100644
--- a/core/java/android/content/pm/PackageInfo.java
+++ b/core/java/android/content/pm/PackageInfo.java
@@ -271,6 +271,9 @@
*/
public String overlayTarget;
+ /** @hide */
+ public boolean isStaticOverlay;
+
public PackageInfo() {
}
@@ -323,6 +326,7 @@
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 @@
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 3a875bc..33f57e0 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -5286,6 +5286,11 @@
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 a1c325a..e15a0e2 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -603,6 +603,7 @@
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 +2098,9 @@
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 +2108,9 @@
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)) {
@@ -5580,6 +5587,7 @@
public String mRequiredAccountType;
public String mOverlayTarget;
+ public boolean mIsStaticOverlay;
public boolean mTrustedOverlay;
/**
@@ -6056,6 +6064,7 @@
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 +6180,7 @@
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 50fc344..091cc26 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 @@
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 @@
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 @@
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 @@
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 @@
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 38efa49..949d644 100644
--- a/core/java/android/content/res/ResourcesImpl.java
+++ b/core/java/android/content/res/ResourcesImpl.java
@@ -769,8 +769,13 @@
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 @@
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 0000000..fbd039d
--- /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/net/ConnectivityMetricsEvent.java b/core/java/android/net/ConnectivityMetricsEvent.java
index 6fdc739..4faff62 100644
--- a/core/java/android/net/ConnectivityMetricsEvent.java
+++ b/core/java/android/net/ConnectivityMetricsEvent.java
@@ -19,39 +19,40 @@
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 @@
}
};
- /** Implement the Parcelable interface */
@Override
public int describeContents() {
return 0;
@@ -68,13 +68,15 @@
@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 c2795a2a..ad4588f 100644
--- a/core/java/android/net/metrics/ApfProgramEvent.java
+++ b/core/java/android/net/metrics/ApfProgramEvent.java
@@ -47,23 +47,19 @@
@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 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(
- 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 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 @@
@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 @@
@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 f8d7fa9..3b0dc7e 100644
--- a/core/java/android/net/metrics/ApfStats.java
+++ b/core/java/android/net/metrics/ApfStats.java
@@ -25,25 +25,28 @@
*/
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 @@
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 @@
out.writeInt(zeroLifetimeRas);
out.writeInt(parseErrors);
out.writeInt(programUpdates);
+ out.writeInt(programUpdatesAll);
+ out.writeInt(programUpdatesAllowingMulticast);
out.writeInt(maxProgramSize);
}
@@ -83,8 +90,9 @@
.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 173e5fd..79094c0 100644
--- a/core/java/android/net/metrics/IpConnectivityLog.java
+++ b/core/java/android/net/metrics/IpConnectivityLog.java
@@ -60,21 +60,23 @@
}
/**
- * 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 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 dc170ed..29884b1 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -2042,8 +2042,8 @@
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/storage/StorageVolume.java b/core/java/android/os/storage/StorageVolume.java
index 46f2d38..1fc0b82 100644
--- a/core/java/android/os/storage/StorageVolume.java
+++ b/core/java/android/os/storage/StorageVolume.java
@@ -373,8 +373,7 @@
}
/** {@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 a0d16bc..dac8354 100644
--- a/core/java/android/provider/ContactsContract.java
+++ b/core/java/android/provider/ContactsContract.java
@@ -44,7 +44,6 @@
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 @@
* 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 @@
"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 f53b0d7..56d4ff7 100644
--- a/core/java/android/provider/DocumentsContract.java
+++ b/core/java/android/provider/DocumentsContract.java
@@ -1563,7 +1563,7 @@
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/Settings.java b/core/java/android/provider/Settings.java
index 8a2a14c..391ee83 100755
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -5605,10 +5605,10 @@
"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 @@
"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 @@
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 29e2073..709e5f9 100644
--- a/core/java/android/service/autofill/AutofillService.java
+++ b/core/java/android/service/autofill/AutofillService.java
@@ -30,6 +30,7 @@
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 @@
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 @@
@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 @@
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 @@
* <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 @@
* 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 @@
* <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 d220052..f6d40db 100644
--- a/core/java/android/service/autofill/AutofillServiceInfo.java
+++ b/core/java/android/service/autofill/AutofillServiceInfo.java
@@ -78,14 +78,12 @@
// 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 2461947..ebe02c2 100644
--- a/core/java/android/service/autofill/Dataset.java
+++ b/core/java/android/service/autofill/Dataset.java
@@ -167,6 +167,7 @@
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 00b206c..e8ad14f 100644
--- a/core/java/android/service/autofill/FillCallback.java
+++ b/core/java/android/service/autofill/FillCallback.java
@@ -37,7 +37,7 @@
/**
* 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 @@
/**
* 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 069e83c..c43019d 100644
--- a/core/java/android/service/autofill/FillResponse.java
+++ b/core/java/android/service/autofill/FillResponse.java
@@ -31,7 +31,7 @@
/**
* 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 @@
* <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 @@
* <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 @@
* <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 @@
* <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 @@
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 @@
* 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 80685d8..9f296c6 100644
--- a/core/java/android/service/autofill/IAutoFillService.aidl
+++ b/core/java/android/service/autofill/IAutoFillService.aidl
@@ -31,7 +31,7 @@
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 1bd88c7..6663f03 100644
--- a/core/java/android/service/autofill/SaveInfo.java
+++ b/core/java/android/service/autofill/SaveInfo.java
@@ -25,28 +25,86 @@
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}.
+ * <p>A {@link SaveInfo} is always associated with a {@link FillResponse}, and it contains at least
+ * two pieces of information:
*
- * <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...)}.
+ * <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>
*
- * <p>See {@link AutofillService#onSaveRequest(android.app.assist.AssistStructure, Bundle,
- * SaveCallback)} and {@link FillResponse} for more info.
+ * 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:
+ *
+ * <pre class="prettyprint">
+ * new FillResponse.Builder()
+ * .setSaveInfo(new SaveInfo.Builder(SaveInfo.SAVE_INFO_TYPE_PASSWORD, new int[] {id1, id2})
+ * .build())
+ * .build();
+ * </pre>
+ *
+ * <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:
+ *
+ * <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 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 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 @@
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 @@
}
/** @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 @@
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 @@
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,15 @@
* 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) {
+ Preconditions.checkArgument(requiredIds != null && requiredIds.length > 0,
+ "must have at least on required id: " + Arrays.toString(requiredIds));
switch (type) {
case SAVE_DATA_TYPE_PASSWORD:
case SAVE_DATA_TYPE_ADDRESS:
@@ -172,28 +226,43 @@
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 +275,14 @@
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 +297,7 @@
* @return This Builder.
*/
public @NonNull Builder setDescription(@Nullable CharSequence description) {
+ throwIfDestroyed();
mDescription = description;
return this;
}
@@ -293,7 +363,9 @@
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 +381,10 @@
@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 +394,10 @@
// 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/text/FontConfig.java b/core/java/android/text/FontConfig.java
index 1087851..04596fa 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 @@
* @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 @@
* 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 @@
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 @@
/**
* Returns the list of axes associated to this font.
*/
- public List<Axis> getAxes() {
+ public @NonNull Axis[] getAxes() {
return mAxes;
}
@@ -251,35 +240,24 @@
/**
* 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 @@
} 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 @@
* 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 @@
* 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;
+ private final @NonNull String mName;
+ private final @NonNull Font[] mFonts;
+ private final @NonNull String mLanguage;
- public Family(String name, List<Font> fonts, String language, String variant) {
+ /** @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 94c463c..353dfed 100644
--- a/core/java/android/text/StaticLayout.java
+++ b/core/java/android/text/StaticLayout.java
@@ -1031,6 +1031,7 @@
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 @@
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 af2547e..255a029 100644
--- a/core/java/android/transition/Transition.java
+++ b/core/java/android/transition/Transition.java
@@ -799,8 +799,10 @@
* 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/view/LayoutInflater.java b/core/java/android/view/LayoutInflater.java
index e3ac40c..0e06cd3 100644
--- a/core/java/android/view/LayoutInflater.java
+++ b/core/java/android/view/LayoutInflater.java
@@ -833,6 +833,7 @@
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 @@
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 @@
}
}
+ 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/View.java b/core/java/android/view/View.java
index 029caf9..80f6c32 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -978,142 +978,123 @@
*/
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 @@
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 @@
* 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>
@@ -7464,12 +7476,12 @@
/**
* 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 +9111,15 @@
* 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 580888c..ed42385 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -164,6 +164,17 @@
static final ArrayList<ComponentCallbacks> sConfigCallbacks = new ArrayList();
/**
+ * 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
* the list or when accessing the list from a non-main thread.
*/
@@ -451,6 +462,13 @@
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();
}
@@ -2180,7 +2198,7 @@
}
}
- if (mFirst) {
+ if (mFirst && sAlwaysAssignFocus) {
// handle first focus request
if (DEBUG_INPUT_RESIZE) Log.v(mTag, "First: mView.hasFocus()="
+ mView.hasFocus());
@@ -3290,7 +3308,9 @@
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
@@ -4463,9 +4483,7 @@
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 +4493,10 @@
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.
diff --git a/core/java/android/view/ViewStructure.java b/core/java/android/view/ViewStructure.java
index bccaca2..38c7738 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 @@
* 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 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 a541a4c..6dbc09c 100644
--- a/core/java/android/view/WindowManagerInternal.java
+++ b/core/java/android/view/WindowManagerInternal.java
@@ -168,6 +168,14 @@
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 5dd17f1..4774d8f 100644
--- a/core/java/android/view/autofill/AutoFillValue.java
+++ b/core/java/android/view/autofill/AutoFillValue.java
@@ -89,11 +89,6 @@
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 c4f90dc..f036b9c 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -71,10 +71,17 @@
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 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;
private final Rect mTempRect = new Rect();
@@ -121,6 +128,66 @@
}
/**
+ * 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.
@@ -139,7 +206,7 @@
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);
@@ -181,7 +248,7 @@
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 +291,16 @@
/**
* 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);
}
@@ -305,15 +372,16 @@
}
private void startSession(AutofillId id, IBinder windowToken, Rect bounds,
- AutofillValue value) {
+ 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);
+ mCallback != null, flags);
final AutofillClient client = getClient();
if (client != null) {
client.resetableStateAvailable();
diff --git a/core/java/android/view/autofill/AutofillValue.java b/core/java/android/view/autofill/AutofillValue.java
index 0c7620e..e2dd7fe 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 @@
* {@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
*/
- public CharSequence getTextValue() {
- return mText;
+ @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 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 @@
@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 @@
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;
- } else {
- if (!mText.equals(other.mText)) return false;
- }
- 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;
+ if (mType != other.mType) return false;
+
+ if (isText()) {
+ return mValue.toString().equals(other.mValue.toString());
+ } else {
+ return Objects.equals(mValue, other.mValue);
+ }
}
@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 @@
@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 @@
* <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 @@
* <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 @@
* <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 @@
* <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 86a4965..85b05e5 100644
--- a/core/java/android/view/autofill/IAutoFillManager.aidl
+++ b/core/java/android/view/autofill/IAutoFillManager.aidl
@@ -32,7 +32,7 @@
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);
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 f032414..35c9a29 100644
--- a/core/java/android/view/textclassifier/TextClassificationManager.java
+++ b/core/java/android/view/textclassifier/TextClassificationManager.java
@@ -63,7 +63,7 @@
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 @@
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 c95a1fb..06ac869 100644
--- a/core/java/android/view/textclassifier/TextClassifierImpl.java
+++ b/core/java/android/view/textclassifier/TextClassifierImpl.java
@@ -121,8 +121,8 @@
.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 @@
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 @@
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 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 053574f..020e80a 100644
--- a/core/java/android/widget/AbsSpinner.java
+++ b/core/java/android/widget/AbsSpinner.java
@@ -23,6 +23,7 @@
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 @@
* @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;
@@ -514,8 +517,11 @@
public void autofill(AutofillValue value) {
if (!isEnabled()) return;
- 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);
+ }
}
@Override
diff --git a/core/java/android/widget/CompoundButton.java b/core/java/android/widget/CompoundButton.java
index d246405..899a824 100644
--- a/core/java/android/widget/CompoundButton.java
+++ b/core/java/android/widget/CompoundButton.java
@@ -28,6 +28,7 @@
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 @@
* </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,18 @@
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;
- setChecked(value.getToggleValue());
+ if (value.isToggle()) {
+ setChecked(value.getToggleValue());
+ } else {
+ Log.w(LOG_TAG, value + " could not be autofilled into " + this);
+ }
}
@Override
diff --git a/core/java/android/widget/DatePicker.java b/core/java/android/widget/DatePicker.java
index 31a88d4..f63573f 100644
--- a/core/java/android/widget/DatePicker.java
+++ b/core/java/android/widget/DatePicker.java
@@ -29,6 +29,7 @@
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 @@
*/
@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.
@@ -775,7 +778,11 @@
public void autofill(AutofillValue value) {
if (!isEnabled()) return;
- mDelegate.updateDate(value.getDateValue());
+ if (value.isDate()) {
+ mDelegate.updateDate(value.getDateValue());
+ } else {
+ Log.w(LOG_TAG, value + " could not be autofilled into " + this);
+ }
}
@Override
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index ade03e1..faa2310 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -157,6 +157,7 @@
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 @@
.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 @@
.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 dc9976d..5e8279a 100644
--- a/core/java/android/widget/RadioGroup.java
+++ b/core/java/android/widget/RadioGroup.java
@@ -55,6 +55,7 @@
*
*/
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,14 +422,21 @@
@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;
- final int index = value.getListValue();
+ int index;
+ if (value.isList()) {
+ index = value.getListValue();
+ } else {
+ Log.w(LOG_TAG, value + " could not be autofilled into " + this);
+ return;
+ }
+
final View child = getChildAt(index);
if (child == null) {
Log.w(VIEW_LOG_TAG, "RadioGroup.autoFill(): no child with index " + index);
diff --git a/core/java/android/widget/TextClock.java b/core/java/android/widget/TextClock.java
index a6a9db4..59881b5 100644
--- a/core/java/android/widget/TextClock.java
+++ b/core/java/android/widget/TextClock.java
@@ -132,7 +132,7 @@
private CharSequence mDescFormat;
- private boolean mRegistered;
+ private boolean mAttached;
private Calendar mTime;
private String mTimeZone;
@@ -252,7 +252,7 @@
}
createTime(mTimeZone);
- // Wait until registering for events to handle the ticker
+ // Wait until onAttachedToWindow() to handle the ticker
chooseFormat(false);
}
@@ -503,9 +503,12 @@
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 @@
}
@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 @@
}
private void registerObserver() {
- if (mRegistered) {
+ if (mAttached) {
if (mFormatChangeObserver == null) {
mFormatChangeObserver = new FormatChangeObserver(getHandler());
}
@@ -587,9 +613,11 @@
}
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 d591316f..c5c317d 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -9900,7 +9900,7 @@
final boolean isPassword = hasPasswordTransformationMethod()
|| isPasswordInputType(getInputType());
if (forAutofill) {
- structure.setSanitized(mTextFromResource);
+ structure.setDataIsSensitive(!mTextFromResource);
}
if (!isPassword || forAutofill) {
@@ -10012,12 +10012,29 @@
// TODO(b/33197203): add unit/CTS tests for autofill methods
+ 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);
+ }
+ }
+
@Override
public void autofill(AutofillValue value) {
- final CharSequence text = value.getTextValue();
-
- if (text != null && isTextEditable()) {
- setText(text, mBufferType, true, 0);
+ if (value.isText()) {
+ if (isTextEditable()) {
+ setText(value.getTextValue(), mBufferType, true, 0);
+ }
+ } else {
+ Log.w(LOG_TAG, value + " could not be autofilled into " + this);
}
}
@@ -10479,6 +10496,7 @@
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 +10561,11 @@
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 9825f1e..cfa78b5 100644
--- a/core/java/android/widget/TimePicker.java
+++ b/core/java/android/widget/TimePicker.java
@@ -27,6 +27,7 @@
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 @@
*/
@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.
@@ -530,7 +533,11 @@
public void autofill(AutofillValue value) {
if (!isEnabled()) return;
- mDelegate.setDate(value.getDateValue());
+ if (value.isDate()) {
+ mDelegate.setDate(value.getDateValue());
+ } else {
+ Log.w(LOG_TAG, value + " could not be autofilled into " + this);
+ }
}
@Override
diff --git a/core/java/com/android/internal/content/FileSystemProvider.java b/core/java/com/android/internal/content/FileSystemProvider.java
index 3ac5a72..83cc9f0 100644
--- a/core/java/com/android/internal/content/FileSystemProvider.java
+++ b/core/java/com/android/internal/content/FileSystemProvider.java
@@ -18,6 +18,7 @@
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 @@
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 @@
}
@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 @@
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/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java
index a7e900a..76d8af1 100644
--- a/core/java/com/android/internal/os/ZygoteInit.java
+++ b/core/java/com/android/internal/os/ZygoteInit.java
@@ -132,9 +132,6 @@
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,
@@ -585,7 +582,6 @@
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 ae31873..95d714f 100644
--- a/core/java/com/android/internal/policy/PipSnapAlgorithm.java
+++ b/core/java/com/android/internal/policy/PipSnapAlgorithm.java
@@ -321,7 +321,7 @@
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/widget/FloatingToolbar.java b/core/java/com/android/internal/widget/FloatingToolbar.java
index 79b0cd1..818cc2c 100644
--- a/core/java/com/android/internal/widget/FloatingToolbar.java
+++ b/core/java/com/android/internal/widget/FloatingToolbar.java
@@ -1163,21 +1163,21 @@
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 @@
menuItemButton.getPaddingEnd(),
menuItemButton.getPaddingBottom());
- // Include a border.
- mMainPanel.addView(border);
+ // Include a divider.
+ mMainPanel.addView(divider);
}
setButtonTagAndClickListener(menuItemButton, menuItem);
@@ -1670,21 +1670,28 @@
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/jni/android/graphics/Bitmap.cpp b/core/jni/android/graphics/Bitmap.cpp
index f852194..a8d6830 100755
--- a/core/jni/android/graphics/Bitmap.cpp
+++ b/core/jni/android/graphics/Bitmap.cpp
@@ -453,7 +453,8 @@
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 @@
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 @@
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 @@
}
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 c1bb69d..e64a574 100644
--- a/core/jni/android/graphics/BitmapFactory.cpp
+++ b/core/jni/android/graphics/BitmapFactory.cpp
@@ -417,11 +417,7 @@
// 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/FontFamily.cpp b/core/jni/android/graphics/FontFamily.cpp
index 49024b6..fb7c5c4 100644
--- a/core/jni/android/graphics/FontFamily.cpp
+++ b/core/jni/android/graphics/FontFamily.cpp
@@ -44,6 +44,7 @@
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_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 @@
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 @@
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 @@
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 @@
{ "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 7c56c7b..e66587a 100644
--- a/core/jni/android/graphics/Graphics.cpp
+++ b/core/jni/android/graphics/Graphics.cpp
@@ -461,12 +461,7 @@
}
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 1846237..fa25a8f 100644
--- a/core/jni/android/graphics/Paint.cpp
+++ b/core/jni/android/graphics/Paint.cpp
@@ -239,30 +239,38 @@
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 @@
{"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/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 248fd15..4432e3c 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 bfe666e..67050f7 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 6015ed5..f9fd57c 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/ids.xml b/core/res/res/values/ids.xml
index f8a071d..cd3624d 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 483d05b..ba14843 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 bb3f1c3..2897c62 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 d1d406d..1ed069b 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>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 0c318cf..07cecbc 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2425,6 +2425,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 +2841,13 @@
<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_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 d100c63..a661b07 100644
--- a/core/res/res/values/themes.xml
+++ b/core/res/res/values/themes.xml
@@ -398,6 +398,7 @@
<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 @@
<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 29c6b79..1ae922a 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/src/android/content/res/FontResourcesParserTest.java b/core/tests/coretests/src/android/content/res/FontResourcesParserTest.java
index 23d3aa5..82f4690 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 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 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 0000000..0be89d5
--- /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 76598a0..38a1a46 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/graphics/java/android/graphics/Bitmap.java b/graphics/java/android/graphics/Bitmap.java
index 3d5ba79..ed587bb 100644
--- a/graphics/java/android/graphics/Bitmap.java
+++ b/graphics/java/android/graphics/Bitmap.java
@@ -791,12 +791,12 @@
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 @@
} else {
final boolean transformed = !m.rectStaysRect();
- RectF deviceR = new RectF();
m.mapRect(deviceR, dstR);
neww = Math.round(deviceR.width());
@@ -841,9 +840,6 @@
}
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 @@
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 2a2e14b..7289429 100644
--- a/graphics/java/android/graphics/Canvas.java
+++ b/graphics/java/android/graphics/Canvas.java
@@ -157,12 +157,10 @@
/**
* 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/ColorSpace.java b/graphics/java/android/graphics/ColorSpace.java
index 908ec50..929ac22 100644
--- a/graphics/java/android/graphics/ColorSpace.java
+++ b/graphics/java/android/graphics/ColorSpace.java
@@ -490,16 +490,16 @@
* <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 @@
"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 317f232..16fc2b1 100644
--- a/graphics/java/android/graphics/FontFamily.java
+++ b/graphics/java/android/graphics/FontFamily.java
@@ -25,7 +25,6 @@
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 @@
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 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 @@
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 b757842..1b6969f 100644
--- a/graphics/java/android/graphics/FontListParser.java
+++ b/graphics/java/android/graphics/FontListParser.java
@@ -116,20 +116,23 @@
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 @@
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 @@
}
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/Paint.java b/graphics/java/android/graphics/Paint.java
index b1d51ec..7ca4615 100644
--- a/graphics/java/android/graphics/Paint.java
+++ b/graphics/java/android/graphics/Paint.java
@@ -2294,7 +2294,7 @@
throw new IndexOutOfBoundsException();
}
- return nGetTextRunCursor(mNativePaint, text,
+ return nGetTextRunCursor(mNativePaint, mNativeTypeface, text,
contextStart, contextLength, dir, offset, cursorOpt);
}
@@ -2380,7 +2380,7 @@
throw new IndexOutOfBoundsException();
}
- return nGetTextRunCursor(mNativePaint, text,
+ return nGetTextRunCursor(mNativePaint, mNativeTypeface, text,
contextStart, contextEnd, dir, offset, cursorOpt);
}
@@ -2686,9 +2686,9 @@
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/Typeface.java b/graphics/java/android/graphics/Typeface.java
index 6de19cb..95577ca 100644
--- a/graphics/java/android/graphics/Typeface.java
+++ b/graphics/java/android/graphics/Typeface.java
@@ -16,6 +16,11 @@
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;
@@ -161,46 +166,35 @@
* 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;
}
}
@@ -677,8 +671,8 @@
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 +681,9 @@
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/libs/common_time/Android.mk b/libs/common_time/Android.mk
index 1fec504..636f057 100644
--- a/libs/common_time/Android.mk
+++ b/libs/common_time/Android.mk
@@ -15,7 +15,8 @@
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 0000000..6730855
--- /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 0000000..bf6ab8e
--- /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 278a75e..8066a39 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 ee326e1..aed52f1 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 b786fdc..5e4e5f5 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/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp
index 363aa83..812e4d8 100644
--- a/libs/hwui/SkiaCanvas.cpp
+++ b/libs/hwui/SkiaCanvas.cpp
@@ -73,9 +73,39 @@
// 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/hwui/MinikinUtils.cpp b/libs/hwui/hwui/MinikinUtils.cpp
index d1871ff..415eef7 100644
--- a/libs/hwui/hwui/MinikinUtils.cpp
+++ b/libs/hwui/hwui/MinikinUtils.cpp
@@ -58,8 +58,9 @@
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 39184f1..0906ba5 100644
--- a/media/java/android/media/AudioRecord.java
+++ b/media/java/android/media/AudioRecord.java
@@ -677,7 +677,7 @@
((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 @@
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 @@
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 d264127..13a22b4 100644
--- a/media/java/android/media/MediaCodec.java
+++ b/media/java/android/media/MediaCodec.java
@@ -23,6 +23,7 @@
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 @@
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 b9e409d..2ed6668 100644
--- a/media/java/android/media/MediaExtractor.java
+++ b/media/java/android/media/MediaExtractor.java
@@ -25,6 +25,7 @@
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 @@
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 0000000..5ecbee2
--- /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 b85c911..1ee05b8 100644
--- a/media/java/android/media/MediaPlayer.java
+++ b/media/java/android/media/MediaPlayer.java
@@ -49,6 +49,7 @@
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 native int getVideoHeight();
/**
- * Returns Analytics/Metrics data about the current video in this player.
+ * Return Metrics data about the current player.
*
- * @return the a map of attributes and values available for this video
- * player or null if no metrics are available.
+ * @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}.
*
- * <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
+ * 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 075a84f..cdc1d60 100644
--- a/media/java/android/media/MediaRecorder.java
+++ b/media/java/android/media/MediaRecorder.java
@@ -20,6 +20,7 @@
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 @@
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>
+ * 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;
+ }
- public native Bundle getMetrics();
+ 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 6e15d8d..62fd395 100644
--- a/media/java/android/media/tv/TvContract.java
+++ b/media/java/android/media/tv/TvContract.java
@@ -2679,6 +2679,9 @@
/**
* 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 51fa036..957c582 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 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 @@
private int mAudioType;
private String mAudioAddress;
private int mHdmiPortId;
+ @CableConnectionStatus
+ private int mCableConnectionStatus;
private TvInputHardwareInfo() {
}
@@ -96,6 +124,19 @@
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 @@
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 @@
if (mType == TV_INPUT_TYPE_HDMI) {
dest.writeInt(mHdmiPortId);
}
+ dest.writeInt(mCableConnectionStatus);
}
public void readFromParcel(Parcel source) {
@@ -135,6 +178,7 @@
if (mType == TV_INPUT_TYPE_HDMI) {
mHdmiPortId = source.readInt();
}
+ mCableConnectionStatus = source.readInt();
}
public static final class Builder {
@@ -143,6 +187,7 @@
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 @@
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 @@
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 35988d4..a292b8e 100644
--- a/media/java/android/media/tv/TvInputInfo.java
+++ b/media/java/android/media/tv/TvInputInfo.java
@@ -138,7 +138,6 @@
// 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 @@
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 @@
mIconStandby = iconStandby;
mIconDisconnected = iconDisconnected;
mSetupActivity = setupActivity;
- mSettingsActivity = settingsActivity;
mCanRecord = canRecord;
mTunerCount = tunerCount;
mHdmiDeviceInfo = hdmiDeviceInfo;
@@ -340,14 +337,12 @@
/**
* 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 @@
&& 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 @@
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 @@
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 @@
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 @@
}
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 @@
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 1eae8db..09b2050 100644
--- a/media/java/android/media/tv/TvInputManager.java
+++ b/media/java/android/media/tv/TvInputManager.java
@@ -224,9 +224,8 @@
* {@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 @@
* 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 293e5dd..a8dd313 100644
--- a/media/jni/android_media_MediaCodec.cpp
+++ b/media/jni/android_media_MediaCodec.cpp
@@ -624,7 +624,7 @@
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_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 @@
}
// 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 @@
{ "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 a8c76c5..c9a1700 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 @@
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 3c33493..c2cfed9 100644
--- a/media/jni/android_media_MediaExtractor.cpp
+++ b/media/jni/android_media_MediaExtractor.cpp
@@ -811,9 +811,9 @@
}
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 @@
{ "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 27724a1..1b52cf5 100644
--- a/media/jni/android_media_MediaPlayer.cpp
+++ b/media/jni/android_media_MediaPlayer.cpp
@@ -708,7 +708,7 @@
}
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 @@
{"_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 77544eb..7a63e00 100644
--- a/media/jni/android_media_MediaRecorder.cpp
+++ b/media/jni/android_media_MediaRecorder.cpp
@@ -628,9 +628,9 @@
}
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 @@
{"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 803ed29..9b57958 100644
--- a/media/mca/filterfw/jni/jni_util.h
+++ b/media/mca/filterfw/jni/jni_util.h
@@ -198,7 +198,8 @@
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 2e900fe..feaefcb 100644
--- a/media/mca/filterfw/native/Android.mk
+++ b/media/mca/filterfw/native/Android.mk
@@ -41,7 +41,11 @@
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 6bb3b7f..2818f72 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 0445301..6af91af 100644
--- a/media/mca/filterfw/native/core/gl_env.h
+++ b/media/mca/filterfw/native/core/gl_env.h
@@ -256,7 +256,8 @@
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 2da557d..fd52165 100644
--- a/media/mca/filterfw/native/core/native_frame.h
+++ b/media/mca/filterfw/native/core/native_frame.h
@@ -76,7 +76,8 @@
// 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 ce704af..e39afc9 100644
--- a/media/mca/filterfw/native/core/native_program.h
+++ b/media/mca/filterfw/native/core/native_program.h
@@ -75,7 +75,8 @@
// 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 2e642ec..8df194c 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
new file mode 100644
index 0000000..08294ce
--- /dev/null
+++ b/packages/CarrierDefaultApp/assets/quantum_ic_warning_amber_96.png
Binary files differ
diff --git a/packages/CarrierDefaultApp/res/drawable/ic_sim_card.xml b/packages/CarrierDefaultApp/res/drawable/ic_sim_card.xml
index dc54fe2..75aa405 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 0000000..528576b
--- /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 a3c5049..1ea8c35 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 f904600..f9342b7 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 3d26915..939c1aa 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 28251cb..0000000
--- 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 0000000..a5820f2
--- /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 d9bd2fc..73ff3a9 100644
--- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CarrierActionUtils.java
+++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CarrierActionUtils.java
@@ -112,8 +112,10 @@
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 8a18d72..0000000
--- 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/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index f77d466..c64574f 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 fa5ba73..ee7885d 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 @@
mCategoryByKeyMap.put(category.key, category);
}
backwardCompatCleanupForCategory(mTileByComponentCache, mCategoryByKeyMap);
- normalizePriority(context, mCategoryByKeyMap);
+ sortCategories(context, mCategoryByKeyMap);
filterDuplicateTiles(mCategoryByKeyMap);
}
}
@@ -188,17 +188,17 @@
}
/**
- * 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 @@
}
/**
- * 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 6fe581e..48f3e2a 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.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 @@
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 @@
}
}
update(mConfig, mInfo, mNetworkInfo);
- mRssi = getRssi();
- mSeen = getSeen();
+ updateRssi();
+ updateSeen();
mId = sLastId.incrementAndGet();
}
@@ -369,27 +375,57 @@
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 @@
}
}
- return seen;
+ mSeen = seen;
}
public NetworkInfo getNetworkInfo() {
@@ -822,13 +858,12 @@
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 @@
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 @@
}
}
+ @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 @@
}
/** 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 50972c7..c9fa017 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 @@
}
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 8421c2c..55c886e 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 @@
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 @@
}
}
if (!apFound) {
- accessPoint.setRssi(Integer.MAX_VALUE);
+ accessPoint.setUnreachable();
}
accessPoints.add(accessPoint);
apMap.put(accessPoint.getSsidStr(), accessPoint);
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 ec0190c..e8a58c1 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 @@
@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 @@
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 434241d..8d61338 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 @@
}
@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 @@
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 @@
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 @@
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 6c06d05..9374d52 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/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 428b7b8..4b932a3 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/layout/pip_menu_activity.xml b/packages/SystemUI/res/layout/pip_menu_activity.xml
index 5e49d05..c6837fa 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/quick_settings_footer_dialog.xml b/packages/SystemUI/res/layout/quick_settings_footer_dialog.xml
index 2ba04fd..397fbf1 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 ffaa7ba..72bdbf1 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 bb7e19d..4dfaf45 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.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.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 @@
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 @@
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/pip/phone/PipMenuActivity.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java
index 86e2c49..2f9c3fc 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_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.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 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 @@
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 @@
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 @@
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,10 @@
private void showMenu(Rect stackBounds, Rect movementBounds) {
if (!mMenuVisible) {
+ updateActionViews(stackBounds);
if (mMenuContainerAnimator != null) {
mMenuContainerAnimator.cancel();
}
-
notifyMenuVisibility(true);
updateExpandButtonFromBounds(stackBounds, movementBounds);
mMenuContainerAnimator = ObjectAnimator.ofFloat(mMenuContainer, View.ALPHA,
@@ -238,6 +265,7 @@
repostDelayedFinish(INITIAL_DISMISS_DELAY);
}
});
+ mMenuContainerAnimator.addUpdateListener(mMenuBgUpdateListener);
mMenuContainerAnimator.start();
} else {
repostDelayedFinish(POST_INTERACTION_DISMISS_DELAY);
@@ -269,20 +297,24 @@
}
}
});
+ 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());
}
- showMenu(stackBounds, movementBounds);
+ 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);
+ }
}
private void updateExpandButtonFromBounds(Rect stackBounds, Rect movementBounds) {
@@ -365,6 +397,19 @@
}
}
+ private void updateDismissFraction(float fraction) {
+ 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;
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 badf64b..7dc455b 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.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 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;
@@ -101,6 +102,7 @@
private ParceledListSlice mMediaActions;
private boolean mMenuVisible;
+ private boolean mStartActivityRequested;
private Messenger mToActivityMessenger;
private Messenger mMessenger = new Messenger(new Handler() {
@Override
@@ -135,6 +137,7 @@
}
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 +182,25 @@
}
/**
+ * 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 +213,8 @@
} 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 +274,39 @@
}
/**
+ * 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 49d89a2..c4cf28c 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 @@
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 @@
/**
* 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 @@
/**
* 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 @@
mFlingAnimationUtils.apply(mBoundsAnimator, 0,
distanceBetweenRectOffsets(mBounds, toBounds),
velocity);
+ if (listener != null) {
+ mBoundsAnimator.addUpdateListener(listener);
+ }
mBoundsAnimator.start();
}
return toBounds;
@@ -266,12 +274,15 @@
/**
* 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 @@
/**
* 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 @@
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 80231a9..c52fc3e 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 @@
// 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 @@
}
}
};
+ private ValueAnimator.AnimatorUpdateListener mUpdateScrimListener =
+ new AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ updateDismissFraction();
+ }
+ };
// Behaviour states
private boolean mIsMenuVisible;
@@ -124,7 +133,7 @@
@Override
public void onPipMinimize() {
setMinimizedStateInternal(true);
- mMotionHelper.animateToClosestMinimizedState(mMovementBounds);
+ mMotionHelper.animateToClosestMinimizedState(mMovementBounds, null /* updateListener */);
}
@Override
@@ -331,6 +340,22 @@
}
/**
+ * 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 @@
if (ENABLE_DISMISS_DRAG_TO_TARGET) {
mDismissViewController.updateDismissTarget(mTmpBounds);
}
+ if (ENABLE_DISMISS_DRAG_TO_EDGE) {
+ updateDismissFraction();
+ }
return true;
}
return false;
@@ -502,7 +530,8 @@
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 @@
// minimize offset adjusted
mMenuController.hideMenu();
} else {
- mMotionHelper.animateToClosestMinimizedState(mMovementBounds);
+ mMotionHelper.animateToClosestMinimizedState(mMovementBounds,
+ mUpdateScrimListener);
}
return true;
}
@@ -536,13 +566,14 @@
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 79f78c9..07ac52d 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 @@
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 @@
@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 @@
.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 9ad862d..298eaf1 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";
+ String NOTIFICATION_CHANNEL_ID = "ALR";
- private static PluginManager sInstance;
+ <T extends Plugin> T getOneShotPlugin(Class<T> cls);
+ <T extends Plugin> T getOneShotPlugin(String action, Class<?> cls);
- 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;
+ <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);
- public PluginManager(Context context) {
- this(context, new PluginInstanceManagerFactory(),
- Build.IS_DEBUGGABLE, Thread.getDefaultUncaughtExceptionHandler());
- }
+ void removePluginListener(PluginListener<?> listener);
- @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);
+ <T> boolean dependsOn(Plugin p, Class<T> cls);
- 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);
- }
- }
- }
- }
-
- 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 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 @@
}
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 0000000..1fb6c87
--- /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/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index dceeb74..0924089 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.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 @@
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 @@
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 @@
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/phone/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java
index 6cd3eae..aa0fcbd 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 @@
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 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 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 8a3c4e3..2b52b48 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.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.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 @@
private int mNavigationIconHints = 0;
private int mNavigationBarMode;
private AccessibilityManager mAccessibilityManager;
+ private MagnificationContentObserver mMagnificationObserver;
private int mDisabledFlags1;
private StatusBar mStatusBar;
@@ -135,6 +138,12 @@
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 @@
mCommandQueue.removeCallbacks(this);
mAccessibilityManager.removeAccessibilityServicesStateChangeListener(
this::updateAccessibilityServicesState);
+ getContext().getContentResolver().unregisterContentObserver(mMagnificationObserver);
try {
WindowManagerGlobal.getWindowManagerService()
.removeRotationWatcher(mRotationWatcher);
@@ -387,6 +397,7 @@
ButtonDispatcher accessibilityButton = mNavigationBarView.getAccessibilityButton();
accessibilityButton.setOnClickListener(this::onAccessibilityClick);
accessibilityButton.setOnLongClickListener(this::onAccessibilityLongClick);
+ updateAccessibilityServicesState();
}
private boolean onHomeTouch(View v, MotionEvent event) {
@@ -550,10 +561,18 @@
}
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 @@
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 23d3816..cefe972 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 @@
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/tuner/TunerService.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java
index 7c4f2ee..369ce69 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java
@@ -1,226 +1,94 @@
/*
- * 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";
+ public abstract void clearAll();
+ public abstract void destroy();
- private static final int CURRENT_TUNER_VERSION = 1;
+ public abstract String getValue(String setting);
+ public abstract int getValue(String setting, int def);
+ public abstract String getValue(String setting, String def);
- 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;
+ public abstract void setValue(String setting, String value);
+ public abstract void setValue(String setting, int value);
- private ContentResolver mContentResolver;
- private int mCurrentUser;
- private CurrentUserTracker mUserTracker;
+ public abstract void addTunable(Tunable tunable, String... keys);
+ public abstract void removeTunable(Tunable tunable);
- public TunerService(Context context) {
- mContext = context;
- mContentResolver = mContext.getContentResolver();
+ public interface Tunable {
+ void onTuningChanged(String key, String newValue);
+ }
- 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);
+ private static Context userContext(Context context) {
+ try {
+ return context.createPackageContextAsUser(context.getPackageName(), 0,
+ new UserHandle(ActivityManager.getCurrentUser()));
+ } catch (NameNotFoundException e) {
+ return context;
+ }
+ }
+
+ 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;
+ }
+
+ 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();
}
}
-
- 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);
-
- iconBlacklist.add("rotate");
- iconBlacklist.add("headset");
-
- Settings.Secure.putStringForUser(mContentResolver,
- StatusBarIconController.ICON_BLACKLIST,
- TextUtils.join(",", iconBlacklist), mCurrentUser);
- }
- }
- setValue(TUNER_VERSION, newVersion);
- }
-
- public String getValue(String setting) {
- return Settings.Secure.getStringForUser(mContentResolver, setting, mCurrentUser);
- }
-
- public void setValue(String setting, String value) {
- Settings.Secure.putStringForUser(mContentResolver, setting, value, mCurrentUser);
- }
-
- 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);
- }
- }
-
- 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);
- }
-
- 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);
- }
- }
-
- 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);
- }
- }
-
- 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 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) {
@@ -247,59 +115,4 @@
});
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 0000000..8e584bc
--- /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/tests/Android.mk b/packages/SystemUI/tests/Android.mk
index 760d875..576299f 100644
--- a/packages/SystemUI/tests/Android.mk
+++ b/packages/SystemUI/tests/Android.mk
@@ -49,7 +49,8 @@
mockito-updated-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 27955ec..c297ae8 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 @@
@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 @@
@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 0000000..15cebc7
--- /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 c0e7e80..aadae0f 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 android.testing.LeakCheck;
-import com.android.systemui.Dependency.DependencyKey;
-import com.android.systemui.utils.TestableContext;
-import com.android.systemui.utils.leaks.Tracker;
-
-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 @@
}
}
- // 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 0000000..b94a2fb
--- /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 0000000..53a7994
--- /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 5477afa8..048936b 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 @@
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 ba39671..cdbde5e 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.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 53053fa..8484bed 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.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 658966c..4f0815d 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 @@
private PackageManager mMockPm;
private PluginListener mMockListener;
private PluginInstanceManager mPluginInstanceManager;
- private PluginManager mMockManager;
+ private PluginManagerImpl mMockManager;
private VersionInfo mMockVersionInfo;
@Before
@@ -82,7 +82,7 @@
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 053e5cf2..a3d5d5f 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.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 @@
private PluginInstanceManagerFactory mMockFactory;
private PluginInstanceManager mMockPluginInstance;
- private PluginManager mPluginManager;
+ private PluginManagerImpl mPluginManager;
private PluginListener mMockListener;
private UncaughtExceptionHandler mRealExceptionHandler;
@@ -66,7 +66,8 @@
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 @@
@Test
public void testNonDebuggable() {
- mPluginManager = new PluginManager(getContext(), mMockFactory, false,
+ mPluginManager = new PluginManagerImpl(getContext(), mMockFactory, false,
mMockExceptionHandler);
resetExceptionHandler();
@@ -148,7 +149,7 @@
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 7153340..deb31da 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 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.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 @@
.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 e38c30f..1ff373c 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.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 @@
@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 d1e17f4..5ae107a 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.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.test.suitebuilder.annotation.SmallTest;
@SmallTest
-@RunWith(SysUIRunner.class)
+@RunWith(AndroidTestingRunner.class)
@RunWithLooper
public class TileQueryHelperTest extends SysuiTestCase {
private TestableLooper mBGLooper;
@@ -46,7 +46,7 @@
@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 6d7b50f..ddd6615 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.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 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 4250962..bdd05c7 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.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 @@
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 @@
assertFalse(mWakeLock.isHeld());
}
- @Ignore("Flaky")
@Test
public void transientIndication_releasesWakeLock_afterHidingDelayed() throws Throwable {
mInstrumentation.runOnMainSync(() -> {
@@ -188,12 +183,10 @@
});
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 8520bdb..5b9270d 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.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";
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 b8be4fa..c2c6336 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 3ccb160..e41a827 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 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.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 f55115e0..a9acda3 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.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 @@
@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 1fa9846..e7cdcb5 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 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 @@
@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 e3a5ef0..3e79a04 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 @@
@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 8cbf95b..efa232b 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 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 @@
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 f40fe4c..0000000
--- 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 b118fdc..d94ecc0 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 @@
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 fa07d33..a843cca 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 @@
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 6074a01..0ba0319 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 @@
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 08211f8..51149ab 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 857a785..886722e 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 c0f5783..b9d188a 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 @@
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 630abd7..f6fd2cb 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 781960d..69e2361 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 21871fc..51e35cc 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 eab436c..29d7f1c 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 0ec0d77..18b07cf 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 47ed5ca..64fe8dd 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 707fc4b..5ae8e22 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 59a9361..0a83a89 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 @@
}
@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 00e2404..d60fe78 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 2d53c77..157b8a0 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 b13535f..6b501af 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 @@
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 b841ce9..8db82e2 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 @@
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 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 578b310..f7ef653a 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 7581363..fb9bf7a 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.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 6c51524..94af7733 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.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 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 @@
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 @@
};
@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 <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;
+ 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).addCallback(any());
- doAnswer(new Answer<Void>() {
- @Override
- public Void answer(InvocationOnMock invocation) throws Throwable {
- getTracker(tag).getLeakInfo(invocation.getArguments()[0]).clearAllocations();
- return null;
- }
- }).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 1d016fb..0000000
--- 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 26ffd10..0000000
--- 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 25481ce..3800f29 100644
--- a/proto/src/metrics_constants.proto
+++ b/proto/src/metrics_constants.proto
@@ -3601,6 +3601,13 @@
// 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;
+
// ---- 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 b4630ef..c532efb 100644
--- a/rs/jni/android_renderscript_RenderScript.cpp
+++ b/rs/jni/android_renderscript_RenderScript.cpp
@@ -48,9 +48,6 @@
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 fd93865..9e4d89c 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 @@
*/
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 @@
*/
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 @@
}
}
+ void notifyAccessibilityButtonClicked() {
+ if (mMagnificationGestureHandler != null) {
+ mMagnificationGestureHandler.notifyShortcutTriggered();
+ }
+ }
+
private void enableFeatures() {
resetStreamState();
@@ -393,11 +406,14 @@
}
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 b56035f..397938a 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 @@
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 @@
userState.mIsTouchExplorationEnabled = false;
userState.mIsEnhancedWebAccessibilityEnabled = false;
userState.mIsDisplayMagnificationEnabled = false;
+ userState.mIsNavBarMagnificationEnabled = false;
userState.mIsAutoclickEnabled = false;
userState.mEnabledServices.clear();
}
@@ -829,6 +832,7 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 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 @@
}
});
} break;
+
+ case MSG_SEND_ACCESSIBILITY_BUTTON_TO_INPUT_FILTER: {
+ synchronized (mLock) {
+ if (mHasInputFilter && mInputFilter != null) {
+ mInputFilter.notifyAccessibilityButtonClicked();
+ }
+ }
+ }
}
}
@@ -4786,6 +4815,7 @@
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 @@
mIsTouchExplorationEnabled = false;
mIsEnhancedWebAccessibilityEnabled = false;
mIsDisplayMagnificationEnabled = false;
+ mIsNavBarMagnificationEnabled = false;
mIsAutoclickEnabled = false;
mSoftKeyboardShowMode = 0;
@@ -4886,6 +4917,9 @@
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 @@
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 @@
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 f65046c..caa74b9 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 @@
}
}
+ 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 f6e5340..7e82eda 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 @@
* 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 @@
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 @@
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 @@
}
return;
}
- if (!mDetectControlGestures) {
+ if (!mDetectTripleTap && !mTriggerable) {
if (mNext != null) {
dispatchTransformedEvent(event, rawEvent, policyFlags);
}
@@ -151,7 +184,7 @@
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 @@
@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 @@
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 @@
final double distance = GestureUtils.computeDistance(mLastDownEvent,
event, 0);
if (Math.abs(distance) > mTapDistanceSlop) {
- transitionToDelegatingStateAndClear();
+ transitionToDelegatingState(true);
}
}
}
break;
case MotionEvent.ACTION_UP: {
+ if (!mMagnificationController.magnificationRegionContains(
+ event.getX(), event.getY())) {
+ transitionToDelegatingState(!mShortcutTriggered);
+ return;
+ }
+ if (mShortcutTriggered) {
+ clear();
+ onActionTap(event, policyFlags);
+ return;
+ }
if (mLastDownEvent == null) {
return;
}
mHandler.removeMessages(MESSAGE_ON_ACTION_TAP_AND_HOLD);
- if (!mMagnificationController.magnificationRegionContains(
- event.getX(), event.getY())) {
- transitionToDelegatingStateAndClear();
- return;
- }
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 @@
@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 @@
}
}
- 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 @@
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 a372f95..af1193d 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
@@ -316,13 +316,13 @@
@Override
public void startSession(IBinder activityToken, IBinder windowToken, IBinder appCallback,
AutofillId autofillId, Rect bounds, AutofillValue value, int userId,
- boolean hasCallback) {
+ boolean hasCallback, int flags) {
// TODO(b/33197203): make sure it's called by resumed / focused activity
synchronized (mLock) {
final AutofillManagerServiceImpl service = getServiceForUserLocked(userId);
service.startSessionLocked(activityToken, windowToken, appCallback,
- autofillId, bounds, value, hasCallback);
+ autofillId, bounds, value, hasCallback, flags);
}
}
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
index b6c60d0..3e5ad82 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
@@ -60,6 +60,7 @@
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;
@@ -169,7 +170,7 @@
structure.sanitizeForParceling(true);
// TODO(b/33197203): Need to pipe the bundle
- session.mRemoteFillService.onFillRequest(structure, null);
+ session.mRemoteFillService.onFillRequest(structure, null, session.mFlags);
}
};
@@ -284,7 +285,8 @@
}
void startSessionLocked(IBinder activityToken, IBinder windowToken, IBinder appCallbackToken,
- AutofillId autofillId, Rect bounds, AutofillValue value, boolean hasCallback) {
+ AutofillId autofillId, Rect bounds, AutofillValue value, boolean hasCallback,
+ int flags) {
if (!hasService()) {
return;
}
@@ -292,7 +294,7 @@
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 +305,7 @@
}
final Session newSession = createSessionByTokenLocked(activityToken,
- windowToken, appCallbackToken, hasCallback);
+ windowToken, appCallbackToken, hasCallback, flags);
newSession.updateLocked(autofillId, bounds, value, FLAG_START_SESSION);
}
@@ -336,9 +338,9 @@
}
private Session createSessionByTokenLocked(IBinder activityToken, IBinder windowToken,
- IBinder appCallbackToken, boolean hasCallback) {
+ IBinder appCallbackToken, boolean hasCallback, int flags) {
final Session newSession = new Session(mContext, activityToken,
- windowToken, appCallbackToken, hasCallback);
+ windowToken, appCallbackToken, hasCallback, flags);
mSessions.put(activityToken, newSession);
/*
@@ -628,13 +630,19 @@
*/
private boolean mHasCallback;
+ /**
+ * Flags used to start the session.
+ */
+ private int mFlags;
+
private Session(Context context, IBinder activityToken, IBinder windowToken,
- IBinder client, boolean hasCallback) {
+ IBinder client, boolean hasCallback, int flags) {
mRemoteFillService = new RemoteFillService(context,
mInfo.getServiceInfo().getComponentName(), mUserId, this);
mActivityToken = activityToken;
mWindowToken = windowToken;
mHasCallback = hasCallback;
+ mFlags = flags;
mClient = IAutoFillManagerClient.Stub.asInterface(client);
try {
@@ -802,35 +810,101 @@
Slog.d(TAG, "showSaveLocked(): saveInfo=" + saveInfo);
}
- if (saveInfo == null || saveInfo.getSavableIds() == null
- || saveInfo.getSavableIds().isEmpty()) {
+ /*
+ * 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;
}
- 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;
+ }
+
+ 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;
}
}
-
// 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);
}
+ removeSelf();
}
/**
@@ -913,7 +987,11 @@
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,13 +1029,9 @@
@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);
@@ -1034,6 +1108,7 @@
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);
diff --git a/services/autofill/java/com/android/server/autofill/RemoteFillService.java b/services/autofill/java/com/android/server/autofill/RemoteFillService.java
index eeff37c..b1cc89b 100644
--- a/services/autofill/java/com/android/server/autofill/RemoteFillService.java
+++ b/services/autofill/java/com/android/server/autofill/RemoteFillService.java
@@ -132,9 +132,10 @@
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();
}
@@ -240,7 +241,7 @@
}
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 {
@@ -322,7 +323,7 @@
}
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) {
@@ -418,11 +419,13 @@
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,7 +472,7 @@
if (remoteService != null) {
try {
remoteService.mAutoFillService.onFillRequest(mStructure,
- mExtras, mCallback);
+ mExtras, mCallback, mFlags);
synchronized (mLock) {
mStructure = null;
mExtras = null;
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 3eefa7c..c7e59a3 100644
--- a/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
+++ b/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
@@ -123,7 +123,7 @@
}
hideSaveUiUiThread();
if (mFillUi != null) {
- mFillUi.filter(filterText);
+ mFillUi.setFilterText(filterText);
}
});
}
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 3fdcd9e..a7d9fe9 100644
--- a/services/autofill/java/com/android/server/autofill/ui/FillUi.java
+++ b/services/autofill/java/com/android/server/autofill/ui/FillUi.java
@@ -113,8 +113,13 @@
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 +139,13 @@
mCallback.onDatasetPicked(vi.getDataset());
});
- filter(filterText);
+ if (filterText == null) {
+ mFilterText = null;
+ } else {
+ mFilterText = filterText.toLowerCase();
+ }
+
+ applyNewFilterText();
mWindow = new AnchoredWindow(windowToken, mListView);
}
}
@@ -147,16 +158,8 @@
}
}
- 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,6 +179,26 @@
});
}
+ 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();
mWindow.hide();
@@ -235,7 +258,7 @@
ViewItem(Dataset dataset, String value, View view) {
mDataset = dataset;
- mValue = value.toLowerCase();
+ mValue = value;
mView = view;
}
diff --git a/services/core/java/com/android/server/FontManagerService.java b/services/core/java/com/android/server/FontManagerService.java
index 593c322..55a945a 100644
--- a/services/core/java/com/android/server/FontManagerService.java
+++ b/services/core/java/com/android/server/FontManagerService.java
@@ -71,11 +71,8 @@
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/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index 0d5a3e0..accae0d 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -4003,12 +4003,12 @@
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 +4061,7 @@
return getAccountsInternal(
accounts,
callingUid,
- callingPackage,
+ opPackageName,
visibleAccountTypes,
includeUserManagedNotVisible);
} finally {
@@ -4178,7 +4178,7 @@
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 +4197,10 @@
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 +4362,7 @@
* 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 +4374,9 @@
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 +4389,13 @@
}
}
+ 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 +5374,13 @@
@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 +5400,7 @@
@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 +5480,7 @@
*/
@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 6bf77ae..55d661c 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 @@
: 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/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index 2d91cad..2885e66 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.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 @@
}
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 @@
}
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 @@
}
}
+ /**
+ * @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 @@
// 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 @@
}
// 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 2ae815e30..42efe0b 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 @@
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/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java
index c0c433e..99fe418 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 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 @@
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 79b99a3..333d27b 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 @@
/** @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 @@
/** @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 d5fa26c..81e891a 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 @@
}
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 @@
}
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 @@
}
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 @@
}
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 @@
}
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 @@
}
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 @@
}
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 @@
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/fingerprint/AuthenticationClient.java b/services/core/java/com/android/server/fingerprint/AuthenticationClient.java
index 14f2e86..552f0d1 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 @@
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 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 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 43bb21d..492cd61 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 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 @@
}
/**
- * 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 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 @@
}
/**
- * 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 eddcd5b..e1b78a8 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 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 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 55bf689..34f245f 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 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 0f29942..3262151 100644
--- a/services/core/java/com/android/server/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/fingerprint/FingerprintService.java
@@ -206,7 +206,7 @@
@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;
@@ -235,7 +235,7 @@
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 +391,7 @@
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 +405,7 @@
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 +417,7 @@
}
/**
- * 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 +447,7 @@
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 +469,7 @@
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/om/OverlayManagerServiceImpl.java b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
index b085179..6af1c3b 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 @@
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 @@
}
}
+ 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 @@
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/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index 71bfa64..e1426fd 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 @@
// 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 c27806d..4ac1cce 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -399,7 +399,7 @@
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;
@@ -10565,9 +10565,9 @@
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 +12190,11 @@
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) {
@@ -16885,6 +16890,7 @@
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 +19680,17 @@
}
@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/PackageSettingBase.java b/services/core/java/com/android/server/pm/PackageSettingBase.java
index b9c43da..dfed72f 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 @@
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 @@
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 @@
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 570b31f..7bd3424 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 @@
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 @@
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 @@
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 @@
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 7885748..21fe5ba 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 @@
// 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 @@
}
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 8998212..ef46bae 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 @@
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/dex/DexManager.java b/services/core/java/com/android/server/pm/dex/DexManager.java
index 755c486..83dd392 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 @@
// 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 8043c65..08eca73 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 @@
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 @@
+ 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 @@
|| 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 @@
}
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 @@
+ ", 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 @@
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 e026130..52763a1 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 @@
userState.serviceStateMap.put(component, serviceState);
updateServiceConnectionLocked(component, userId);
} else {
- inputList.addAll(serviceState.hardwareInputList);
+ inputList.addAll(serviceState.hardwareInputMap.values());
}
} else {
try {
@@ -2105,7 +2105,7 @@
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 @@
}
if (serviceState.isHardware) {
- serviceState.hardwareInputList.clear();
+ serviceState.hardwareInputMap.clear();
for (TvInputHardwareInfo hardware : mTvInputHardwareManager.getHardwareList()) {
try {
serviceState.service.notifyHardwareAdded(hardware);
@@ -2283,7 +2283,7 @@
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 @@
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/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java
index 1510dd1..5abc4e4 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 @@
|| 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 @@
private final long mLongAnimationDuration;
+ private boolean mForceShowMagnifiableBounds = false;
+
public DisplayMagnifier(WindowManagerService windowManagerService,
MagnificationCallbacks callbacks) {
mContext = windowManagerService.mContext;
@@ -283,6 +293,15 @@
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 @@
// 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 @@
}
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 @@
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 4191d31..7a36da2 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_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 @@
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 @@
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 @@
}
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/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index cb3a663..e5b00f3 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_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 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.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 @@
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 @@
* 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 @@
*/
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 @@
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 0222b3d..68d0f24 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_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 @@
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/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index bde8e33..5551afe 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.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;
@@ -592,11 +591,6 @@
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 +835,6 @@
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.
@@ -2386,10 +2377,10 @@
// 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()) {
@@ -2430,7 +2421,7 @@
if (dc.isDefaultDisplay) {
mPolicy.setCurrentOrientationLw(req);
}
- if (updateRotationUncheckedLocked(inTransaction, displayId)) {
+ if (dc.updateRotationUnchecked(inTransaction)) {
// changed
return true;
}
@@ -3768,10 +3759,12 @@
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();
}
}
}
@@ -3787,9 +3780,10 @@
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();
@@ -3797,234 +3791,13 @@
}
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) {
@@ -4497,7 +4270,7 @@
// 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;
@@ -4522,297 +4295,9 @@
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() {
@@ -6024,7 +5509,7 @@
displayId);
final Configuration currentDisplayConfig = displayContent.getConfiguration();
mTempConfiguration.setTo(currentDisplayConfig);
- computeScreenConfigurationLocked(mTempConfiguration, displayId);
+ displayContent.computeScreenConfiguration(mTempConfiguration);
configChanged |= currentDisplayConfig.diff(mTempConfiguration) != 0;
if (configChanged) {
@@ -6487,7 +5972,8 @@
if (updateRotation) {
if (DEBUG_ORIENTATION) Slog.d(TAG_WM, "Performing post-rotate rotation");
- configChanged |= updateRotationUncheckedLocked(false, displayId);
+ configChanged |= displayContent.updateRotationUnchecked(
+ false /* inTransaction */);
}
if (configChanged) {
@@ -6530,8 +6016,9 @@
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) {
@@ -7567,9 +7054,10 @@
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();
}
}
}
@@ -7595,6 +7083,17 @@
}
@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 ccbc5ba..4e593d8 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -1273,8 +1273,8 @@
void prelayout() {
if (mEnforceSizeCompat) {
- mGlobalScale = mService.mCompatibleScreenScale;
- mInvGlobalScale = 1/mGlobalScale;
+ mGlobalScale = getDisplayContent().mCompatibleScreenScale;
+ mInvGlobalScale = 1 / mGlobalScale;
} else {
mGlobalScale = mInvGlobalScale = 1;
}
diff --git a/services/core/jni/com_android_server_tv_TvInputHal.cpp b/services/core/jni/com_android_server_tv_TvInputHal.cpp
index 9f528b1..b433350 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 @@
jmethodID deviceId;
jmethodID type;
jmethodID hdmiPortId;
+ jmethodID cableConnectionStatus;
jmethodID audioType;
jmethodID audioAddress;
jmethodID build;
@@ -469,6 +470,9 @@
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 @@
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 7ad0292..0e6a542 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 @@
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/net/java/android/net/apf/ApfFilter.java b/services/net/java/android/net/apf/ApfFilter.java
index 0a90749..a0a9310 100644
--- a/services/net/java/android/net/apf/ApfFilter.java
+++ b/services/net/java/android/net/apf/ApfFilter.java
@@ -93,17 +93,10 @@
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 @@
}
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 @@
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 @@
mMulticastFilter = multicastFilter;
mMetricsLog = log;
+ // TODO: ApfFilter should not generate programs until IpManager sends provisioning success.
maybeStartFilter();
}
@@ -661,14 +662,19 @@
// 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;
+ private int mNumProgramUpdates = 0;
+ // How many times the program was updated since we started for allowing multicast traffic.
+ @GuardedBy("this")
+ private int mNumProgramUpdatesAllowingMulticast = 0;
/**
* Generate filter code to process ARP packets. Execution of this code ends in either the
@@ -947,7 +953,8 @@
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 @@
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 @@
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 76b1c90..3e3a19b 100644
--- a/services/net/java/android/net/ip/IpManager.java
+++ b/services/net/java/android/net/ip/IpManager.java
@@ -23,6 +23,7 @@
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,10 +35,12 @@
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.text.TextUtils;
import android.util.LocalLog;
@@ -631,6 +634,13 @@
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();
@@ -1020,14 +1030,16 @@
private boolean startIPv6() {
// Set privacy extensions.
+ final String PREFER_TEMPADDRS = "2";
try {
- mNwService.setInterfaceIpv6PrivacyExtensions(mInterfaceName, true);
+ NetdService.run((INetd netd) -> {
+ netd.setProcSysNet(
+ INetd.IPV6, INetd.CONF, mInterfaceName, "use_tempaddr",
+ PREFER_TEMPADDRS);
+ });
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 153cb50..6e69ff5 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 @@
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 @@
}
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/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java b/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java
index fa0bd39..72fb78e 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 @@
@Before
public void setup() {
-
mUser0 = 0;
mUser1 = 1;
@@ -352,6 +351,15 @@
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/usage/java/com/android/server/usage/StorageStatsService.java b/services/usage/java/com/android/server/usage/StorageStatsService.java
index ebd4b01..169cf4d 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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
}
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/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 632a1d6..7a226a0 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -1312,6 +1312,84 @@
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 @@
});
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 @@
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 a3e11c8..199a12a 100644
--- a/telephony/java/android/telephony/PhoneNumberUtils.java
+++ b/telephony/java/android/telephony/PhoneNumberUtils.java
@@ -1620,7 +1620,7 @@
//
// 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 7a83979..5fb83ab 100644
--- a/telephony/java/android/telephony/ServiceState.java
+++ b/telephony/java/android/telephony/ServiceState.java
@@ -243,6 +243,10 @@
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 @@
mIsEmergencyOnly = s.mIsEmergencyOnly;
mIsDataRoamingFromRegistration = s.mIsDataRoamingFromRegistration;
mIsUsingCarrierAggregation = s.mIsUsingCarrierAggregation;
+ mLteEarfcnRsrpBoost = s.mLteEarfcnRsrpBoost;
}
/**
@@ -351,6 +356,7 @@
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 @@
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 @@
+ " DefRoamInd=" + mCdmaDefaultRoamingIndicator
+ " EmergOnly=" + mIsEmergencyOnly
+ " IsDataRoamingFromRegistration=" + mIsDataRoamingFromRegistration
- + " IsUsingCarrierAggregation=" + mIsUsingCarrierAggregation);
+ + " IsUsingCarrierAggregation=" + mIsUsingCarrierAggregation
+ + " mLteEarfcnRsrpBoost=" + mLteEarfcnRsrpBoost);
}
private void setNullState(int state) {
@@ -842,6 +850,7 @@
mIsEmergencyOnly = false;
mIsDataRoamingFromRegistration = false;
mIsUsingCarrierAggregation = false;
+ mLteEarfcnRsrpBoost = 0;
}
public void setStateOutOfService() {
@@ -1016,6 +1025,7 @@
mIsEmergencyOnly = m.getBoolean("emergencyOnly");
mIsDataRoamingFromRegistration = m.getBoolean("isDataRoamingFromRegistration");
mIsUsingCarrierAggregation = m.getBoolean("isUsingCarrierAggregation");
+ mLteEarfcnRsrpBoost = m.getInt("LteEarfcnRsrpBoost");
}
/**
@@ -1046,6 +1056,7 @@
m.putBoolean("emergencyOnly", mIsEmergencyOnly);
m.putBoolean("isDataRoamingFromRegistration", mIsDataRoamingFromRegistration);
m.putBoolean("isUsingCarrierAggregation", mIsUsingCarrierAggregation);
+ m.putInt("LteEarfcnRsrpBoost", mLteEarfcnRsrpBoost);
}
/** @hide */
@@ -1081,6 +1092,16 @@
}
/** @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 c484fd3..9e02399 100644
--- a/telephony/java/android/telephony/SignalStrength.java
+++ b/telephony/java/android/telephony/SignalStrength.java
@@ -64,6 +64,8 @@
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 @@
mLteRsrq = INVALID;
mLteRssnr = INVALID;
mLteCqi = INVALID;
+ mLteRsrpBoost = 0;
mTdScdmaRscp = INVALID;
isGsm = true;
}
@@ -129,6 +132,7 @@
mLteRsrq = INVALID;
mLteRssnr = INVALID;
mLteCqi = INVALID;
+ mLteRsrpBoost = 0;
mTdScdmaRscp = INVALID;
isGsm = gsmFlag;
}
@@ -142,10 +146,26 @@
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 @@
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 @@
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 @@
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 @@
* @param lteRsrq
* @param lteRssnr
* @param lteCqi
+ * @param lteRsrpBoost
* @param gsm
*
* @hide
@@ -235,7 +256,7 @@
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 @@
mLteRsrq = lteRsrq;
mLteRssnr = lteRssnr;
mLteCqi = lteCqi;
+ mLteRsrpBoost = lteRsrpBoost;
mTdScdmaRscp = INVALID;
isGsm = gsm;
if (DBG) log("initialize: " + toString());
@@ -269,6 +291,7 @@
mLteRsrq = s.mLteRsrq;
mLteRssnr = s.mLteRssnr;
mLteCqi = s.mLteCqi;
+ mLteRsrpBoost = s.mLteRsrpBoost;
mTdScdmaRscp = s.mTdScdmaRscp;
isGsm = s.isGsm;
}
@@ -293,6 +316,7 @@
mLteRsrq = in.readInt();
mLteRssnr = in.readInt();
mLteCqi = in.readInt();
+ mLteRsrpBoost = in.readInt();
mTdScdmaRscp = in.readInt();
isGsm = (in.readInt() != 0);
}
@@ -340,6 +364,7 @@
out.writeInt(mLteRsrq);
out.writeInt(mLteRssnr);
out.writeInt(mLteCqi);
+ out.writeInt(mLteRsrpBoost);
out.writeInt(mTdScdmaRscp);
out.writeInt(isGsm ? 1 : 0);
}
@@ -416,6 +441,18 @@
}
/**
+ * @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 @@
return mLteCqi;
}
+ /** @hide */
+ public int getLteRsrpBoost() {
+ return mLteRsrpBoost;
+ }
+
/**
* Retrieve an abstract level value for the overall signal strength.
*
@@ -793,12 +835,19 @@
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 @@
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 @@
+ (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 @@
&& mLteRsrq == s.mLteRsrq
&& mLteRssnr == s.mLteRssnr
&& mLteCqi == s.mLteCqi
+ && mLteRsrpBoost == s.mLteRsrpBoost
&& mTdScdmaRscp == s.mTdScdmaRscp
&& isGsm == s.isGsm);
}
@@ -993,6 +1044,7 @@
+ " " + mLteRsrq
+ " " + mLteRssnr
+ " " + mLteCqi
+ + " " + mLteRsrpBoost
+ " " + mTdScdmaRscp
+ " " + (isGsm ? "gsm|lte" : "cdma"));
}
@@ -1016,6 +1068,7 @@
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 @@
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 51b91f4..7862523 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -5276,23 +5276,42 @@
}
}
+
+ /**
+ * @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 29ba776..506f406 100644
--- a/test-runner/src/android/test/mock/MockPackageManager.java
+++ b/test-runner/src/android/test/mock/MockPackageManager.java
@@ -643,6 +643,12 @@
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/net/java/com/android/server/connectivity/IpConnectivityEventBuilderTest.java b/tests/net/java/com/android/server/connectivity/IpConnectivityEventBuilderTest.java
index 11105d6..48861bd 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 @@
ConnectivityMetricsEvent ev = describeIpEvent(
aType(ApfProgramEvent.class),
aLong(200),
+ aLong(18),
anInt(7),
anInt(9),
anInt(2048),
@@ -320,7 +321,7 @@
" 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 @@
anInt(1),
anInt(2),
anInt(4),
+ anInt(7),
+ anInt(3),
anInt(2048));
String want = joinLines(
@@ -360,8 +363,8 @@
" 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 1f7c5f4..785e1ce 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 @@
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 @@
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 @@
@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 @@
// 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 @@
" 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 @@
}
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 c5965e8..5064b9b 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 @@
}
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 9e0f3213..0ab4406 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.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 @@
// 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 @@
}
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 0000000..58399fd
--- /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-updated-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
similarity index 89%
rename from packages/SystemUI/tests/src/com/android/systemui/SysUIRunner.java
rename to tests/testables/src/android/testing/AndroidTestingRunner.java
index fd99d1d..816ed03 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 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
similarity index 86%
rename from packages/SystemUI/tests/src/com/android/systemui/FragmentTestCase.java
rename to tests/testables/src/android/testing/BaseFragmentTest.java
index 1678d92..53841d5 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.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 @@
* 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 @@
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 @@
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 @@
});
}
- private String hex(Looper looper) {
- return Integer.toHexString(System.identityHashCode(looper));
+ protected TestableContext getContext() {
+ return new TestableContext(InstrumentationRegistry.getContext());
}
@After
@@ -174,14 +178,14 @@
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 @@
@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
similarity index 92%
rename from packages/SystemUI/src/com/android/systemui/util/LayoutInflaterBuilder.java
rename to tests/testables/src/android/testing/LayoutInflaterBuilder.java
index 5cfe677..098302e 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 0000000..8daaa8f
--- /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
similarity index 94%
rename from packages/SystemUI/tests/src/com/android/systemui/utils/FakeContentResolver.java
rename to tests/testables/src/android/testing/TestableContentResolver.java
index 34f2e01..bfafbe0 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 @@
/**
* 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
similarity index 70%
rename from packages/SystemUI/tests/src/com/android/systemui/utils/TestableContext.java
rename to tests/testables/src/android/testing/TestableContext.java
index 1429390..cb5d4cb 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.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, SysuiTestCase test) {
+ public TestableContext(Context base) {
+ this(base, null);
+ }
+
+ 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 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 @@
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 @@
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 @@
super.unregisterComponentCallbacks(callback);
}
- @SuppressWarnings("unchecked")
- public <T> T getComponent(Class<T> interfaceType) {
- return (T) (mComponents != null ? mComponents.get(interfaceType) : null);
- }
+ @Override
+ public Statement apply(Statement base, Description description) {
+ return new TestWatcher() {
+ @Override
+ protected void succeeded(Description description) {
+ mSettingsProvider.clearOverrides();
+ }
- public <T, C extends T> void putComponent(Class<T> interfaceType, C component) {
- mComponents = lazyInit(mComponents);
- mComponents.put(interfaceType, component);
+ @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
similarity index 92%
rename from packages/SystemUI/tests/src/com/android/systemui/utils/TestableImageView.java
rename to tests/testables/src/android/testing/TestableImageView.java
index b131460..901e25b 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
similarity index 99%
rename from packages/SystemUI/tests/src/com/android/systemui/utils/TestableLooper.java
rename to tests/testables/src/android/testing/TestableLooper.java
index 8902e0c..8a33cf9 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 0000000..d19f1ef
--- /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
similarity index 96%
rename from packages/SystemUI/tests/src/com/android/systemui/UiThreadTest.java
rename to tests/testables/src/android/testing/UiThreadTest.java
index 58369b1..e40e1d7 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
similarity index 90%
rename from packages/SystemUI/tests/src/com/android/systemui/utils/ViewUtils.java
rename to tests/testables/src/android/testing/ViewUtils.java
index 678b9f4..5a651aa 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 0000000..752d536
--- /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-updated-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 0000000..f6006b0
--- /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
similarity index 93%
rename from packages/SystemUI/tests/src/com/android/systemui/utils/TestableLooperTest.java
rename to tests/testables/tests/src/android/testing/TestableLooperTest.java
index 2416e1d..18e5fff 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 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
similarity index 88%
rename from packages/SystemUI/tests/src/com/android/systemui/utils/FakeSettingsProviderTest.java
rename to tests/testables/tests/src/android/testing/TestableSettingsTest.java
index 63bb5e7..1b01542 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.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 @@
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 @@
@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 @@
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 @@
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 a6f32d4..38bd5b5 100644
--- a/tools/aapt2/integration-tests/AppOne/Android.mk
+++ b/tools/aapt2/integration-tests/AppOne/Android.mk
@@ -21,6 +21,7 @@
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 0000000..1251949
--- /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 0000000..88266de
--- /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 0000000..f4963a9
--- /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 0000000..5d8b36c
--- /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 c8f0217..1042111 100644
--- a/tools/aapt2/link/Link.cpp
+++ b/tools/aapt2/link/Link.cpp
@@ -77,6 +77,7 @@
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 @@
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 @@
}
// 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 @@
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 @@
}
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 @@
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 @@
.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 aa840e2..d10351b 100644
--- a/tools/aapt2/util/Files.cpp
+++ b/tools/aapt2/util/Files.cpp
@@ -264,5 +264,57 @@
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 95c492f..b3b1e48 100644
--- a/tools/aapt2/util/Files.h
+++ b/tools/aapt2/util/Files.h
@@ -132,6 +132,11 @@
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/drawable/AdaptiveIconDrawable_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/drawable/AdaptiveIconDrawable_Delegate.java
new file mode 100644
index 0000000..7e9432d
--- /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 2274b90..906ebb1 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 @@
}
@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/impl/ResourceHelper.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/ResourceHelper.java
index f1e7b51..4533774 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 @@
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
new file mode 100644
index 0000000..7014ddb
--- /dev/null
+++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/adaptive_icon.png
Binary files differ
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 0000000..8f862c8
--- /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 0000000..ca9fa55
--- /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 7199781..2b5e0f9 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 @@
}
@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 b0aa3c2..4f226cb 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 @@
"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 @@
* 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 18c1245..af48d0a 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 @@
*/
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/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index bbe96a7..4f2881b 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -21,6 +21,7 @@
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 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 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 @@
@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 @@
}
/**
- * 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 {