diff options
435 files changed, 11027 insertions, 8907 deletions
diff --git a/api/current.txt b/api/current.txt index 97a6794c200c..a4893f9ed378 100644 --- a/api/current.txt +++ b/api/current.txt @@ -751,6 +751,7 @@ package android { field public static final int isModifier = 16843334; // 0x1010246 field public static final int isRepeatable = 16843336; // 0x1010248 field public static final int isScrollContainer = 16843342; // 0x101024e + field public static final int isStatic = 16844125; // 0x101055d field public static final int isSticky = 16843335; // 0x1010247 field public static final int isolatedProcess = 16843689; // 0x10103a9 field public static final int isolatedSplits = 16844109; // 0x101054d @@ -1052,6 +1053,7 @@ package android { field public static final int ratingBarStyleSmall = 16842877; // 0x101007d field public static final int readPermission = 16842759; // 0x1010007 field public static final int recognitionService = 16843932; // 0x101049c + field public static final int recycleEnabled = 16844124; // 0x101055c field public static final int relinquishTaskIdentity = 16843894; // 0x1010476 field public static final int reparent = 16843964; // 0x10104bc field public static final int reparentWithOverlay = 16843965; // 0x10104bd @@ -1788,6 +1790,7 @@ package android { field public static final int accessibilityActionSetProgress = 16908349; // 0x102003d field public static final int accessibilityActionShowOnScreen = 16908342; // 0x1020036 field public static final int addToDictionary = 16908330; // 0x102002a + field public static final int autofill = 16908355; // 0x1020043 field public static final int background = 16908288; // 0x1020000 field public static final int button1 = 16908313; // 0x1020019 field public static final int button2 = 16908314; // 0x102001a @@ -6565,7 +6568,7 @@ package android.app.assist { public static class AssistStructure.ViewNode { method public float getAlpha(); - method public int getAutoFillHint(); + method public java.lang.String[] getAutoFillHint(); method public android.view.autofill.AutofillId getAutofillId(); method public java.lang.String[] getAutofillOptions(); method public int getAutofillType(); @@ -8822,7 +8825,9 @@ package android.content { method public abstract deprecated android.graphics.drawable.Drawable peekWallpaper(); method public void registerComponentCallbacks(android.content.ComponentCallbacks); method public abstract android.content.Intent registerReceiver(android.content.BroadcastReceiver, android.content.IntentFilter); + method public abstract android.content.Intent registerReceiver(android.content.BroadcastReceiver, android.content.IntentFilter, boolean); method public abstract android.content.Intent registerReceiver(android.content.BroadcastReceiver, android.content.IntentFilter, java.lang.String, android.os.Handler); + method public abstract android.content.Intent registerReceiver(android.content.BroadcastReceiver, android.content.IntentFilter, java.lang.String, android.os.Handler, boolean); method public abstract deprecated void removeStickyBroadcast(android.content.Intent); method public abstract deprecated void removeStickyBroadcastAsUser(android.content.Intent, android.os.UserHandle); method public abstract void revokeUriPermission(android.net.Uri, int); @@ -9011,7 +9016,9 @@ package android.content { method public android.database.sqlite.SQLiteDatabase openOrCreateDatabase(java.lang.String, int, android.database.sqlite.SQLiteDatabase.CursorFactory, android.database.DatabaseErrorHandler); method public deprecated android.graphics.drawable.Drawable peekWallpaper(); method public android.content.Intent registerReceiver(android.content.BroadcastReceiver, android.content.IntentFilter); + method public android.content.Intent registerReceiver(android.content.BroadcastReceiver, android.content.IntentFilter, boolean); method public android.content.Intent registerReceiver(android.content.BroadcastReceiver, android.content.IntentFilter, java.lang.String, android.os.Handler); + method public android.content.Intent registerReceiver(android.content.BroadcastReceiver, android.content.IntentFilter, java.lang.String, android.os.Handler, boolean); method public deprecated void removeStickyBroadcast(android.content.Intent); method public deprecated void removeStickyBroadcastAsUser(android.content.Intent, android.os.UserHandle); method public void revokeUriPermission(android.net.Uri, int); @@ -9514,6 +9521,7 @@ package android.content { field public static final int FLAG_RECEIVER_NO_ABORT = 134217728; // 0x8000000 field public static final int FLAG_RECEIVER_REGISTERED_ONLY = 1073741824; // 0x40000000 field public static final int FLAG_RECEIVER_REPLACE_PENDING = 536870912; // 0x20000000 + field public static final int FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS = 2097152; // 0x200000 field public static final java.lang.String METADATA_DOCK_HOME = "android.dock_home"; field public static final int URI_ALLOW_UNSAFE = 4; // 0x4 field public static final int URI_ANDROID_APP_SCHEME = 2; // 0x2 @@ -21820,7 +21828,7 @@ package android.media { method public deprecated java.nio.ByteBuffer[] getInputBuffers(); method public final android.media.MediaFormat getInputFormat(); method public android.media.Image getInputImage(int); - method public android.os.Bundle getMetrics(); + method public android.media.MediaMetricsSet getMetrics(); method public final java.lang.String getName(); method public java.nio.ByteBuffer getOutputBuffer(int); method public deprecated java.nio.ByteBuffer[] getOutputBuffers(); @@ -22372,7 +22380,7 @@ package android.media { method public boolean advance(); method public long getCachedDuration(); method public android.media.DrmInitData getDrmInitData(); - method public android.os.Bundle getMetrics(); + method public android.media.MediaMetricsSet getMetrics(); method public java.util.Map<java.util.UUID, byte[]> getPsshInfo(); method public boolean getSampleCryptoInfo(android.media.MediaCodec.CryptoInfo); method public int getSampleFlags(); @@ -22627,6 +22635,69 @@ package android.media { field public static final int OPTION_PREVIOUS_SYNC = 0; // 0x0 } + public final class MediaMetricsSet { + method public double getDouble(java.lang.String, double); + method public int getInt(java.lang.String, int); + method public long getLong(java.lang.String, long); + method public java.lang.String getString(java.lang.String, java.lang.String); + method public boolean isEmpty(); + method public java.util.Set<java.lang.String> keySet(); + method public int size(); + } + + public static final class MediaMetricsSet.MediaCodec { + field public static final java.lang.String KEY_CODEC = "android.media.mediacodec.codec"; + field public static final java.lang.String KEY_ENCODER = "android.media.mediacodec.encoder"; + field public static final java.lang.String KEY_HEIGHT = "android.media.mediacodec.height"; + field public static final java.lang.String KEY_MIME = "android.media.mediacodec.mime"; + field public static final java.lang.String KEY_MODE = "android.media.mediacodec.mode"; + field public static final java.lang.String KEY_ROTATION = "android.media.mediacodec.rotation"; + field public static final java.lang.String KEY_SECURE = "android.media.mediacodec.secure"; + field public static final java.lang.String KEY_WIDTH = "android.media.mediacodec.width"; + field public static final java.lang.String MODE_AUDIO = "audio"; + field public static final java.lang.String MODE_VIDEO = "video"; + } + + public static final class MediaMetricsSet.MediaExtractor { + field public static final java.lang.String KEY_FORMAT = "android.media.mediaextractor.fmt"; + field public static final java.lang.String KEY_MIME = "android.media.mediaextractor.mime"; + field public static final java.lang.String KEY_TRACKS = "android.media.mediaextractor.ntrk"; + } + + public static final class MediaMetricsSet.MediaPlayer { + field public static final java.lang.String KEY_CODEC_AUDIO = "android.media.mediaplayer.audio.codec"; + field public static final java.lang.String KEY_CODEC_VIDEO = "android.media.mediaplayer.video.codec"; + field public static final java.lang.String KEY_DURATION = "android.media.mediaplayer.durationMs"; + field public static final java.lang.String KEY_ERRORS = "android.media.mediaplayer.err"; + field public static final java.lang.String KEY_ERROR_CODE = "android.media.mediaplayer.errcode"; + field public static final java.lang.String KEY_FRAMES = "android.media.mediaplayer.frames"; + field public static final java.lang.String KEY_FRAMES_DROPPED = "android.media.mediaplayer.dropped"; + field public static final java.lang.String KEY_HEIGHT = "android.media.mediaplayer.height"; + field public static final java.lang.String KEY_MIME_AUDIO = "android.media.mediaplayer.audio.mime"; + field public static final java.lang.String KEY_MIME_VIDEO = "android.media.mediaplayer.video.mime"; + field public static final java.lang.String KEY_PLAYING = "android.media.mediaplayer.playingMs"; + field public static final java.lang.String KEY_WIDTH = "android.media.mediaplayer.width"; + } + + public static final class MediaMetricsSet.MediaRecorder { + field public static final java.lang.String KEY_AUDIO_BITRATE = "android.media.mediarecorder.audio-bitrate"; + field public static final java.lang.String KEY_AUDIO_CHANNELS = "android.media.mediarecorder.audio-channels"; + field public static final java.lang.String KEY_AUDIO_SAMPLERATE = "android.media.mediarecorder.audio-samplerate"; + field public static final java.lang.String KEY_AUDIO_TIMESCALE = "android.media.mediarecorder.audio-timescale"; + field public static final java.lang.String KEY_CAPTURE_FPS = "android.media.mediarecorder.capture-fps"; + field public static final java.lang.String KEY_CAPTURE_FPS_ENABLE = "android.media.mediarecorder.capture-fpsenable"; + field public static final java.lang.String KEY_FRAMERATE = "android.media.mediarecorder.frame-rate"; + field public static final java.lang.String KEY_HEIGHT = "android.media.mediarecorder.height"; + field public static final java.lang.String KEY_MOVIE_TIMESCALE = "android.media.mediarecorder.movie-timescale"; + field public static final java.lang.String KEY_ROTATION = "android.media.mediarecorder.rotation"; + field public static final java.lang.String KEY_VIDEO_BITRATE = "android.media.mediarecorder.video-bitrate"; + field public static final java.lang.String KEY_VIDEO_IFRAME_INTERVAL = "android.media.mediarecorder.video-iframe-interval"; + field public static final java.lang.String KEY_VIDEO_LEVEL = "android.media.mediarecorder.video-encoder-level"; + field public static final java.lang.String KEY_VIDEO_PROFILE = "android.media.mediarecorder.video-encoder-profile"; + field public static final java.lang.String KEY_VIDEO_TIMESCALE = "android.media.mediarecorder.video-timescale"; + field public static final java.lang.String KEY_WIDTH = "android.media.mediarecorder.width"; + } + public final class MediaMuxer { ctor public MediaMuxer(java.lang.String, int) throws java.io.IOException; ctor public MediaMuxer(java.io.FileDescriptor, int) throws java.io.IOException; @@ -22667,7 +22738,7 @@ package android.media { method public java.lang.String getDrmPropertyString(java.lang.String) throws android.media.MediaPlayer.NoDrmSchemeException; method public int getDuration(); method public android.media.MediaDrm.KeyRequest getKeyRequest(byte[], java.lang.String, int, java.util.Map<java.lang.String, java.lang.String>) throws android.media.MediaPlayer.NoDrmSchemeException; - method public android.os.Bundle getMetrics(); + method public android.media.MediaMetricsSet getMetrics(); method public android.media.PlaybackParams getPlaybackParams(); method public int getSelectedTrack(int) throws java.lang.IllegalStateException; method public android.media.SyncParams getSyncParams(); @@ -22835,7 +22906,7 @@ package android.media { ctor public MediaRecorder(); method public static final int getAudioSourceMax(); method public int getMaxAmplitude() throws java.lang.IllegalStateException; - method public android.os.Bundle getMetrics(); + method public android.media.MediaMetricsSet getMetrics(); method public android.view.Surface getSurface(); method public void pause() throws java.lang.IllegalStateException; method public void prepare() throws java.io.IOException, java.lang.IllegalStateException; @@ -24646,7 +24717,7 @@ package android.media.tv { public final class TvInputInfo implements android.os.Parcelable { method public boolean canRecord(); - method public android.content.Intent createSettingsIntent(); + method public deprecated android.content.Intent createSettingsIntent(); method public android.content.Intent createSetupIntent(); method public int describeContents(); method public android.os.Bundle getExtras(); @@ -25187,8 +25258,8 @@ package android.net { method public void reportNetworkConnectivity(android.net.Network, boolean); method public boolean requestBandwidthUpdate(android.net.Network); method public void requestNetwork(android.net.NetworkRequest, android.net.ConnectivityManager.NetworkCallback); - method public void requestNetwork(android.net.NetworkRequest, int, android.net.ConnectivityManager.NetworkCallback); method public void requestNetwork(android.net.NetworkRequest, android.net.ConnectivityManager.NetworkCallback, android.os.Handler); + method public void requestNetwork(android.net.NetworkRequest, int, android.net.ConnectivityManager.NetworkCallback); method public void requestNetwork(android.net.NetworkRequest, int, android.net.ConnectivityManager.NetworkCallback, android.os.Handler); method public void requestNetwork(android.net.NetworkRequest, android.app.PendingIntent); method public deprecated void setNetworkPreference(int); @@ -26169,6 +26240,16 @@ package android.net.sip { package android.net.wifi { + public final class IconInfo implements android.os.Parcelable { + ctor public IconInfo(java.lang.String, byte[]); + ctor public IconInfo(android.net.wifi.IconInfo); + method public int describeContents(); + method public byte[] getData(); + method public java.lang.String getFilename(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.net.wifi.IconInfo> CREATOR; + } + public class ScanResult implements android.os.Parcelable { method public int describeContents(); method public boolean is80211mcResponder(); @@ -26368,7 +26449,7 @@ package android.net.wifi { public class WifiManager { method public int addNetwork(android.net.wifi.WifiConfiguration); - method public boolean addOrUpdatePasspointConfiguration(android.net.wifi.hotspot2.PasspointConfiguration); + method public void addOrUpdatePasspointConfiguration(android.net.wifi.hotspot2.PasspointConfiguration); method public static int calculateSignalLevel(int, int); method public void cancelWps(android.net.wifi.WifiManager.WpsCallback); method public static int compareSignalLevel(int, int); @@ -26397,7 +26478,7 @@ package android.net.wifi { method public boolean reassociate(); method public boolean reconnect(); method public boolean removeNetwork(int); - method public boolean removePasspointConfiguration(java.lang.String); + method public void removePasspointConfiguration(java.lang.String); method public deprecated boolean saveConfiguration(); method public void setTdlsEnabled(java.net.InetAddress, boolean); method public void setTdlsEnabledWithMacAddress(java.lang.String, boolean); @@ -26412,26 +26493,21 @@ package android.net.wifi { field public static final java.lang.String ACTION_PICK_WIFI_NETWORK = "android.net.wifi.PICK_WIFI_NETWORK"; field public static final java.lang.String ACTION_REQUEST_SCAN_ALWAYS_AVAILABLE = "android.net.wifi.action.REQUEST_SCAN_ALWAYS_AVAILABLE"; field public static final int ERROR_AUTHENTICATING = 1; // 0x1 + field public static final java.lang.String EXTRA_ANQP_ELEMENT_DATA = "android.net.wifi.extra.ANQP_ELEMENT_DATA"; field public static final java.lang.String EXTRA_BSSID = "bssid"; + field public static final java.lang.String EXTRA_BSSID_LONG = "android.net.wifi.extra.BSSID_LONG"; + field public static final java.lang.String EXTRA_DELAY = "android.net.wifi.extra.DELAY"; + field public static final java.lang.String EXTRA_ESS = "android.net.wifi.extra.ESS"; + field public static final java.lang.String EXTRA_ICON_INFO = "android.net.wifi.extra.ICON_INFO"; field public static final java.lang.String EXTRA_NETWORK_INFO = "networkInfo"; field public static final java.lang.String EXTRA_NEW_RSSI = "newRssi"; field public static final java.lang.String EXTRA_NEW_STATE = "newState"; - field public static final java.lang.String EXTRA_PASSPOINT_DEAUTH_IMMINENT_BSSID = "android.net.wifi.extra.PASSPOINT_DEAUTH_IMMINENT_BSSID"; - field public static final java.lang.String EXTRA_PASSPOINT_DEAUTH_IMMINENT_ESS = "android.net.wifi.extra.PASSPOINT_DEAUTH_IMMINENT_ESS"; - field public static final java.lang.String EXTRA_PASSPOINT_DEAUTH_IMMINENT_REASON_URL = "android.net.wifi.extra.PASSPOINT_DEAUTH_IMMINENT_REASON_URL"; - field public static final java.lang.String EXTRA_PASSPOINT_DEAUTH_IMMINENT_REAUTH_DELAY = "android.net.wifi.extra.PASSPOINT_DEAUTH_IMMINENT_REAUTH_DELAY"; - field public static final java.lang.String EXTRA_PASSPOINT_ICON_BSSID = "android.net.wifi.extra.PASSPOINT_ICON_BSSID"; - field public static final java.lang.String EXTRA_PASSPOINT_ICON_DATA = "android.net.wifi.extra.PASSPOINT_ICON_DATA"; - field public static final java.lang.String EXTRA_PASSPOINT_ICON_FILENAME = "android.net.wifi.extra.PASSPOINT_ICON_FILENAME"; - field public static final java.lang.String EXTRA_PASSPOINT_OSU_PROVIDERS_LIST_BSSID = "android.net.wifi.extra.PASSPOINT_OSU_PROVIDERS_LIST_BSSID"; - field public static final java.lang.String EXTRA_PASSPOINT_OSU_PROVIDERS_LIST_DATA = "android.net.wifi.extra.PASSPOINT_OSU_PROVIDERS_LIST_DATA"; - field public static final java.lang.String EXTRA_PASSPOINT_SUBSCRIPTION_REMEDIATION_BSSID = "android.net.wifi.extra.PASSPOINT_SUBSCRIPTION_REMEDIATION_BSSID"; - field public static final java.lang.String EXTRA_PASSPOINT_SUBSCRIPTION_REMEDIATION_SERVER_METHOD = "android.net.wifi.extra.PASSPOINT_SUBSCRIPTION_REMEDIATION_SERVER_METHOD"; - field public static final java.lang.String EXTRA_PASSPOINT_SUBSCRIPTION_REMEDIATION_SERVER_URL = "android.net.wifi.extra.PASSPOINT_SUBSCRIPTION_REMEDIATION_SERVER_URL"; field public static final java.lang.String EXTRA_PREVIOUS_WIFI_STATE = "previous_wifi_state"; field public static final java.lang.String EXTRA_RESULTS_UPDATED = "resultsUpdated"; + field public static final java.lang.String EXTRA_SUBSCRIPTION_REMEDIATION_METHOD = "android.net.wifi.extra.SUBSCRIPTION_REMEDIATION_METHOD"; field public static final java.lang.String EXTRA_SUPPLICANT_CONNECTED = "connected"; field public static final java.lang.String EXTRA_SUPPLICANT_ERROR = "supplicantError"; + field public static final java.lang.String EXTRA_URL = "android.net.wifi.extra.URL"; field public static final java.lang.String EXTRA_WIFI_INFO = "wifiInfo"; field public static final java.lang.String EXTRA_WIFI_STATE = "wifi_state"; field public static final java.lang.String NETWORK_IDS_CHANGED_ACTION = "android.net.wifi.NETWORK_IDS_CHANGED"; @@ -26650,7 +26726,6 @@ package android.net.wifi.hotspot2 { method public void setUsageLimitStartTimeInMs(long); method public void setUsageLimitTimeLimitInMinutes(long); method public void setUsageLimitUsageTimePeriodInMinutes(long); - method public boolean validate(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.net.wifi.hotspot2.PasspointConfiguration> CREATOR; } @@ -26691,7 +26766,6 @@ package android.net.wifi.hotspot2.pps { method public void setRealm(java.lang.String); method public void setSimCredential(android.net.wifi.hotspot2.pps.Credential.SimCredential); method public void setUserCredential(android.net.wifi.hotspot2.pps.Credential.UserCredential); - method public boolean validate(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.net.wifi.hotspot2.pps.Credential> CREATOR; } @@ -26704,7 +26778,6 @@ package android.net.wifi.hotspot2.pps { method public java.lang.String getCertType(); method public void setCertSha256Fingerprint(byte[]); method public void setCertType(java.lang.String); - method public boolean validate(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.net.wifi.hotspot2.pps.Credential.CertificateCredential> CREATOR; } @@ -26717,7 +26790,6 @@ package android.net.wifi.hotspot2.pps { method public java.lang.String getImsi(); method public void setEapType(int); method public void setImsi(java.lang.String); - method public boolean validate(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.net.wifi.hotspot2.pps.Credential.SimCredential> CREATOR; } @@ -26740,7 +26812,6 @@ package android.net.wifi.hotspot2.pps { method public void setPassword(java.lang.String); method public void setSoftTokenApp(java.lang.String); method public void setUsername(java.lang.String); - method public boolean validate(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.net.wifi.hotspot2.pps.Credential.UserCredential> CREATOR; } @@ -26765,7 +26836,6 @@ package android.net.wifi.hotspot2.pps { method public void setMatchAnyOis(long[]); method public void setOtherHomePartners(java.lang.String[]); method public void setRoamingConsortiumOis(long[]); - method public boolean validate(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.net.wifi.hotspot2.pps.HomeSp> CREATOR; } @@ -26792,7 +26862,6 @@ package android.net.wifi.hotspot2.pps { method public void setPolicyUpdate(android.net.wifi.hotspot2.pps.UpdateParameter); method public void setPreferredRoamingPartnerList(java.util.List<android.net.wifi.hotspot2.pps.Policy.RoamingPartner>); method public void setRequiredProtoPortMap(java.util.Map<java.lang.Integer, java.lang.String>); - method public boolean validate(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.net.wifi.hotspot2.pps.Policy> CREATOR; } @@ -26809,7 +26878,6 @@ package android.net.wifi.hotspot2.pps { method public void setFqdn(java.lang.String); method public void setFqdnExactMatch(boolean); method public void setPriority(int); - method public boolean validate(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.net.wifi.hotspot2.pps.Policy.RoamingPartner> CREATOR; } @@ -26834,7 +26902,6 @@ package android.net.wifi.hotspot2.pps { method public void setUpdateIntervalInMinutes(long); method public void setUpdateMethod(java.lang.String); method public void setUsername(java.lang.String); - method public boolean validate(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.net.wifi.hotspot2.pps.UpdateParameter> CREATOR; field public static final long UPDATE_CHECK_INTERVAL_NEVER = 4294967295L; // 0xffffffffL @@ -31915,6 +31982,7 @@ package android.preference { method public boolean hasKey(); method public boolean isEnabled(); method public boolean isPersistent(); + method public boolean isRecycleEnabled(); method public boolean isSelectable(); method protected void notifyChanged(); method public void notifyDependencyChange(boolean); @@ -31954,6 +32022,7 @@ package android.preference { method public void setOrder(int); method public void setPersistent(boolean); method public void setPreferenceDataStore(android.preference.PreferenceDataStore); + method public void setRecycleEnabled(boolean); method public void setSelectable(boolean); method public void setShouldDisableView(boolean); method public void setSummary(java.lang.CharSequence); @@ -33873,6 +33942,7 @@ package android.provider { field public static final java.lang.String EXTRA_RECIPIENT_CONTACT_CHAT_ID = "android.provider.extra.RECIPIENT_CONTACT_CHAT_ID"; field public static final java.lang.String EXTRA_RECIPIENT_CONTACT_NAME = "android.provider.extra.RECIPIENT_CONTACT_NAME"; field public static final java.lang.String EXTRA_RECIPIENT_CONTACT_URI = "android.provider.extra.RECIPIENT_CONTACT_URI"; + field public static final java.lang.String EXTRA_SENDER_ACCOUNT_HASH = "android.provider.extra.SENDER_ACCOUNT_HASH"; field public static final java.lang.String INVITE_CONTACT = "com.android.contacts.action.INVITE_CONTACT"; field public static final java.lang.String METADATA_ACCOUNT_TYPE = "android.provider.account_type"; field public static final java.lang.String METADATA_MIMETYPE = "android.provider.mimetype"; @@ -34123,21 +34193,21 @@ package android.provider { method public static android.net.Uri buildRootsUri(java.lang.String); method public static android.net.Uri buildSearchDocumentsUri(java.lang.String, java.lang.String, java.lang.String); method public static android.net.Uri buildTreeDocumentUri(java.lang.String, java.lang.String); - method public static android.net.Uri copyDocument(android.content.ContentResolver, android.net.Uri, android.net.Uri); - method public static android.net.Uri createDocument(android.content.ContentResolver, android.net.Uri, java.lang.String, java.lang.String); - method public static android.content.IntentSender createWebLinkIntent(android.content.ContentResolver, android.net.Uri, android.os.Bundle); - method public static boolean deleteDocument(android.content.ContentResolver, android.net.Uri); - method public static android.provider.DocumentsContract.Path findDocumentPath(android.content.ContentResolver, android.net.Uri); + method public static android.net.Uri copyDocument(android.content.ContentResolver, android.net.Uri, android.net.Uri) throws java.io.FileNotFoundException; + method public static android.net.Uri createDocument(android.content.ContentResolver, android.net.Uri, java.lang.String, java.lang.String) throws java.io.FileNotFoundException; + method public static android.content.IntentSender createWebLinkIntent(android.content.ContentResolver, android.net.Uri, android.os.Bundle) throws java.io.FileNotFoundException; + method public static boolean deleteDocument(android.content.ContentResolver, android.net.Uri) throws java.io.FileNotFoundException; + method public static android.provider.DocumentsContract.Path findDocumentPath(android.content.ContentResolver, android.net.Uri) throws java.io.FileNotFoundException; method public static java.lang.String getDocumentId(android.net.Uri); - method public static android.graphics.Bitmap getDocumentThumbnail(android.content.ContentResolver, android.net.Uri, android.graphics.Point, android.os.CancellationSignal); + method public static android.graphics.Bitmap getDocumentThumbnail(android.content.ContentResolver, android.net.Uri, android.graphics.Point, android.os.CancellationSignal) throws java.io.FileNotFoundException; method public static java.lang.String getRootId(android.net.Uri); method public static java.lang.String getSearchDocumentsQuery(android.net.Uri); method public static java.lang.String getTreeDocumentId(android.net.Uri); method public static boolean isDocumentUri(android.content.Context, android.net.Uri); method public static boolean isTreeUri(android.net.Uri); - method public static android.net.Uri moveDocument(android.content.ContentResolver, android.net.Uri, android.net.Uri, android.net.Uri); - method public static boolean removeDocument(android.content.ContentResolver, android.net.Uri, android.net.Uri); - method public static android.net.Uri renameDocument(android.content.ContentResolver, android.net.Uri, java.lang.String); + method public static android.net.Uri moveDocument(android.content.ContentResolver, android.net.Uri, android.net.Uri, android.net.Uri) throws java.io.FileNotFoundException; + method public static boolean removeDocument(android.content.ContentResolver, android.net.Uri, android.net.Uri) throws java.io.FileNotFoundException; + method public static android.net.Uri renameDocument(android.content.ContentResolver, android.net.Uri, java.lang.String) throws java.io.FileNotFoundException; field public static final java.lang.String ACTION_DOCUMENT_SETTINGS = "android.provider.action.DOCUMENT_SETTINGS"; field public static final java.lang.String EXTRA_ERROR = "error"; field public static final java.lang.String EXTRA_EXCLUDE_SELF = "android.provider.extra.EXCLUDE_SELF"; @@ -34645,6 +34715,7 @@ package android.provider { field public static final java.lang.String ACTION_NFCSHARING_SETTINGS = "android.settings.NFCSHARING_SETTINGS"; field public static final java.lang.String ACTION_NFC_PAYMENT_SETTINGS = "android.settings.NFC_PAYMENT_SETTINGS"; field public static final java.lang.String ACTION_NFC_SETTINGS = "android.settings.NFC_SETTINGS"; + field public static final java.lang.String ACTION_NIGHT_DISPLAY_SETTINGS = "android.settings.NIGHT_DISPLAY_SETTINGS"; field public static final java.lang.String ACTION_NOTIFICATION_LISTENER_SETTINGS = "android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS"; field public static final java.lang.String ACTION_NOTIFICATION_POLICY_ACCESS_SETTINGS = "android.settings.NOTIFICATION_POLICY_ACCESS_SETTINGS"; field public static final java.lang.String ACTION_PRINT_SETTINGS = "android.settings.ACTION_PRINT_SETTINGS"; @@ -36736,7 +36807,7 @@ package android.service.autofill { method public final android.os.IBinder onBind(android.content.Intent); method public void onConnected(); method public void onDisconnected(); - method public abstract void onFillRequest(android.app.assist.AssistStructure, android.os.Bundle, android.os.CancellationSignal, android.service.autofill.FillCallback); + method public void onFillRequest(android.app.assist.AssistStructure, android.os.Bundle, int, android.os.CancellationSignal, android.service.autofill.FillCallback); method public abstract void onSaveRequest(android.app.assist.AssistStructure, android.os.Bundle, android.service.autofill.SaveCallback); field public static final java.lang.String SERVICE_INTERFACE = "android.service.autofill.AutofillService"; field public static final java.lang.String SERVICE_META_DATA = "android.autofill"; @@ -36791,11 +36862,11 @@ package android.service.autofill { } public static final class SaveInfo.Builder { - ctor public SaveInfo.Builder(int); - method public android.service.autofill.SaveInfo.Builder addSavableIds(android.view.autofill.AutofillId...); + ctor public SaveInfo.Builder(int, android.view.autofill.AutofillId[]); method public android.service.autofill.SaveInfo build(); method public android.service.autofill.SaveInfo.Builder setDescription(java.lang.CharSequence); method public android.service.autofill.SaveInfo.Builder setNegativeAction(java.lang.CharSequence, android.content.IntentSender); + method public android.service.autofill.SaveInfo.Builder setOptionalIds(android.view.autofill.AutofillId[]); } } @@ -39173,6 +39244,7 @@ package android.telephony { method public android.os.PersistableBundle getConfigForSubId(int); method public void notifyConfigChangedForSubId(int); field public static final java.lang.String ACTION_CARRIER_CONFIG_CHANGED = "android.telephony.action.CARRIER_CONFIG_CHANGED"; + field public static final int DATA_CYCLE_THRESHOLD_DISABLED = -2; // 0xfffffffe field public static final java.lang.String KEY_ADDITIONAL_CALL_SETTING_BOOL = "additional_call_setting_bool"; field public static final java.lang.String KEY_ALLOW_ADDING_APNS_BOOL = "allow_adding_apns_bool"; field public static final java.lang.String KEY_ALLOW_ADD_CALL_DURING_VIDEO_CALL_BOOL = "allow_add_call_during_video_call"; @@ -39215,6 +39287,8 @@ package android.telephony { field public static final java.lang.String KEY_CI_ACTION_ON_SYS_UPDATE_INTENT_STRING = "ci_action_on_sys_update_intent_string"; field public static final java.lang.String KEY_CONFIG_IMS_PACKAGE_OVERRIDE_STRING = "config_ims_package_override_string"; field public static final java.lang.String KEY_CSP_ENABLED_BOOL = "csp_enabled_bool"; + field public static final java.lang.String KEY_DATA_LIMIT_THRESHOLD_BYTES_LONG = "data_limit_threshold_bytes_long"; + field public static final java.lang.String KEY_DATA_WARNING_THRESHOLD_BYTES_LONG = "data_warning_threshold_bytes_long"; field public static final java.lang.String KEY_DEFAULT_SIM_CALL_MANAGER_STRING = "default_sim_call_manager_string"; field public static final java.lang.String KEY_DEFAULT_VM_NUMBER_STRING = "default_vm_number_string"; field public static final java.lang.String KEY_DIAL_STRING_REPLACE_STRING_ARRAY = "dial_string_replace_string_array"; @@ -39270,6 +39344,7 @@ package android.telephony { field public static final java.lang.String KEY_MMS_UA_PROF_TAG_NAME_STRING = "uaProfTagName"; field public static final java.lang.String KEY_MMS_UA_PROF_URL_STRING = "uaProfUrl"; field public static final java.lang.String KEY_MMS_USER_AGENT_STRING = "userAgent"; + field public static final java.lang.String KEY_MONTHLY_DATA_CYCLE_DAY_INT = "monthly_data_cycle_day_int"; field public static final java.lang.String KEY_ONLY_SINGLE_DC_ALLOWED_INT_ARRAY = "only_single_dc_allowed_int_array"; field public static final java.lang.String KEY_OPERATOR_SELECTION_EXPAND_BOOL = "operator_selection_expand_bool"; field public static final java.lang.String KEY_PREFER_2G_BOOL = "prefer_2g_bool"; @@ -39802,7 +39877,6 @@ package android.telephony { method public android.os.PersistableBundle getCarrierConfig(); method public android.telephony.CellLocation getCellLocation(); method public int getDataActivity(); - method public boolean getDataEnabled(); method public int getDataNetworkType(); method public int getDataState(); method public java.lang.String getDeviceId(); @@ -39843,6 +39917,7 @@ package android.telephony { method public java.lang.String iccTransmitApduBasicChannel(int, int, int, int, int, java.lang.String); method public java.lang.String iccTransmitApduLogicalChannel(int, int, int, int, int, int, java.lang.String); method public boolean isConcurrentVoiceAndDataAllowed(); + method public boolean isDataEnabled(); method public boolean isHearingAidCompatibilitySupported(); method public boolean isNetworkRoaming(); method public boolean isSmsCapable(); @@ -40498,7 +40573,9 @@ package android.test.mock { method public android.database.sqlite.SQLiteDatabase openOrCreateDatabase(java.lang.String, int, android.database.sqlite.SQLiteDatabase.CursorFactory, android.database.DatabaseErrorHandler); method public android.graphics.drawable.Drawable peekWallpaper(); method public android.content.Intent registerReceiver(android.content.BroadcastReceiver, android.content.IntentFilter); + method public android.content.Intent registerReceiver(android.content.BroadcastReceiver, android.content.IntentFilter, boolean); method public android.content.Intent registerReceiver(android.content.BroadcastReceiver, android.content.IntentFilter, java.lang.String, android.os.Handler); + method public android.content.Intent registerReceiver(android.content.BroadcastReceiver, android.content.IntentFilter, java.lang.String, android.os.Handler, boolean); method public void removeStickyBroadcast(android.content.Intent); method public void removeStickyBroadcastAsUser(android.content.Intent, android.os.UserHandle); method public void revokeUriPermission(android.net.Uri, int); @@ -40878,11 +40955,10 @@ package android.text { } public final class FontConfig implements android.os.Parcelable { - ctor public FontConfig(); - ctor public FontConfig(android.text.FontConfig); + ctor public FontConfig(android.text.FontConfig.Family[], android.text.FontConfig.Alias[]); method public int describeContents(); - method public java.util.List<android.text.FontConfig.Alias> getAliases(); - method public java.util.List<android.text.FontConfig.Family> getFamilies(); + method public android.text.FontConfig.Alias[] getAliases(); + method public android.text.FontConfig.Family[] getFamilies(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.text.FontConfig> CREATOR; } @@ -40907,22 +40983,22 @@ package android.text { } public static final class FontConfig.Family implements android.os.Parcelable { - ctor public FontConfig.Family(java.lang.String, java.util.List<android.text.FontConfig.Font>, java.lang.String, java.lang.String); - ctor public FontConfig.Family(android.text.FontConfig.Family); + ctor public FontConfig.Family(java.lang.String, android.text.FontConfig.Font[], java.lang.String, int); method public int describeContents(); - method public java.util.List<android.text.FontConfig.Font> getFonts(); + method public android.text.FontConfig.Font[] getFonts(); method public java.lang.String getLanguage(); method public java.lang.String getName(); - method public java.lang.String getVariant(); + method public int getVariant(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.text.FontConfig.Family> CREATOR; + field public static final int VARIANT_COMPACT = 1; // 0x1 + field public static final int VARIANT_DEFAULT = 0; // 0x0 + field public static final int VARIANT_ELEGANT = 2; // 0x2 } public static final class FontConfig.Font implements android.os.Parcelable { - ctor public FontConfig.Font(java.lang.String, int, java.util.List<android.text.FontConfig.Axis>, int, boolean); - ctor public FontConfig.Font(android.text.FontConfig.Font); method public int describeContents(); - method public java.util.List<android.text.FontConfig.Axis> getAxes(); + method public android.text.FontConfig.Axis[] getAxes(); method public android.os.ParcelFileDescriptor getFd(); method public java.lang.String getFontName(); method public int getTtcIndex(); @@ -45091,7 +45167,7 @@ package android.view { method public float getAlpha(); method public android.view.animation.Animation getAnimation(); method public android.os.IBinder getApplicationWindowToken(); - method public int getAutofillHint(); + method public java.lang.String[] getAutofillHint(); method public int getAutofillMode(); method public int getAutofillType(); method public android.view.autofill.AutofillValue getAutofillValue(); @@ -45412,7 +45488,7 @@ package android.view { method public void setActivated(boolean); method public void setAlpha(float); method public void setAnimation(android.view.animation.Animation); - method public void setAutofillHint(int); + method public void setAutofillHint(java.lang.String...); method public void setAutofillMode(int); method public void setBackground(android.graphics.drawable.Drawable); method public void setBackgroundColor(int); @@ -45555,20 +45631,19 @@ package android.view { field public static final int ACCESSIBILITY_LIVE_REGION_NONE = 0; // 0x0 field public static final int ACCESSIBILITY_LIVE_REGION_POLITE = 1; // 0x1 field public static final android.util.Property<android.view.View, java.lang.Float> ALPHA; - field public static final int AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_DATE = 512; // 0x200 - field public static final int AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_DAY = 4096; // 0x1000 - field public static final int AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_MONTH = 1024; // 0x400 - field public static final int AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_YEAR = 2048; // 0x800 - field public static final int AUTOFILL_HINT_CREDIT_CARD_NUMBER = 128; // 0x80 - field public static final int AUTOFILL_HINT_CREDIT_CARD_SECURITY_CODE = 256; // 0x100 - field public static final int AUTOFILL_HINT_EMAIL_ADDRESS = 1; // 0x1 - field public static final int AUTOFILL_HINT_NAME = 2; // 0x2 - field public static final int AUTOFILL_HINT_NONE = 0; // 0x0 - field public static final int AUTOFILL_HINT_PASSWORD = 8; // 0x8 - field public static final int AUTOFILL_HINT_PHONE = 16; // 0x10 - field public static final int AUTOFILL_HINT_POSTAL_ADDRESS = 32; // 0x20 - field public static final int AUTOFILL_HINT_POSTAL_CODE = 64; // 0x40 - field public static final int AUTOFILL_HINT_USERNAME = 4; // 0x4 + field public static final java.lang.String AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_DATE = "creditCardExpirationDate"; + field public static final java.lang.String AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_DAY = "creditCardExpirationDay"; + field public static final java.lang.String AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_MONTH = "creditCardExpirationMonth"; + field public static final java.lang.String AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_YEAR = "creditCardExpirationYear"; + field public static final java.lang.String AUTOFILL_HINT_CREDIT_CARD_NUMBER = "creditCardNumber"; + field public static final java.lang.String AUTOFILL_HINT_CREDIT_CARD_SECURITY_CODE = "creditCardSecurityCode"; + field public static final java.lang.String AUTOFILL_HINT_EMAIL_ADDRESS = "emailAddress"; + field public static final java.lang.String AUTOFILL_HINT_NAME = "name"; + field public static final java.lang.String AUTOFILL_HINT_PASSWORD = "password"; + field public static final java.lang.String AUTOFILL_HINT_PHONE = "phone"; + field public static final java.lang.String AUTOFILL_HINT_POSTAL_ADDRESS = "postalAddress"; + field public static final java.lang.String AUTOFILL_HINT_POSTAL_CODE = "postalCode"; + field public static final java.lang.String AUTOFILL_HINT_USERNAME = "username"; field public static final int AUTOFILL_MODE_AUTO = 1; // 0x1 field public static final int AUTOFILL_MODE_INHERIT = 0; // 0x0 field public static final int AUTOFILL_MODE_MANUAL = 2; // 0x2 @@ -46230,7 +46305,7 @@ package android.view { method public abstract void setAccessibilityFocused(boolean); method public abstract void setActivated(boolean); method public abstract void setAlpha(float); - method public abstract void setAutofillHint(int); + method public abstract void setAutofillHint(java.lang.String[]); method public abstract void setAutofillOptions(java.lang.String[]); method public abstract void setAutofillType(int); method public abstract void setAutofillValue(android.view.autofill.AutofillValue); @@ -46241,6 +46316,7 @@ package android.view { method public abstract void setClickable(boolean); method public abstract void setContentDescription(java.lang.CharSequence); method public abstract void setContextClickable(boolean); + method public abstract void setDataIsSensitive(boolean); method public abstract void setDimens(int, int, int, int, int, int); method public abstract void setElevation(float); method public abstract void setEnabled(boolean); @@ -46251,7 +46327,6 @@ package android.view { method public abstract void setInputType(int); method public abstract void setLongClickable(boolean); method public abstract void setOpaque(boolean); - method public abstract void setSanitized(boolean); method public abstract void setSelected(boolean); method public abstract void setText(java.lang.CharSequence); method public abstract void setText(java.lang.CharSequence, int, int); @@ -47494,17 +47569,22 @@ package android.view.autofill { } public final class AutofillManager { + method public void cancel(); + method public void commit(); + method public boolean isEnabled(); + method public void notifyValueChanged(android.view.View); + method public void notifyViewEntered(android.view.View); + method public void notifyViewExited(android.view.View); + method public void notifyVirtualValueChanged(android.view.View, int, android.view.autofill.AutofillValue); + 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 reset(); - method public void startAutofillRequest(android.view.View); - method public void startAutofillRequestOnVirtualView(android.view.View, int, android.graphics.Rect); - method public void stopAutofillRequest(android.view.View); - method public void stopAutofillRequestOnVirtualView(android.view.View, int); + 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); - method public void valueChanged(android.view.View); - method public void virtualValueChanged(android.view.View, int, android.view.autofill.AutofillValue); 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 { @@ -47525,6 +47605,10 @@ package android.view.autofill { method public int getListValue(); method public java.lang.CharSequence getTextValue(); method public boolean getToggleValue(); + method public boolean isDate(); + method public boolean isList(); + method public boolean isText(); + method public boolean isToggle(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.view.autofill.AutofillValue> CREATOR; } diff --git a/api/system-current.txt b/api/system-current.txt index 517c748cf608..cf4902c4e18d 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -864,6 +864,7 @@ package android { field public static final int isModifier = 16843334; // 0x1010246 field public static final int isRepeatable = 16843336; // 0x1010248 field public static final int isScrollContainer = 16843342; // 0x101024e + field public static final int isStatic = 16844125; // 0x101055d field public static final int isSticky = 16843335; // 0x1010247 field public static final int isolatedProcess = 16843689; // 0x10103a9 field public static final int isolatedSplits = 16844109; // 0x101054d @@ -1165,6 +1166,7 @@ package android { field public static final int ratingBarStyleSmall = 16842877; // 0x101007d field public static final int readPermission = 16842759; // 0x1010007 field public static final int recognitionService = 16843932; // 0x101049c + field public static final int recycleEnabled = 16844124; // 0x101055c field public static final int relinquishTaskIdentity = 16843894; // 0x1010476 field public static final int reparent = 16843964; // 0x10104bc field public static final int reparentWithOverlay = 16843965; // 0x10104bd @@ -1905,6 +1907,7 @@ package android { field public static final int accessibilityActionSetProgress = 16908349; // 0x102003d field public static final int accessibilityActionShowOnScreen = 16908342; // 0x1020036 field public static final int addToDictionary = 16908330; // 0x102002a + field public static final int autofill = 16908355; // 0x1020043 field public static final int background = 16908288; // 0x1020000 field public static final int button1 = 16908313; // 0x1020019 field public static final int button2 = 16908314; // 0x102001a @@ -6815,7 +6818,7 @@ package android.app.assist { public static class AssistStructure.ViewNode { method public float getAlpha(); - method public int getAutoFillHint(); + method public java.lang.String[] getAutoFillHint(); method public android.view.autofill.AutofillId getAutofillId(); method public java.lang.String[] getAutofillOptions(); method public int getAutofillType(); @@ -9327,7 +9330,9 @@ package android.content { method public abstract deprecated android.graphics.drawable.Drawable peekWallpaper(); method public void registerComponentCallbacks(android.content.ComponentCallbacks); method public abstract android.content.Intent registerReceiver(android.content.BroadcastReceiver, android.content.IntentFilter); + method public abstract android.content.Intent registerReceiver(android.content.BroadcastReceiver, android.content.IntentFilter, boolean); method public abstract android.content.Intent registerReceiver(android.content.BroadcastReceiver, android.content.IntentFilter, java.lang.String, android.os.Handler); + method public abstract android.content.Intent registerReceiver(android.content.BroadcastReceiver, android.content.IntentFilter, java.lang.String, android.os.Handler, boolean); method public abstract deprecated void removeStickyBroadcast(android.content.Intent); method public abstract deprecated void removeStickyBroadcastAsUser(android.content.Intent, android.os.UserHandle); method public abstract void revokeUriPermission(android.net.Uri, int); @@ -9530,7 +9535,9 @@ package android.content { method public android.database.sqlite.SQLiteDatabase openOrCreateDatabase(java.lang.String, int, android.database.sqlite.SQLiteDatabase.CursorFactory, android.database.DatabaseErrorHandler); method public deprecated android.graphics.drawable.Drawable peekWallpaper(); method public android.content.Intent registerReceiver(android.content.BroadcastReceiver, android.content.IntentFilter); + method public android.content.Intent registerReceiver(android.content.BroadcastReceiver, android.content.IntentFilter, boolean); method public android.content.Intent registerReceiver(android.content.BroadcastReceiver, android.content.IntentFilter, java.lang.String, android.os.Handler); + method public android.content.Intent registerReceiver(android.content.BroadcastReceiver, android.content.IntentFilter, java.lang.String, android.os.Handler, boolean); method public deprecated void removeStickyBroadcast(android.content.Intent); method public deprecated void removeStickyBroadcastAsUser(android.content.Intent, android.os.UserHandle); method public void revokeUriPermission(android.net.Uri, int); @@ -10055,6 +10062,7 @@ package android.content { field public static final int FLAG_RECEIVER_NO_ABORT = 134217728; // 0x8000000 field public static final int FLAG_RECEIVER_REGISTERED_ONLY = 1073741824; // 0x40000000 field public static final int FLAG_RECEIVER_REPLACE_PENDING = 536870912; // 0x20000000 + field public static final int FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS = 2097152; // 0x200000 field public static final java.lang.String METADATA_DOCK_HOME = "android.dock_home"; field public static final int URI_ALLOW_UNSAFE = 4; // 0x4 field public static final int URI_ANDROID_APP_SCHEME = 2; // 0x2 @@ -11235,6 +11243,7 @@ package android.content.pm { method public abstract boolean setDefaultBrowserPackageNameAsUser(java.lang.String, int); method public abstract void setInstallerPackageName(java.lang.String, java.lang.String); method public abstract boolean setInstantAppCookie(byte[]); + method public abstract void setUpdateAvailable(java.lang.String, boolean); method public abstract boolean updateIntentVerificationStatusAsUser(java.lang.String, int, int); method public abstract void updatePermissionFlags(java.lang.String, java.lang.String, int, int, android.os.UserHandle); method public abstract void verifyIntentFilter(int, int, java.util.List<java.lang.String>); @@ -23615,7 +23624,7 @@ package android.media { method public deprecated java.nio.ByteBuffer[] getInputBuffers(); method public final android.media.MediaFormat getInputFormat(); method public android.media.Image getInputImage(int); - method public android.os.Bundle getMetrics(); + method public android.media.MediaMetricsSet getMetrics(); method public final java.lang.String getName(); method public java.nio.ByteBuffer getOutputBuffer(int); method public deprecated java.nio.ByteBuffer[] getOutputBuffers(); @@ -24167,7 +24176,7 @@ package android.media { method public boolean advance(); method public long getCachedDuration(); method public android.media.DrmInitData getDrmInitData(); - method public android.os.Bundle getMetrics(); + method public android.media.MediaMetricsSet getMetrics(); method public java.util.Map<java.util.UUID, byte[]> getPsshInfo(); method public boolean getSampleCryptoInfo(android.media.MediaCodec.CryptoInfo); method public int getSampleFlags(); @@ -24422,6 +24431,69 @@ package android.media { field public static final int OPTION_PREVIOUS_SYNC = 0; // 0x0 } + public final class MediaMetricsSet { + method public double getDouble(java.lang.String, double); + method public int getInt(java.lang.String, int); + method public long getLong(java.lang.String, long); + method public java.lang.String getString(java.lang.String, java.lang.String); + method public boolean isEmpty(); + method public java.util.Set<java.lang.String> keySet(); + method public int size(); + } + + public static final class MediaMetricsSet.MediaCodec { + field public static final java.lang.String KEY_CODEC = "android.media.mediacodec.codec"; + field public static final java.lang.String KEY_ENCODER = "android.media.mediacodec.encoder"; + field public static final java.lang.String KEY_HEIGHT = "android.media.mediacodec.height"; + field public static final java.lang.String KEY_MIME = "android.media.mediacodec.mime"; + field public static final java.lang.String KEY_MODE = "android.media.mediacodec.mode"; + field public static final java.lang.String KEY_ROTATION = "android.media.mediacodec.rotation"; + field public static final java.lang.String KEY_SECURE = "android.media.mediacodec.secure"; + field public static final java.lang.String KEY_WIDTH = "android.media.mediacodec.width"; + field public static final java.lang.String MODE_AUDIO = "audio"; + field public static final java.lang.String MODE_VIDEO = "video"; + } + + public static final class MediaMetricsSet.MediaExtractor { + field public static final java.lang.String KEY_FORMAT = "android.media.mediaextractor.fmt"; + field public static final java.lang.String KEY_MIME = "android.media.mediaextractor.mime"; + field public static final java.lang.String KEY_TRACKS = "android.media.mediaextractor.ntrk"; + } + + public static final class MediaMetricsSet.MediaPlayer { + field public static final java.lang.String KEY_CODEC_AUDIO = "android.media.mediaplayer.audio.codec"; + field public static final java.lang.String KEY_CODEC_VIDEO = "android.media.mediaplayer.video.codec"; + field public static final java.lang.String KEY_DURATION = "android.media.mediaplayer.durationMs"; + field public static final java.lang.String KEY_ERRORS = "android.media.mediaplayer.err"; + field public static final java.lang.String KEY_ERROR_CODE = "android.media.mediaplayer.errcode"; + field public static final java.lang.String KEY_FRAMES = "android.media.mediaplayer.frames"; + field public static final java.lang.String KEY_FRAMES_DROPPED = "android.media.mediaplayer.dropped"; + field public static final java.lang.String KEY_HEIGHT = "android.media.mediaplayer.height"; + field public static final java.lang.String KEY_MIME_AUDIO = "android.media.mediaplayer.audio.mime"; + field public static final java.lang.String KEY_MIME_VIDEO = "android.media.mediaplayer.video.mime"; + field public static final java.lang.String KEY_PLAYING = "android.media.mediaplayer.playingMs"; + field public static final java.lang.String KEY_WIDTH = "android.media.mediaplayer.width"; + } + + public static final class MediaMetricsSet.MediaRecorder { + field public static final java.lang.String KEY_AUDIO_BITRATE = "android.media.mediarecorder.audio-bitrate"; + field public static final java.lang.String KEY_AUDIO_CHANNELS = "android.media.mediarecorder.audio-channels"; + field public static final java.lang.String KEY_AUDIO_SAMPLERATE = "android.media.mediarecorder.audio-samplerate"; + field public static final java.lang.String KEY_AUDIO_TIMESCALE = "android.media.mediarecorder.audio-timescale"; + field public static final java.lang.String KEY_CAPTURE_FPS = "android.media.mediarecorder.capture-fps"; + field public static final java.lang.String KEY_CAPTURE_FPS_ENABLE = "android.media.mediarecorder.capture-fpsenable"; + field public static final java.lang.String KEY_FRAMERATE = "android.media.mediarecorder.frame-rate"; + field public static final java.lang.String KEY_HEIGHT = "android.media.mediarecorder.height"; + field public static final java.lang.String KEY_MOVIE_TIMESCALE = "android.media.mediarecorder.movie-timescale"; + field public static final java.lang.String KEY_ROTATION = "android.media.mediarecorder.rotation"; + field public static final java.lang.String KEY_VIDEO_BITRATE = "android.media.mediarecorder.video-bitrate"; + field public static final java.lang.String KEY_VIDEO_IFRAME_INTERVAL = "android.media.mediarecorder.video-iframe-interval"; + field public static final java.lang.String KEY_VIDEO_LEVEL = "android.media.mediarecorder.video-encoder-level"; + field public static final java.lang.String KEY_VIDEO_PROFILE = "android.media.mediarecorder.video-encoder-profile"; + field public static final java.lang.String KEY_VIDEO_TIMESCALE = "android.media.mediarecorder.video-timescale"; + field public static final java.lang.String KEY_WIDTH = "android.media.mediarecorder.width"; + } + public final class MediaMuxer { ctor public MediaMuxer(java.lang.String, int) throws java.io.IOException; ctor public MediaMuxer(java.io.FileDescriptor, int) throws java.io.IOException; @@ -24462,7 +24534,7 @@ package android.media { method public java.lang.String getDrmPropertyString(java.lang.String) throws android.media.MediaPlayer.NoDrmSchemeException; method public int getDuration(); method public android.media.MediaDrm.KeyRequest getKeyRequest(byte[], java.lang.String, int, java.util.Map<java.lang.String, java.lang.String>) throws android.media.MediaPlayer.NoDrmSchemeException; - method public android.os.Bundle getMetrics(); + method public android.media.MediaMetricsSet getMetrics(); method public android.media.PlaybackParams getPlaybackParams(); method public int getSelectedTrack(int) throws java.lang.IllegalStateException; method public android.media.SyncParams getSyncParams(); @@ -24630,7 +24702,7 @@ package android.media { ctor public MediaRecorder(); method public static final int getAudioSourceMax(); method public int getMaxAmplitude() throws java.lang.IllegalStateException; - method public android.os.Bundle getMetrics(); + method public android.media.MediaMetricsSet getMetrics(); method public android.view.Surface getSurface(); method public void pause() throws java.lang.IllegalStateException; method public void prepare() throws java.io.IOException, java.lang.IllegalStateException; @@ -26610,11 +26682,15 @@ package android.media.tv { method public int describeContents(); method public java.lang.String getAudioAddress(); method public int getAudioType(); + method public int getCableConnectionStatus(); method public int getDeviceId(); method public int getHdmiPortId(); method public int getType(); method public void readFromParcel(android.os.Parcel); method public void writeToParcel(android.os.Parcel, int); + field public static final int CABLE_CONNECTION_STATUS_CONNECTED = 1; // 0x1 + field public static final int CABLE_CONNECTION_STATUS_DISCONNECTED = 2; // 0x2 + field public static final int CABLE_CONNECTION_STATUS_UNKNOWN = 0; // 0x0 field public static final android.os.Parcelable.Creator<android.media.tv.TvInputHardwareInfo> CREATOR; field public static final int TV_INPUT_TYPE_COMPONENT = 6; // 0x6 field public static final int TV_INPUT_TYPE_COMPOSITE = 3; // 0x3 @@ -26633,6 +26709,7 @@ package android.media.tv { method public android.media.tv.TvInputHardwareInfo.Builder audioAddress(java.lang.String); method public android.media.tv.TvInputHardwareInfo.Builder audioType(int); method public android.media.tv.TvInputHardwareInfo build(); + method public android.media.tv.TvInputHardwareInfo.Builder cableConnectionStatus(int); method public android.media.tv.TvInputHardwareInfo.Builder deviceId(int); method public android.media.tv.TvInputHardwareInfo.Builder hdmiPortId(int); method public android.media.tv.TvInputHardwareInfo.Builder type(int); @@ -26640,7 +26717,7 @@ package android.media.tv { public final class TvInputInfo implements android.os.Parcelable { method public boolean canRecord(); - method public android.content.Intent createSettingsIntent(); + method public deprecated android.content.Intent createSettingsIntent(); method public android.content.Intent createSetupIntent(); method public static deprecated android.media.tv.TvInputInfo createTvInputInfo(android.content.Context, android.content.pm.ResolveInfo, android.hardware.hdmi.HdmiDeviceInfo, java.lang.String, java.lang.String, android.net.Uri) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException; method public static deprecated android.media.tv.TvInputInfo createTvInputInfo(android.content.Context, android.content.pm.ResolveInfo, android.hardware.hdmi.HdmiDeviceInfo, java.lang.String, int, android.graphics.drawable.Icon) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException; @@ -27306,8 +27383,8 @@ package android.net { method public void reportNetworkConnectivity(android.net.Network, boolean); method public boolean requestBandwidthUpdate(android.net.Network); method public void requestNetwork(android.net.NetworkRequest, android.net.ConnectivityManager.NetworkCallback); - method public void requestNetwork(android.net.NetworkRequest, int, android.net.ConnectivityManager.NetworkCallback); method public void requestNetwork(android.net.NetworkRequest, android.net.ConnectivityManager.NetworkCallback, android.os.Handler); + method public void requestNetwork(android.net.NetworkRequest, int, android.net.ConnectivityManager.NetworkCallback); method public void requestNetwork(android.net.NetworkRequest, int, android.net.ConnectivityManager.NetworkCallback, android.os.Handler); method public void requestNetwork(android.net.NetworkRequest, android.app.PendingIntent); method public deprecated void setNetworkPreference(int); @@ -27715,7 +27792,6 @@ package android.net { method public boolean clearScores() throws java.lang.SecurityException; method public void disableScoring() throws java.lang.SecurityException; method public java.lang.String getActiveScorerPackage(); - method public android.net.RecommendationResult requestRecommendation(android.net.RecommendationRequest) throws java.lang.SecurityException; method public boolean setActiveScorer(java.lang.String) throws java.lang.SecurityException; method public boolean updateScores(android.net.ScoredNetwork[]) throws java.lang.SecurityException; field public static final java.lang.String ACTION_CHANGE_ACTIVE = "android.net.scoring.CHANGE_ACTIVE"; @@ -28458,6 +28534,16 @@ package android.net.wifi { field public boolean truncated; } + public final class IconInfo implements android.os.Parcelable { + ctor public IconInfo(java.lang.String, byte[]); + ctor public IconInfo(android.net.wifi.IconInfo); + method public int describeContents(); + method public byte[] getData(); + method public java.lang.String getFilename(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.net.wifi.IconInfo> CREATOR; + } + public class RttManager { method public void disableResponder(android.net.wifi.RttManager.ResponderCallback); method public void enableResponder(android.net.wifi.RttManager.ResponderCallback); @@ -28884,7 +28970,7 @@ package android.net.wifi { public class WifiManager { method public int addNetwork(android.net.wifi.WifiConfiguration); - method public boolean addOrUpdatePasspointConfiguration(android.net.wifi.hotspot2.PasspointConfiguration); + method public void addOrUpdatePasspointConfiguration(android.net.wifi.hotspot2.PasspointConfiguration); method public static int calculateSignalLevel(int, int); method public void cancelWps(android.net.wifi.WifiManager.WpsCallback); method public static int compareSignalLevel(int, int); @@ -28924,7 +29010,7 @@ package android.net.wifi { method public boolean reassociate(); method public boolean reconnect(); method public boolean removeNetwork(int); - method public boolean removePasspointConfiguration(java.lang.String); + method public void removePasspointConfiguration(java.lang.String); method public deprecated boolean saveConfiguration(); method public void setTdlsEnabled(java.net.InetAddress, boolean); method public void setTdlsEnabledWithMacAddress(java.lang.String, boolean); @@ -28947,29 +29033,24 @@ package android.net.wifi { field public static final int CHANGE_REASON_REMOVED = 1; // 0x1 field public static final java.lang.String CONFIGURED_NETWORKS_CHANGED_ACTION = "android.net.wifi.CONFIGURED_NETWORKS_CHANGE"; field public static final int ERROR_AUTHENTICATING = 1; // 0x1 + field public static final java.lang.String EXTRA_ANQP_ELEMENT_DATA = "android.net.wifi.extra.ANQP_ELEMENT_DATA"; field public static final java.lang.String EXTRA_BSSID = "bssid"; + field public static final java.lang.String EXTRA_BSSID_LONG = "android.net.wifi.extra.BSSID_LONG"; field public static final java.lang.String EXTRA_CHANGE_REASON = "changeReason"; + field public static final java.lang.String EXTRA_DELAY = "android.net.wifi.extra.DELAY"; + field public static final java.lang.String EXTRA_ESS = "android.net.wifi.extra.ESS"; + field public static final java.lang.String EXTRA_ICON_INFO = "android.net.wifi.extra.ICON_INFO"; field public static final java.lang.String EXTRA_MULTIPLE_NETWORKS_CHANGED = "multipleChanges"; field public static final java.lang.String EXTRA_NETWORK_INFO = "networkInfo"; field public static final java.lang.String EXTRA_NEW_RSSI = "newRssi"; field public static final java.lang.String EXTRA_NEW_STATE = "newState"; - field public static final java.lang.String EXTRA_PASSPOINT_DEAUTH_IMMINENT_BSSID = "android.net.wifi.extra.PASSPOINT_DEAUTH_IMMINENT_BSSID"; - field public static final java.lang.String EXTRA_PASSPOINT_DEAUTH_IMMINENT_ESS = "android.net.wifi.extra.PASSPOINT_DEAUTH_IMMINENT_ESS"; - field public static final java.lang.String EXTRA_PASSPOINT_DEAUTH_IMMINENT_REASON_URL = "android.net.wifi.extra.PASSPOINT_DEAUTH_IMMINENT_REASON_URL"; - field public static final java.lang.String EXTRA_PASSPOINT_DEAUTH_IMMINENT_REAUTH_DELAY = "android.net.wifi.extra.PASSPOINT_DEAUTH_IMMINENT_REAUTH_DELAY"; - field public static final java.lang.String EXTRA_PASSPOINT_ICON_BSSID = "android.net.wifi.extra.PASSPOINT_ICON_BSSID"; - field public static final java.lang.String EXTRA_PASSPOINT_ICON_DATA = "android.net.wifi.extra.PASSPOINT_ICON_DATA"; - field public static final java.lang.String EXTRA_PASSPOINT_ICON_FILENAME = "android.net.wifi.extra.PASSPOINT_ICON_FILENAME"; - field public static final java.lang.String EXTRA_PASSPOINT_OSU_PROVIDERS_LIST_BSSID = "android.net.wifi.extra.PASSPOINT_OSU_PROVIDERS_LIST_BSSID"; - field public static final java.lang.String EXTRA_PASSPOINT_OSU_PROVIDERS_LIST_DATA = "android.net.wifi.extra.PASSPOINT_OSU_PROVIDERS_LIST_DATA"; - field public static final java.lang.String EXTRA_PASSPOINT_SUBSCRIPTION_REMEDIATION_BSSID = "android.net.wifi.extra.PASSPOINT_SUBSCRIPTION_REMEDIATION_BSSID"; - field public static final java.lang.String EXTRA_PASSPOINT_SUBSCRIPTION_REMEDIATION_SERVER_METHOD = "android.net.wifi.extra.PASSPOINT_SUBSCRIPTION_REMEDIATION_SERVER_METHOD"; - field public static final java.lang.String EXTRA_PASSPOINT_SUBSCRIPTION_REMEDIATION_SERVER_URL = "android.net.wifi.extra.PASSPOINT_SUBSCRIPTION_REMEDIATION_SERVER_URL"; field public static final java.lang.String EXTRA_PREVIOUS_WIFI_AP_STATE = "previous_wifi_state"; field public static final java.lang.String EXTRA_PREVIOUS_WIFI_STATE = "previous_wifi_state"; field public static final java.lang.String EXTRA_RESULTS_UPDATED = "resultsUpdated"; + field public static final java.lang.String EXTRA_SUBSCRIPTION_REMEDIATION_METHOD = "android.net.wifi.extra.SUBSCRIPTION_REMEDIATION_METHOD"; field public static final java.lang.String EXTRA_SUPPLICANT_CONNECTED = "connected"; field public static final java.lang.String EXTRA_SUPPLICANT_ERROR = "supplicantError"; + field public static final java.lang.String EXTRA_URL = "android.net.wifi.extra.URL"; field public static final java.lang.String EXTRA_WIFI_AP_STATE = "wifi_state"; field public static final java.lang.String EXTRA_WIFI_CONFIGURATION = "wifiConfiguration"; field public static final java.lang.String EXTRA_WIFI_CREDENTIAL_EVENT_TYPE = "et"; @@ -29348,7 +29429,6 @@ package android.net.wifi.hotspot2 { method public void setUsageLimitStartTimeInMs(long); method public void setUsageLimitTimeLimitInMinutes(long); method public void setUsageLimitUsageTimePeriodInMinutes(long); - method public boolean validate(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.net.wifi.hotspot2.PasspointConfiguration> CREATOR; } @@ -29389,7 +29469,6 @@ package android.net.wifi.hotspot2.pps { method public void setRealm(java.lang.String); method public void setSimCredential(android.net.wifi.hotspot2.pps.Credential.SimCredential); method public void setUserCredential(android.net.wifi.hotspot2.pps.Credential.UserCredential); - method public boolean validate(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.net.wifi.hotspot2.pps.Credential> CREATOR; } @@ -29402,7 +29481,6 @@ package android.net.wifi.hotspot2.pps { method public java.lang.String getCertType(); method public void setCertSha256Fingerprint(byte[]); method public void setCertType(java.lang.String); - method public boolean validate(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.net.wifi.hotspot2.pps.Credential.CertificateCredential> CREATOR; } @@ -29415,7 +29493,6 @@ package android.net.wifi.hotspot2.pps { method public java.lang.String getImsi(); method public void setEapType(int); method public void setImsi(java.lang.String); - method public boolean validate(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.net.wifi.hotspot2.pps.Credential.SimCredential> CREATOR; } @@ -29438,7 +29515,6 @@ package android.net.wifi.hotspot2.pps { method public void setPassword(java.lang.String); method public void setSoftTokenApp(java.lang.String); method public void setUsername(java.lang.String); - method public boolean validate(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.net.wifi.hotspot2.pps.Credential.UserCredential> CREATOR; } @@ -29463,7 +29539,6 @@ package android.net.wifi.hotspot2.pps { method public void setMatchAnyOis(long[]); method public void setOtherHomePartners(java.lang.String[]); method public void setRoamingConsortiumOis(long[]); - method public boolean validate(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.net.wifi.hotspot2.pps.HomeSp> CREATOR; } @@ -29490,7 +29565,6 @@ package android.net.wifi.hotspot2.pps { method public void setPolicyUpdate(android.net.wifi.hotspot2.pps.UpdateParameter); method public void setPreferredRoamingPartnerList(java.util.List<android.net.wifi.hotspot2.pps.Policy.RoamingPartner>); method public void setRequiredProtoPortMap(java.util.Map<java.lang.Integer, java.lang.String>); - method public boolean validate(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.net.wifi.hotspot2.pps.Policy> CREATOR; } @@ -29507,7 +29581,6 @@ package android.net.wifi.hotspot2.pps { method public void setFqdn(java.lang.String); method public void setFqdnExactMatch(boolean); method public void setPriority(int); - method public boolean validate(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.net.wifi.hotspot2.pps.Policy.RoamingPartner> CREATOR; } @@ -29532,7 +29605,6 @@ package android.net.wifi.hotspot2.pps { method public void setUpdateIntervalInMinutes(long); method public void setUpdateMethod(java.lang.String); method public void setUsername(java.lang.String); - method public boolean validate(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.net.wifi.hotspot2.pps.UpdateParameter> CREATOR; field public static final long UPDATE_CHECK_INTERVAL_NEVER = 4294967295L; // 0xffffffffL @@ -34764,6 +34836,7 @@ package android.preference { method public boolean hasKey(); method public boolean isEnabled(); method public boolean isPersistent(); + method public boolean isRecycleEnabled(); method public boolean isSelectable(); method protected void notifyChanged(); method public void notifyDependencyChange(boolean); @@ -34803,6 +34876,7 @@ package android.preference { method public void setOrder(int); method public void setPersistent(boolean); method public void setPreferenceDataStore(android.preference.PreferenceDataStore); + method public void setRecycleEnabled(boolean); method public void setSelectable(boolean); method public void setShouldDisableView(boolean); method public void setSummary(java.lang.CharSequence); @@ -36748,6 +36822,7 @@ package android.provider { field public static final java.lang.String EXTRA_RECIPIENT_CONTACT_CHAT_ID = "android.provider.extra.RECIPIENT_CONTACT_CHAT_ID"; field public static final java.lang.String EXTRA_RECIPIENT_CONTACT_NAME = "android.provider.extra.RECIPIENT_CONTACT_NAME"; field public static final java.lang.String EXTRA_RECIPIENT_CONTACT_URI = "android.provider.extra.RECIPIENT_CONTACT_URI"; + field public static final java.lang.String EXTRA_SENDER_ACCOUNT_HASH = "android.provider.extra.SENDER_ACCOUNT_HASH"; field public static final java.lang.String INVITE_CONTACT = "com.android.contacts.action.INVITE_CONTACT"; field public static final java.lang.String METADATA_ACCOUNT_TYPE = "android.provider.account_type"; field public static final java.lang.String METADATA_MIMETYPE = "android.provider.mimetype"; @@ -37028,21 +37103,21 @@ package android.provider { method public static android.net.Uri buildRootsUri(java.lang.String); method public static android.net.Uri buildSearchDocumentsUri(java.lang.String, java.lang.String, java.lang.String); method public static android.net.Uri buildTreeDocumentUri(java.lang.String, java.lang.String); - method public static android.net.Uri copyDocument(android.content.ContentResolver, android.net.Uri, android.net.Uri); - method public static android.net.Uri createDocument(android.content.ContentResolver, android.net.Uri, java.lang.String, java.lang.String); - method public static android.content.IntentSender createWebLinkIntent(android.content.ContentResolver, android.net.Uri, android.os.Bundle); - method public static boolean deleteDocument(android.content.ContentResolver, android.net.Uri); - method public static android.provider.DocumentsContract.Path findDocumentPath(android.content.ContentResolver, android.net.Uri); + method public static android.net.Uri copyDocument(android.content.ContentResolver, android.net.Uri, android.net.Uri) throws java.io.FileNotFoundException; + method public static android.net.Uri createDocument(android.content.ContentResolver, android.net.Uri, java.lang.String, java.lang.String) throws java.io.FileNotFoundException; + method public static android.content.IntentSender createWebLinkIntent(android.content.ContentResolver, android.net.Uri, android.os.Bundle) throws java.io.FileNotFoundException; + method public static boolean deleteDocument(android.content.ContentResolver, android.net.Uri) throws java.io.FileNotFoundException; + method public static android.provider.DocumentsContract.Path findDocumentPath(android.content.ContentResolver, android.net.Uri) throws java.io.FileNotFoundException; method public static java.lang.String getDocumentId(android.net.Uri); - method public static android.graphics.Bitmap getDocumentThumbnail(android.content.ContentResolver, android.net.Uri, android.graphics.Point, android.os.CancellationSignal); + method public static android.graphics.Bitmap getDocumentThumbnail(android.content.ContentResolver, android.net.Uri, android.graphics.Point, android.os.CancellationSignal) throws java.io.FileNotFoundException; method public static java.lang.String getRootId(android.net.Uri); method public static java.lang.String getSearchDocumentsQuery(android.net.Uri); method public static java.lang.String getTreeDocumentId(android.net.Uri); method public static boolean isDocumentUri(android.content.Context, android.net.Uri); method public static boolean isTreeUri(android.net.Uri); - method public static android.net.Uri moveDocument(android.content.ContentResolver, android.net.Uri, android.net.Uri, android.net.Uri); - method public static boolean removeDocument(android.content.ContentResolver, android.net.Uri, android.net.Uri); - method public static android.net.Uri renameDocument(android.content.ContentResolver, android.net.Uri, java.lang.String); + method public static android.net.Uri moveDocument(android.content.ContentResolver, android.net.Uri, android.net.Uri, android.net.Uri) throws java.io.FileNotFoundException; + method public static boolean removeDocument(android.content.ContentResolver, android.net.Uri, android.net.Uri) throws java.io.FileNotFoundException; + method public static android.net.Uri renameDocument(android.content.ContentResolver, android.net.Uri, java.lang.String) throws java.io.FileNotFoundException; field public static final java.lang.String ACTION_DOCUMENT_SETTINGS = "android.provider.action.DOCUMENT_SETTINGS"; field public static final java.lang.String EXTRA_ERROR = "error"; field public static final java.lang.String EXTRA_EXCLUDE_SELF = "android.provider.extra.EXCLUDE_SELF"; @@ -37654,6 +37729,7 @@ package android.provider { field public static final java.lang.String ACTION_NFCSHARING_SETTINGS = "android.settings.NFCSHARING_SETTINGS"; field public static final java.lang.String ACTION_NFC_PAYMENT_SETTINGS = "android.settings.NFC_PAYMENT_SETTINGS"; field public static final java.lang.String ACTION_NFC_SETTINGS = "android.settings.NFC_SETTINGS"; + field public static final java.lang.String ACTION_NIGHT_DISPLAY_SETTINGS = "android.settings.NIGHT_DISPLAY_SETTINGS"; field public static final java.lang.String ACTION_NOTIFICATION_LISTENER_SETTINGS = "android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS"; field public static final java.lang.String ACTION_NOTIFICATION_POLICY_ACCESS_SETTINGS = "android.settings.NOTIFICATION_POLICY_ACCESS_SETTINGS"; field public static final java.lang.String ACTION_PRINT_SETTINGS = "android.settings.ACTION_PRINT_SETTINGS"; @@ -39769,7 +39845,7 @@ package android.service.autofill { method public final android.os.IBinder onBind(android.content.Intent); method public void onConnected(); method public void onDisconnected(); - method public abstract void onFillRequest(android.app.assist.AssistStructure, android.os.Bundle, android.os.CancellationSignal, android.service.autofill.FillCallback); + method public void onFillRequest(android.app.assist.AssistStructure, android.os.Bundle, int, android.os.CancellationSignal, android.service.autofill.FillCallback); method public abstract void onSaveRequest(android.app.assist.AssistStructure, android.os.Bundle, android.service.autofill.SaveCallback); field public static final java.lang.String SERVICE_INTERFACE = "android.service.autofill.AutofillService"; field public static final java.lang.String SERVICE_META_DATA = "android.autofill"; @@ -39824,11 +39900,11 @@ package android.service.autofill { } public static final class SaveInfo.Builder { - ctor public SaveInfo.Builder(int); - method public android.service.autofill.SaveInfo.Builder addSavableIds(android.view.autofill.AutofillId...); + ctor public SaveInfo.Builder(int, android.view.autofill.AutofillId[]); method public android.service.autofill.SaveInfo build(); method public android.service.autofill.SaveInfo.Builder setDescription(java.lang.CharSequence); method public android.service.autofill.SaveInfo.Builder setNegativeAction(java.lang.CharSequence, android.content.IntentSender); + method public android.service.autofill.SaveInfo.Builder setOptionalIds(android.view.autofill.AutofillId[]); } } @@ -42532,6 +42608,7 @@ package android.telephony { method public void notifyConfigChangedForSubId(int); method public void updateConfigForPhoneId(int, java.lang.String); field public static final java.lang.String ACTION_CARRIER_CONFIG_CHANGED = "android.telephony.action.CARRIER_CONFIG_CHANGED"; + field public static final int DATA_CYCLE_THRESHOLD_DISABLED = -2; // 0xfffffffe field public static final java.lang.String KEY_ADDITIONAL_CALL_SETTING_BOOL = "additional_call_setting_bool"; field public static final java.lang.String KEY_ALLOW_ADDING_APNS_BOOL = "allow_adding_apns_bool"; field public static final java.lang.String KEY_ALLOW_ADD_CALL_DURING_VIDEO_CALL_BOOL = "allow_add_call_during_video_call"; @@ -42574,6 +42651,8 @@ package android.telephony { field public static final java.lang.String KEY_CI_ACTION_ON_SYS_UPDATE_INTENT_STRING = "ci_action_on_sys_update_intent_string"; field public static final java.lang.String KEY_CONFIG_IMS_PACKAGE_OVERRIDE_STRING = "config_ims_package_override_string"; field public static final java.lang.String KEY_CSP_ENABLED_BOOL = "csp_enabled_bool"; + field public static final java.lang.String KEY_DATA_LIMIT_THRESHOLD_BYTES_LONG = "data_limit_threshold_bytes_long"; + field public static final java.lang.String KEY_DATA_WARNING_THRESHOLD_BYTES_LONG = "data_warning_threshold_bytes_long"; field public static final java.lang.String KEY_DEFAULT_SIM_CALL_MANAGER_STRING = "default_sim_call_manager_string"; field public static final java.lang.String KEY_DEFAULT_VM_NUMBER_STRING = "default_vm_number_string"; field public static final java.lang.String KEY_DIAL_STRING_REPLACE_STRING_ARRAY = "dial_string_replace_string_array"; @@ -42629,6 +42708,7 @@ package android.telephony { field public static final java.lang.String KEY_MMS_UA_PROF_TAG_NAME_STRING = "uaProfTagName"; field public static final java.lang.String KEY_MMS_UA_PROF_URL_STRING = "uaProfUrl"; field public static final java.lang.String KEY_MMS_USER_AGENT_STRING = "userAgent"; + field public static final java.lang.String KEY_MONTHLY_DATA_CYCLE_DAY_INT = "monthly_data_cycle_day_int"; field public static final java.lang.String KEY_ONLY_SINGLE_DC_ALLOWED_INT_ARRAY = "only_single_dc_allowed_int_array"; field public static final java.lang.String KEY_OPERATOR_SELECTION_EXPAND_BOOL = "operator_selection_expand_bool"; field public static final java.lang.String KEY_PREFER_2G_BOOL = "prefer_2g_bool"; @@ -43201,8 +43281,8 @@ package android.telephony { method public int getCurrentPhoneType(); method public int getCurrentPhoneType(int); method public int getDataActivity(); - method public boolean getDataEnabled(); - method public boolean getDataEnabled(int); + method public deprecated boolean getDataEnabled(); + method public deprecated boolean getDataEnabled(int); method public int getDataNetworkType(); method public int getDataState(); method public java.lang.String getDeviceId(); @@ -43249,6 +43329,7 @@ package android.telephony { method public java.lang.String iccTransmitApduLogicalChannel(int, int, int, int, int, int, java.lang.String); method public boolean isConcurrentVoiceAndDataAllowed(); method public boolean isDataConnectivityPossible(); + method public boolean isDataEnabled(); method public boolean isHearingAidCompatibilitySupported(); method public boolean isIdle(); method public boolean isNetworkRoaming(); @@ -43939,7 +44020,9 @@ package android.test.mock { method public android.database.sqlite.SQLiteDatabase openOrCreateDatabase(java.lang.String, int, android.database.sqlite.SQLiteDatabase.CursorFactory, android.database.DatabaseErrorHandler); method public android.graphics.drawable.Drawable peekWallpaper(); method public android.content.Intent registerReceiver(android.content.BroadcastReceiver, android.content.IntentFilter); + method public android.content.Intent registerReceiver(android.content.BroadcastReceiver, android.content.IntentFilter, boolean); method public android.content.Intent registerReceiver(android.content.BroadcastReceiver, android.content.IntentFilter, java.lang.String, android.os.Handler); + method public android.content.Intent registerReceiver(android.content.BroadcastReceiver, android.content.IntentFilter, java.lang.String, android.os.Handler, boolean); method public void removeStickyBroadcast(android.content.Intent); method public void removeStickyBroadcastAsUser(android.content.Intent, android.os.UserHandle); method public void revokeUriPermission(android.net.Uri, int); @@ -44132,6 +44215,7 @@ package android.test.mock { method public boolean setDefaultBrowserPackageNameAsUser(java.lang.String, int); method public void setInstallerPackageName(java.lang.String, java.lang.String); method public boolean setInstantAppCookie(byte[]); + method public void setUpdateAvailable(java.lang.String, boolean); method public boolean updateIntentVerificationStatusAsUser(java.lang.String, int, int); method public void updatePermissionFlags(java.lang.String, java.lang.String, int, int, android.os.UserHandle); method public void verifyIntentFilter(int, int, java.util.List<java.lang.String>); @@ -44337,11 +44421,10 @@ package android.text { } public final class FontConfig implements android.os.Parcelable { - ctor public FontConfig(); - ctor public FontConfig(android.text.FontConfig); + ctor public FontConfig(android.text.FontConfig.Family[], android.text.FontConfig.Alias[]); method public int describeContents(); - method public java.util.List<android.text.FontConfig.Alias> getAliases(); - method public java.util.List<android.text.FontConfig.Family> getFamilies(); + method public android.text.FontConfig.Alias[] getAliases(); + method public android.text.FontConfig.Family[] getFamilies(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.text.FontConfig> CREATOR; } @@ -44366,22 +44449,22 @@ package android.text { } public static final class FontConfig.Family implements android.os.Parcelable { - ctor public FontConfig.Family(java.lang.String, java.util.List<android.text.FontConfig.Font>, java.lang.String, java.lang.String); - ctor public FontConfig.Family(android.text.FontConfig.Family); + ctor public FontConfig.Family(java.lang.String, android.text.FontConfig.Font[], java.lang.String, int); method public int describeContents(); - method public java.util.List<android.text.FontConfig.Font> getFonts(); + method public android.text.FontConfig.Font[] getFonts(); method public java.lang.String getLanguage(); method public java.lang.String getName(); - method public java.lang.String getVariant(); + method public int getVariant(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.text.FontConfig.Family> CREATOR; + field public static final int VARIANT_COMPACT = 1; // 0x1 + field public static final int VARIANT_DEFAULT = 0; // 0x0 + field public static final int VARIANT_ELEGANT = 2; // 0x2 } public static final class FontConfig.Font implements android.os.Parcelable { - ctor public FontConfig.Font(java.lang.String, int, java.util.List<android.text.FontConfig.Axis>, int, boolean); - ctor public FontConfig.Font(android.text.FontConfig.Font); method public int describeContents(); - method public java.util.List<android.text.FontConfig.Axis> getAxes(); + method public android.text.FontConfig.Axis[] getAxes(); method public android.os.ParcelFileDescriptor getFd(); method public java.lang.String getFontName(); method public int getTtcIndex(); @@ -48551,7 +48634,7 @@ package android.view { method public float getAlpha(); method public android.view.animation.Animation getAnimation(); method public android.os.IBinder getApplicationWindowToken(); - method public int getAutofillHint(); + method public java.lang.String[] getAutofillHint(); method public int getAutofillMode(); method public int getAutofillType(); method public android.view.autofill.AutofillValue getAutofillValue(); @@ -48872,7 +48955,7 @@ package android.view { method public void setActivated(boolean); method public void setAlpha(float); method public void setAnimation(android.view.animation.Animation); - method public void setAutofillHint(int); + method public void setAutofillHint(java.lang.String...); method public void setAutofillMode(int); method public void setBackground(android.graphics.drawable.Drawable); method public void setBackgroundColor(int); @@ -49015,20 +49098,19 @@ package android.view { field public static final int ACCESSIBILITY_LIVE_REGION_NONE = 0; // 0x0 field public static final int ACCESSIBILITY_LIVE_REGION_POLITE = 1; // 0x1 field public static final android.util.Property<android.view.View, java.lang.Float> ALPHA; - field public static final int AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_DATE = 512; // 0x200 - field public static final int AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_DAY = 4096; // 0x1000 - field public static final int AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_MONTH = 1024; // 0x400 - field public static final int AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_YEAR = 2048; // 0x800 - field public static final int AUTOFILL_HINT_CREDIT_CARD_NUMBER = 128; // 0x80 - field public static final int AUTOFILL_HINT_CREDIT_CARD_SECURITY_CODE = 256; // 0x100 - field public static final int AUTOFILL_HINT_EMAIL_ADDRESS = 1; // 0x1 - field public static final int AUTOFILL_HINT_NAME = 2; // 0x2 - field public static final int AUTOFILL_HINT_NONE = 0; // 0x0 - field public static final int AUTOFILL_HINT_PASSWORD = 8; // 0x8 - field public static final int AUTOFILL_HINT_PHONE = 16; // 0x10 - field public static final int AUTOFILL_HINT_POSTAL_ADDRESS = 32; // 0x20 - field public static final int AUTOFILL_HINT_POSTAL_CODE = 64; // 0x40 - field public static final int AUTOFILL_HINT_USERNAME = 4; // 0x4 + field public static final java.lang.String AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_DATE = "creditCardExpirationDate"; + field public static final java.lang.String AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_DAY = "creditCardExpirationDay"; + field public static final java.lang.String AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_MONTH = "creditCardExpirationMonth"; + field public static final java.lang.String AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_YEAR = "creditCardExpirationYear"; + field public static final java.lang.String AUTOFILL_HINT_CREDIT_CARD_NUMBER = "creditCardNumber"; + field public static final java.lang.String AUTOFILL_HINT_CREDIT_CARD_SECURITY_CODE = "creditCardSecurityCode"; + field public static final java.lang.String AUTOFILL_HINT_EMAIL_ADDRESS = "emailAddress"; + field public static final java.lang.String AUTOFILL_HINT_NAME = "name"; + field public static final java.lang.String AUTOFILL_HINT_PASSWORD = "password"; + field public static final java.lang.String AUTOFILL_HINT_PHONE = "phone"; + field public static final java.lang.String AUTOFILL_HINT_POSTAL_ADDRESS = "postalAddress"; + field public static final java.lang.String AUTOFILL_HINT_POSTAL_CODE = "postalCode"; + field public static final java.lang.String AUTOFILL_HINT_USERNAME = "username"; field public static final int AUTOFILL_MODE_AUTO = 1; // 0x1 field public static final int AUTOFILL_MODE_INHERIT = 0; // 0x0 field public static final int AUTOFILL_MODE_MANUAL = 2; // 0x2 @@ -49690,7 +49772,7 @@ package android.view { method public abstract void setAccessibilityFocused(boolean); method public abstract void setActivated(boolean); method public abstract void setAlpha(float); - method public abstract void setAutofillHint(int); + method public abstract void setAutofillHint(java.lang.String[]); method public abstract void setAutofillOptions(java.lang.String[]); method public abstract void setAutofillType(int); method public abstract void setAutofillValue(android.view.autofill.AutofillValue); @@ -49701,6 +49783,7 @@ package android.view { method public abstract void setClickable(boolean); method public abstract void setContentDescription(java.lang.CharSequence); method public abstract void setContextClickable(boolean); + method public abstract void setDataIsSensitive(boolean); method public abstract void setDimens(int, int, int, int, int, int); method public abstract void setElevation(float); method public abstract void setEnabled(boolean); @@ -49711,7 +49794,6 @@ package android.view { method public abstract void setInputType(int); method public abstract void setLongClickable(boolean); method public abstract void setOpaque(boolean); - method public abstract void setSanitized(boolean); method public abstract void setSelected(boolean); method public abstract void setText(java.lang.CharSequence); method public abstract void setText(java.lang.CharSequence, int, int); @@ -50957,17 +51039,22 @@ package android.view.autofill { } public final class AutofillManager { + method public void cancel(); + method public void commit(); + method public boolean isEnabled(); + method public void notifyValueChanged(android.view.View); + method public void notifyViewEntered(android.view.View); + method public void notifyViewExited(android.view.View); + method public void notifyVirtualValueChanged(android.view.View, int, android.view.autofill.AutofillValue); + 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 reset(); - method public void startAutofillRequest(android.view.View); - method public void startAutofillRequestOnVirtualView(android.view.View, int, android.graphics.Rect); - method public void stopAutofillRequest(android.view.View); - method public void stopAutofillRequestOnVirtualView(android.view.View, int); + 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); - method public void valueChanged(android.view.View); - method public void virtualValueChanged(android.view.View, int, android.view.autofill.AutofillValue); 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 { @@ -50988,6 +51075,10 @@ package android.view.autofill { method public int getListValue(); method public java.lang.CharSequence getTextValue(); method public boolean getToggleValue(); + method public boolean isDate(); + method public boolean isList(); + method public boolean isText(); + method public boolean isToggle(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.view.autofill.AutofillValue> CREATOR; } diff --git a/api/test-current.txt b/api/test-current.txt index bcd1614b51aa..bb8b4540a9fd 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -751,6 +751,7 @@ package android { field public static final int isModifier = 16843334; // 0x1010246 field public static final int isRepeatable = 16843336; // 0x1010248 field public static final int isScrollContainer = 16843342; // 0x101024e + field public static final int isStatic = 16844125; // 0x101055d field public static final int isSticky = 16843335; // 0x1010247 field public static final int isolatedProcess = 16843689; // 0x10103a9 field public static final int isolatedSplits = 16844109; // 0x101054d @@ -1052,6 +1053,7 @@ package android { field public static final int ratingBarStyleSmall = 16842877; // 0x101007d field public static final int readPermission = 16842759; // 0x1010007 field public static final int recognitionService = 16843932; // 0x101049c + field public static final int recycleEnabled = 16844124; // 0x101055c field public static final int relinquishTaskIdentity = 16843894; // 0x1010476 field public static final int reparent = 16843964; // 0x10104bc field public static final int reparentWithOverlay = 16843965; // 0x10104bd @@ -1788,6 +1790,7 @@ package android { field public static final int accessibilityActionSetProgress = 16908349; // 0x102003d field public static final int accessibilityActionShowOnScreen = 16908342; // 0x1020036 field public static final int addToDictionary = 16908330; // 0x102002a + field public static final int autofill = 16908355; // 0x1020043 field public static final int background = 16908288; // 0x1020000 field public static final int button1 = 16908313; // 0x1020019 field public static final int button2 = 16908314; // 0x102001a @@ -6592,7 +6595,7 @@ package android.app.assist { public static class AssistStructure.ViewNode { method public float getAlpha(); - method public int getAutoFillHint(); + method public java.lang.String[] getAutoFillHint(); method public android.view.autofill.AutofillId getAutofillId(); method public java.lang.String[] getAutofillOptions(); method public int getAutofillType(); @@ -8851,7 +8854,9 @@ package android.content { method public abstract deprecated android.graphics.drawable.Drawable peekWallpaper(); method public void registerComponentCallbacks(android.content.ComponentCallbacks); method public abstract android.content.Intent registerReceiver(android.content.BroadcastReceiver, android.content.IntentFilter); + method public abstract android.content.Intent registerReceiver(android.content.BroadcastReceiver, android.content.IntentFilter, boolean); method public abstract android.content.Intent registerReceiver(android.content.BroadcastReceiver, android.content.IntentFilter, java.lang.String, android.os.Handler); + method public abstract android.content.Intent registerReceiver(android.content.BroadcastReceiver, android.content.IntentFilter, java.lang.String, android.os.Handler, boolean); method public abstract deprecated void removeStickyBroadcast(android.content.Intent); method public abstract deprecated void removeStickyBroadcastAsUser(android.content.Intent, android.os.UserHandle); method public abstract void revokeUriPermission(android.net.Uri, int); @@ -9041,7 +9046,9 @@ package android.content { method public android.database.sqlite.SQLiteDatabase openOrCreateDatabase(java.lang.String, int, android.database.sqlite.SQLiteDatabase.CursorFactory, android.database.DatabaseErrorHandler); method public deprecated android.graphics.drawable.Drawable peekWallpaper(); method public android.content.Intent registerReceiver(android.content.BroadcastReceiver, android.content.IntentFilter); + method public android.content.Intent registerReceiver(android.content.BroadcastReceiver, android.content.IntentFilter, boolean); method public android.content.Intent registerReceiver(android.content.BroadcastReceiver, android.content.IntentFilter, java.lang.String, android.os.Handler); + method public android.content.Intent registerReceiver(android.content.BroadcastReceiver, android.content.IntentFilter, java.lang.String, android.os.Handler, boolean); method public deprecated void removeStickyBroadcast(android.content.Intent); method public deprecated void removeStickyBroadcastAsUser(android.content.Intent, android.os.UserHandle); method public void revokeUriPermission(android.net.Uri, int); @@ -9544,6 +9551,7 @@ package android.content { field public static final int FLAG_RECEIVER_NO_ABORT = 134217728; // 0x8000000 field public static final int FLAG_RECEIVER_REGISTERED_ONLY = 1073741824; // 0x40000000 field public static final int FLAG_RECEIVER_REPLACE_PENDING = 536870912; // 0x20000000 + field public static final int FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS = 2097152; // 0x200000 field public static final java.lang.String METADATA_DOCK_HOME = "android.dock_home"; field public static final int URI_ALLOW_UNSAFE = 4; // 0x4 field public static final int URI_ANDROID_APP_SCHEME = 2; // 0x2 @@ -21921,7 +21929,7 @@ package android.media { method public deprecated java.nio.ByteBuffer[] getInputBuffers(); method public final android.media.MediaFormat getInputFormat(); method public android.media.Image getInputImage(int); - method public android.os.Bundle getMetrics(); + method public android.media.MediaMetricsSet getMetrics(); method public final java.lang.String getName(); method public java.nio.ByteBuffer getOutputBuffer(int); method public deprecated java.nio.ByteBuffer[] getOutputBuffers(); @@ -22473,7 +22481,7 @@ package android.media { method public boolean advance(); method public long getCachedDuration(); method public android.media.DrmInitData getDrmInitData(); - method public android.os.Bundle getMetrics(); + method public android.media.MediaMetricsSet getMetrics(); method public java.util.Map<java.util.UUID, byte[]> getPsshInfo(); method public boolean getSampleCryptoInfo(android.media.MediaCodec.CryptoInfo); method public int getSampleFlags(); @@ -22728,6 +22736,69 @@ package android.media { field public static final int OPTION_PREVIOUS_SYNC = 0; // 0x0 } + public final class MediaMetricsSet { + method public double getDouble(java.lang.String, double); + method public int getInt(java.lang.String, int); + method public long getLong(java.lang.String, long); + method public java.lang.String getString(java.lang.String, java.lang.String); + method public boolean isEmpty(); + method public java.util.Set<java.lang.String> keySet(); + method public int size(); + } + + public static final class MediaMetricsSet.MediaCodec { + field public static final java.lang.String KEY_CODEC = "android.media.mediacodec.codec"; + field public static final java.lang.String KEY_ENCODER = "android.media.mediacodec.encoder"; + field public static final java.lang.String KEY_HEIGHT = "android.media.mediacodec.height"; + field public static final java.lang.String KEY_MIME = "android.media.mediacodec.mime"; + field public static final java.lang.String KEY_MODE = "android.media.mediacodec.mode"; + field public static final java.lang.String KEY_ROTATION = "android.media.mediacodec.rotation"; + field public static final java.lang.String KEY_SECURE = "android.media.mediacodec.secure"; + field public static final java.lang.String KEY_WIDTH = "android.media.mediacodec.width"; + field public static final java.lang.String MODE_AUDIO = "audio"; + field public static final java.lang.String MODE_VIDEO = "video"; + } + + public static final class MediaMetricsSet.MediaExtractor { + field public static final java.lang.String KEY_FORMAT = "android.media.mediaextractor.fmt"; + field public static final java.lang.String KEY_MIME = "android.media.mediaextractor.mime"; + field public static final java.lang.String KEY_TRACKS = "android.media.mediaextractor.ntrk"; + } + + public static final class MediaMetricsSet.MediaPlayer { + field public static final java.lang.String KEY_CODEC_AUDIO = "android.media.mediaplayer.audio.codec"; + field public static final java.lang.String KEY_CODEC_VIDEO = "android.media.mediaplayer.video.codec"; + field public static final java.lang.String KEY_DURATION = "android.media.mediaplayer.durationMs"; + field public static final java.lang.String KEY_ERRORS = "android.media.mediaplayer.err"; + field public static final java.lang.String KEY_ERROR_CODE = "android.media.mediaplayer.errcode"; + field public static final java.lang.String KEY_FRAMES = "android.media.mediaplayer.frames"; + field public static final java.lang.String KEY_FRAMES_DROPPED = "android.media.mediaplayer.dropped"; + field public static final java.lang.String KEY_HEIGHT = "android.media.mediaplayer.height"; + field public static final java.lang.String KEY_MIME_AUDIO = "android.media.mediaplayer.audio.mime"; + field public static final java.lang.String KEY_MIME_VIDEO = "android.media.mediaplayer.video.mime"; + field public static final java.lang.String KEY_PLAYING = "android.media.mediaplayer.playingMs"; + field public static final java.lang.String KEY_WIDTH = "android.media.mediaplayer.width"; + } + + public static final class MediaMetricsSet.MediaRecorder { + field public static final java.lang.String KEY_AUDIO_BITRATE = "android.media.mediarecorder.audio-bitrate"; + field public static final java.lang.String KEY_AUDIO_CHANNELS = "android.media.mediarecorder.audio-channels"; + field public static final java.lang.String KEY_AUDIO_SAMPLERATE = "android.media.mediarecorder.audio-samplerate"; + field public static final java.lang.String KEY_AUDIO_TIMESCALE = "android.media.mediarecorder.audio-timescale"; + field public static final java.lang.String KEY_CAPTURE_FPS = "android.media.mediarecorder.capture-fps"; + field public static final java.lang.String KEY_CAPTURE_FPS_ENABLE = "android.media.mediarecorder.capture-fpsenable"; + field public static final java.lang.String KEY_FRAMERATE = "android.media.mediarecorder.frame-rate"; + field public static final java.lang.String KEY_HEIGHT = "android.media.mediarecorder.height"; + field public static final java.lang.String KEY_MOVIE_TIMESCALE = "android.media.mediarecorder.movie-timescale"; + field public static final java.lang.String KEY_ROTATION = "android.media.mediarecorder.rotation"; + field public static final java.lang.String KEY_VIDEO_BITRATE = "android.media.mediarecorder.video-bitrate"; + field public static final java.lang.String KEY_VIDEO_IFRAME_INTERVAL = "android.media.mediarecorder.video-iframe-interval"; + field public static final java.lang.String KEY_VIDEO_LEVEL = "android.media.mediarecorder.video-encoder-level"; + field public static final java.lang.String KEY_VIDEO_PROFILE = "android.media.mediarecorder.video-encoder-profile"; + field public static final java.lang.String KEY_VIDEO_TIMESCALE = "android.media.mediarecorder.video-timescale"; + field public static final java.lang.String KEY_WIDTH = "android.media.mediarecorder.width"; + } + public final class MediaMuxer { ctor public MediaMuxer(java.lang.String, int) throws java.io.IOException; ctor public MediaMuxer(java.io.FileDescriptor, int) throws java.io.IOException; @@ -22768,7 +22839,7 @@ package android.media { method public java.lang.String getDrmPropertyString(java.lang.String) throws android.media.MediaPlayer.NoDrmSchemeException; method public int getDuration(); method public android.media.MediaDrm.KeyRequest getKeyRequest(byte[], java.lang.String, int, java.util.Map<java.lang.String, java.lang.String>) throws android.media.MediaPlayer.NoDrmSchemeException; - method public android.os.Bundle getMetrics(); + method public android.media.MediaMetricsSet getMetrics(); method public android.media.PlaybackParams getPlaybackParams(); method public int getSelectedTrack(int) throws java.lang.IllegalStateException; method public android.media.SyncParams getSyncParams(); @@ -22936,7 +23007,7 @@ package android.media { ctor public MediaRecorder(); method public static final int getAudioSourceMax(); method public int getMaxAmplitude() throws java.lang.IllegalStateException; - method public android.os.Bundle getMetrics(); + method public android.media.MediaMetricsSet getMetrics(); method public android.view.Surface getSurface(); method public void pause() throws java.lang.IllegalStateException; method public void prepare() throws java.io.IOException, java.lang.IllegalStateException; @@ -24747,7 +24818,7 @@ package android.media.tv { public final class TvInputInfo implements android.os.Parcelable { method public boolean canRecord(); - method public android.content.Intent createSettingsIntent(); + method public deprecated android.content.Intent createSettingsIntent(); method public android.content.Intent createSetupIntent(); method public int describeContents(); method public android.os.Bundle getExtras(); @@ -25288,8 +25359,8 @@ package android.net { method public void reportNetworkConnectivity(android.net.Network, boolean); method public boolean requestBandwidthUpdate(android.net.Network); method public void requestNetwork(android.net.NetworkRequest, android.net.ConnectivityManager.NetworkCallback); - method public void requestNetwork(android.net.NetworkRequest, int, android.net.ConnectivityManager.NetworkCallback); method public void requestNetwork(android.net.NetworkRequest, android.net.ConnectivityManager.NetworkCallback, android.os.Handler); + method public void requestNetwork(android.net.NetworkRequest, int, android.net.ConnectivityManager.NetworkCallback); method public void requestNetwork(android.net.NetworkRequest, int, android.net.ConnectivityManager.NetworkCallback, android.os.Handler); method public void requestNetwork(android.net.NetworkRequest, android.app.PendingIntent); method public deprecated void setNetworkPreference(int); @@ -26270,6 +26341,16 @@ package android.net.sip { package android.net.wifi { + public final class IconInfo implements android.os.Parcelable { + ctor public IconInfo(java.lang.String, byte[]); + ctor public IconInfo(android.net.wifi.IconInfo); + method public int describeContents(); + method public byte[] getData(); + method public java.lang.String getFilename(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.net.wifi.IconInfo> CREATOR; + } + public class ScanResult implements android.os.Parcelable { method public int describeContents(); method public boolean is80211mcResponder(); @@ -26469,7 +26550,7 @@ package android.net.wifi { public class WifiManager { method public int addNetwork(android.net.wifi.WifiConfiguration); - method public boolean addOrUpdatePasspointConfiguration(android.net.wifi.hotspot2.PasspointConfiguration); + method public void addOrUpdatePasspointConfiguration(android.net.wifi.hotspot2.PasspointConfiguration); method public static int calculateSignalLevel(int, int); method public void cancelWps(android.net.wifi.WifiManager.WpsCallback); method public static int compareSignalLevel(int, int); @@ -26498,7 +26579,7 @@ package android.net.wifi { method public boolean reassociate(); method public boolean reconnect(); method public boolean removeNetwork(int); - method public boolean removePasspointConfiguration(java.lang.String); + method public void removePasspointConfiguration(java.lang.String); method public deprecated boolean saveConfiguration(); method public void setTdlsEnabled(java.net.InetAddress, boolean); method public void setTdlsEnabledWithMacAddress(java.lang.String, boolean); @@ -26513,26 +26594,21 @@ package android.net.wifi { field public static final java.lang.String ACTION_PICK_WIFI_NETWORK = "android.net.wifi.PICK_WIFI_NETWORK"; field public static final java.lang.String ACTION_REQUEST_SCAN_ALWAYS_AVAILABLE = "android.net.wifi.action.REQUEST_SCAN_ALWAYS_AVAILABLE"; field public static final int ERROR_AUTHENTICATING = 1; // 0x1 + field public static final java.lang.String EXTRA_ANQP_ELEMENT_DATA = "android.net.wifi.extra.ANQP_ELEMENT_DATA"; field public static final java.lang.String EXTRA_BSSID = "bssid"; + field public static final java.lang.String EXTRA_BSSID_LONG = "android.net.wifi.extra.BSSID_LONG"; + field public static final java.lang.String EXTRA_DELAY = "android.net.wifi.extra.DELAY"; + field public static final java.lang.String EXTRA_ESS = "android.net.wifi.extra.ESS"; + field public static final java.lang.String EXTRA_ICON_INFO = "android.net.wifi.extra.ICON_INFO"; field public static final java.lang.String EXTRA_NETWORK_INFO = "networkInfo"; field public static final java.lang.String EXTRA_NEW_RSSI = "newRssi"; field public static final java.lang.String EXTRA_NEW_STATE = "newState"; - field public static final java.lang.String EXTRA_PASSPOINT_DEAUTH_IMMINENT_BSSID = "android.net.wifi.extra.PASSPOINT_DEAUTH_IMMINENT_BSSID"; - field public static final java.lang.String EXTRA_PASSPOINT_DEAUTH_IMMINENT_ESS = "android.net.wifi.extra.PASSPOINT_DEAUTH_IMMINENT_ESS"; - field public static final java.lang.String EXTRA_PASSPOINT_DEAUTH_IMMINENT_REASON_URL = "android.net.wifi.extra.PASSPOINT_DEAUTH_IMMINENT_REASON_URL"; - field public static final java.lang.String EXTRA_PASSPOINT_DEAUTH_IMMINENT_REAUTH_DELAY = "android.net.wifi.extra.PASSPOINT_DEAUTH_IMMINENT_REAUTH_DELAY"; - field public static final java.lang.String EXTRA_PASSPOINT_ICON_BSSID = "android.net.wifi.extra.PASSPOINT_ICON_BSSID"; - field public static final java.lang.String EXTRA_PASSPOINT_ICON_DATA = "android.net.wifi.extra.PASSPOINT_ICON_DATA"; - field public static final java.lang.String EXTRA_PASSPOINT_ICON_FILENAME = "android.net.wifi.extra.PASSPOINT_ICON_FILENAME"; - field public static final java.lang.String EXTRA_PASSPOINT_OSU_PROVIDERS_LIST_BSSID = "android.net.wifi.extra.PASSPOINT_OSU_PROVIDERS_LIST_BSSID"; - field public static final java.lang.String EXTRA_PASSPOINT_OSU_PROVIDERS_LIST_DATA = "android.net.wifi.extra.PASSPOINT_OSU_PROVIDERS_LIST_DATA"; - field public static final java.lang.String EXTRA_PASSPOINT_SUBSCRIPTION_REMEDIATION_BSSID = "android.net.wifi.extra.PASSPOINT_SUBSCRIPTION_REMEDIATION_BSSID"; - field public static final java.lang.String EXTRA_PASSPOINT_SUBSCRIPTION_REMEDIATION_SERVER_METHOD = "android.net.wifi.extra.PASSPOINT_SUBSCRIPTION_REMEDIATION_SERVER_METHOD"; - field public static final java.lang.String EXTRA_PASSPOINT_SUBSCRIPTION_REMEDIATION_SERVER_URL = "android.net.wifi.extra.PASSPOINT_SUBSCRIPTION_REMEDIATION_SERVER_URL"; field public static final java.lang.String EXTRA_PREVIOUS_WIFI_STATE = "previous_wifi_state"; field public static final java.lang.String EXTRA_RESULTS_UPDATED = "resultsUpdated"; + field public static final java.lang.String EXTRA_SUBSCRIPTION_REMEDIATION_METHOD = "android.net.wifi.extra.SUBSCRIPTION_REMEDIATION_METHOD"; field public static final java.lang.String EXTRA_SUPPLICANT_CONNECTED = "connected"; field public static final java.lang.String EXTRA_SUPPLICANT_ERROR = "supplicantError"; + field public static final java.lang.String EXTRA_URL = "android.net.wifi.extra.URL"; field public static final java.lang.String EXTRA_WIFI_INFO = "wifiInfo"; field public static final java.lang.String EXTRA_WIFI_STATE = "wifi_state"; field public static final java.lang.String NETWORK_IDS_CHANGED_ACTION = "android.net.wifi.NETWORK_IDS_CHANGED"; @@ -26751,7 +26827,6 @@ package android.net.wifi.hotspot2 { method public void setUsageLimitStartTimeInMs(long); method public void setUsageLimitTimeLimitInMinutes(long); method public void setUsageLimitUsageTimePeriodInMinutes(long); - method public boolean validate(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.net.wifi.hotspot2.PasspointConfiguration> CREATOR; } @@ -26792,7 +26867,6 @@ package android.net.wifi.hotspot2.pps { method public void setRealm(java.lang.String); method public void setSimCredential(android.net.wifi.hotspot2.pps.Credential.SimCredential); method public void setUserCredential(android.net.wifi.hotspot2.pps.Credential.UserCredential); - method public boolean validate(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.net.wifi.hotspot2.pps.Credential> CREATOR; } @@ -26805,7 +26879,6 @@ package android.net.wifi.hotspot2.pps { method public java.lang.String getCertType(); method public void setCertSha256Fingerprint(byte[]); method public void setCertType(java.lang.String); - method public boolean validate(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.net.wifi.hotspot2.pps.Credential.CertificateCredential> CREATOR; } @@ -26818,7 +26891,6 @@ package android.net.wifi.hotspot2.pps { method public java.lang.String getImsi(); method public void setEapType(int); method public void setImsi(java.lang.String); - method public boolean validate(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.net.wifi.hotspot2.pps.Credential.SimCredential> CREATOR; } @@ -26841,7 +26913,6 @@ package android.net.wifi.hotspot2.pps { method public void setPassword(java.lang.String); method public void setSoftTokenApp(java.lang.String); method public void setUsername(java.lang.String); - method public boolean validate(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.net.wifi.hotspot2.pps.Credential.UserCredential> CREATOR; } @@ -26866,7 +26937,6 @@ package android.net.wifi.hotspot2.pps { method public void setMatchAnyOis(long[]); method public void setOtherHomePartners(java.lang.String[]); method public void setRoamingConsortiumOis(long[]); - method public boolean validate(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.net.wifi.hotspot2.pps.HomeSp> CREATOR; } @@ -26893,7 +26963,6 @@ package android.net.wifi.hotspot2.pps { method public void setPolicyUpdate(android.net.wifi.hotspot2.pps.UpdateParameter); method public void setPreferredRoamingPartnerList(java.util.List<android.net.wifi.hotspot2.pps.Policy.RoamingPartner>); method public void setRequiredProtoPortMap(java.util.Map<java.lang.Integer, java.lang.String>); - method public boolean validate(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.net.wifi.hotspot2.pps.Policy> CREATOR; } @@ -26910,7 +26979,6 @@ package android.net.wifi.hotspot2.pps { method public void setFqdn(java.lang.String); method public void setFqdnExactMatch(boolean); method public void setPriority(int); - method public boolean validate(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.net.wifi.hotspot2.pps.Policy.RoamingPartner> CREATOR; } @@ -26935,7 +27003,6 @@ package android.net.wifi.hotspot2.pps { method public void setUpdateIntervalInMinutes(long); method public void setUpdateMethod(java.lang.String); method public void setUsername(java.lang.String); - method public boolean validate(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.net.wifi.hotspot2.pps.UpdateParameter> CREATOR; field public static final long UPDATE_CHECK_INTERVAL_NEVER = 4294967295L; // 0xffffffffL @@ -32039,6 +32106,7 @@ package android.preference { method public boolean hasKey(); method public boolean isEnabled(); method public boolean isPersistent(); + method public boolean isRecycleEnabled(); method public boolean isSelectable(); method protected void notifyChanged(); method public void notifyDependencyChange(boolean); @@ -32078,6 +32146,7 @@ package android.preference { method public void setOrder(int); method public void setPersistent(boolean); method public void setPreferenceDataStore(android.preference.PreferenceDataStore); + method public void setRecycleEnabled(boolean); method public void setSelectable(boolean); method public void setShouldDisableView(boolean); method public void setSummary(java.lang.CharSequence); @@ -34000,6 +34069,7 @@ package android.provider { field public static final java.lang.String EXTRA_RECIPIENT_CONTACT_CHAT_ID = "android.provider.extra.RECIPIENT_CONTACT_CHAT_ID"; field public static final java.lang.String EXTRA_RECIPIENT_CONTACT_NAME = "android.provider.extra.RECIPIENT_CONTACT_NAME"; field public static final java.lang.String EXTRA_RECIPIENT_CONTACT_URI = "android.provider.extra.RECIPIENT_CONTACT_URI"; + field public static final java.lang.String EXTRA_SENDER_ACCOUNT_HASH = "android.provider.extra.SENDER_ACCOUNT_HASH"; field public static final java.lang.String INVITE_CONTACT = "com.android.contacts.action.INVITE_CONTACT"; field public static final java.lang.String METADATA_ACCOUNT_TYPE = "android.provider.account_type"; field public static final java.lang.String METADATA_MIMETYPE = "android.provider.mimetype"; @@ -34250,21 +34320,21 @@ package android.provider { method public static android.net.Uri buildRootsUri(java.lang.String); method public static android.net.Uri buildSearchDocumentsUri(java.lang.String, java.lang.String, java.lang.String); method public static android.net.Uri buildTreeDocumentUri(java.lang.String, java.lang.String); - method public static android.net.Uri copyDocument(android.content.ContentResolver, android.net.Uri, android.net.Uri); - method public static android.net.Uri createDocument(android.content.ContentResolver, android.net.Uri, java.lang.String, java.lang.String); - method public static android.content.IntentSender createWebLinkIntent(android.content.ContentResolver, android.net.Uri, android.os.Bundle); - method public static boolean deleteDocument(android.content.ContentResolver, android.net.Uri); - method public static android.provider.DocumentsContract.Path findDocumentPath(android.content.ContentResolver, android.net.Uri); + method public static android.net.Uri copyDocument(android.content.ContentResolver, android.net.Uri, android.net.Uri) throws java.io.FileNotFoundException; + method public static android.net.Uri createDocument(android.content.ContentResolver, android.net.Uri, java.lang.String, java.lang.String) throws java.io.FileNotFoundException; + method public static android.content.IntentSender createWebLinkIntent(android.content.ContentResolver, android.net.Uri, android.os.Bundle) throws java.io.FileNotFoundException; + method public static boolean deleteDocument(android.content.ContentResolver, android.net.Uri) throws java.io.FileNotFoundException; + method public static android.provider.DocumentsContract.Path findDocumentPath(android.content.ContentResolver, android.net.Uri) throws java.io.FileNotFoundException; method public static java.lang.String getDocumentId(android.net.Uri); - method public static android.graphics.Bitmap getDocumentThumbnail(android.content.ContentResolver, android.net.Uri, android.graphics.Point, android.os.CancellationSignal); + method public static android.graphics.Bitmap getDocumentThumbnail(android.content.ContentResolver, android.net.Uri, android.graphics.Point, android.os.CancellationSignal) throws java.io.FileNotFoundException; method public static java.lang.String getRootId(android.net.Uri); method public static java.lang.String getSearchDocumentsQuery(android.net.Uri); method public static java.lang.String getTreeDocumentId(android.net.Uri); method public static boolean isDocumentUri(android.content.Context, android.net.Uri); method public static boolean isTreeUri(android.net.Uri); - method public static android.net.Uri moveDocument(android.content.ContentResolver, android.net.Uri, android.net.Uri, android.net.Uri); - method public static boolean removeDocument(android.content.ContentResolver, android.net.Uri, android.net.Uri); - method public static android.net.Uri renameDocument(android.content.ContentResolver, android.net.Uri, java.lang.String); + method public static android.net.Uri moveDocument(android.content.ContentResolver, android.net.Uri, android.net.Uri, android.net.Uri) throws java.io.FileNotFoundException; + method public static boolean removeDocument(android.content.ContentResolver, android.net.Uri, android.net.Uri) throws java.io.FileNotFoundException; + method public static android.net.Uri renameDocument(android.content.ContentResolver, android.net.Uri, java.lang.String) throws java.io.FileNotFoundException; field public static final java.lang.String ACTION_DOCUMENT_SETTINGS = "android.provider.action.DOCUMENT_SETTINGS"; field public static final java.lang.String EXTRA_ERROR = "error"; field public static final java.lang.String EXTRA_EXCLUDE_SELF = "android.provider.extra.EXCLUDE_SELF"; @@ -34773,6 +34843,7 @@ package android.provider { field public static final java.lang.String ACTION_NFCSHARING_SETTINGS = "android.settings.NFCSHARING_SETTINGS"; field public static final java.lang.String ACTION_NFC_PAYMENT_SETTINGS = "android.settings.NFC_PAYMENT_SETTINGS"; field public static final java.lang.String ACTION_NFC_SETTINGS = "android.settings.NFC_SETTINGS"; + field public static final java.lang.String ACTION_NIGHT_DISPLAY_SETTINGS = "android.settings.NIGHT_DISPLAY_SETTINGS"; field public static final java.lang.String ACTION_NOTIFICATION_LISTENER_SETTINGS = "android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS"; field public static final java.lang.String ACTION_NOTIFICATION_POLICY_ACCESS_SETTINGS = "android.settings.NOTIFICATION_POLICY_ACCESS_SETTINGS"; field public static final java.lang.String ACTION_PRINT_SETTINGS = "android.settings.ACTION_PRINT_SETTINGS"; @@ -36880,7 +36951,7 @@ package android.service.autofill { method public final android.os.IBinder onBind(android.content.Intent); method public void onConnected(); method public void onDisconnected(); - method public abstract void onFillRequest(android.app.assist.AssistStructure, android.os.Bundle, android.os.CancellationSignal, android.service.autofill.FillCallback); + method public void onFillRequest(android.app.assist.AssistStructure, android.os.Bundle, int, android.os.CancellationSignal, android.service.autofill.FillCallback); method public abstract void onSaveRequest(android.app.assist.AssistStructure, android.os.Bundle, android.service.autofill.SaveCallback); field public static final java.lang.String SERVICE_INTERFACE = "android.service.autofill.AutofillService"; field public static final java.lang.String SERVICE_META_DATA = "android.autofill"; @@ -36935,11 +37006,11 @@ package android.service.autofill { } public static final class SaveInfo.Builder { - ctor public SaveInfo.Builder(int); - method public android.service.autofill.SaveInfo.Builder addSavableIds(android.view.autofill.AutofillId...); + ctor public SaveInfo.Builder(int, android.view.autofill.AutofillId[]); method public android.service.autofill.SaveInfo build(); method public android.service.autofill.SaveInfo.Builder setDescription(java.lang.CharSequence); method public android.service.autofill.SaveInfo.Builder setNegativeAction(java.lang.CharSequence, android.content.IntentSender); + method public android.service.autofill.SaveInfo.Builder setOptionalIds(android.view.autofill.AutofillId[]); } } @@ -39362,6 +39433,7 @@ package android.telephony { method public android.os.PersistableBundle getConfigForSubId(int); method public void notifyConfigChangedForSubId(int); field public static final java.lang.String ACTION_CARRIER_CONFIG_CHANGED = "android.telephony.action.CARRIER_CONFIG_CHANGED"; + field public static final int DATA_CYCLE_THRESHOLD_DISABLED = -2; // 0xfffffffe field public static final java.lang.String KEY_ADDITIONAL_CALL_SETTING_BOOL = "additional_call_setting_bool"; field public static final java.lang.String KEY_ALLOW_ADDING_APNS_BOOL = "allow_adding_apns_bool"; field public static final java.lang.String KEY_ALLOW_ADD_CALL_DURING_VIDEO_CALL_BOOL = "allow_add_call_during_video_call"; @@ -39404,6 +39476,8 @@ package android.telephony { field public static final java.lang.String KEY_CI_ACTION_ON_SYS_UPDATE_INTENT_STRING = "ci_action_on_sys_update_intent_string"; field public static final java.lang.String KEY_CONFIG_IMS_PACKAGE_OVERRIDE_STRING = "config_ims_package_override_string"; field public static final java.lang.String KEY_CSP_ENABLED_BOOL = "csp_enabled_bool"; + field public static final java.lang.String KEY_DATA_LIMIT_THRESHOLD_BYTES_LONG = "data_limit_threshold_bytes_long"; + field public static final java.lang.String KEY_DATA_WARNING_THRESHOLD_BYTES_LONG = "data_warning_threshold_bytes_long"; field public static final java.lang.String KEY_DEFAULT_SIM_CALL_MANAGER_STRING = "default_sim_call_manager_string"; field public static final java.lang.String KEY_DEFAULT_VM_NUMBER_STRING = "default_vm_number_string"; field public static final java.lang.String KEY_DIAL_STRING_REPLACE_STRING_ARRAY = "dial_string_replace_string_array"; @@ -39459,6 +39533,7 @@ package android.telephony { field public static final java.lang.String KEY_MMS_UA_PROF_TAG_NAME_STRING = "uaProfTagName"; field public static final java.lang.String KEY_MMS_UA_PROF_URL_STRING = "uaProfUrl"; field public static final java.lang.String KEY_MMS_USER_AGENT_STRING = "userAgent"; + field public static final java.lang.String KEY_MONTHLY_DATA_CYCLE_DAY_INT = "monthly_data_cycle_day_int"; field public static final java.lang.String KEY_ONLY_SINGLE_DC_ALLOWED_INT_ARRAY = "only_single_dc_allowed_int_array"; field public static final java.lang.String KEY_OPERATOR_SELECTION_EXPAND_BOOL = "operator_selection_expand_bool"; field public static final java.lang.String KEY_PREFER_2G_BOOL = "prefer_2g_bool"; @@ -39991,7 +40066,6 @@ package android.telephony { method public android.os.PersistableBundle getCarrierConfig(); method public android.telephony.CellLocation getCellLocation(); method public int getDataActivity(); - method public boolean getDataEnabled(); method public int getDataNetworkType(); method public int getDataState(); method public java.lang.String getDeviceId(); @@ -40032,6 +40106,7 @@ package android.telephony { method public java.lang.String iccTransmitApduBasicChannel(int, int, int, int, int, java.lang.String); method public java.lang.String iccTransmitApduLogicalChannel(int, int, int, int, int, int, java.lang.String); method public boolean isConcurrentVoiceAndDataAllowed(); + method public boolean isDataEnabled(); method public boolean isHearingAidCompatibilitySupported(); method public boolean isNetworkRoaming(); method public boolean isSmsCapable(); @@ -40688,7 +40763,9 @@ package android.test.mock { method public android.database.sqlite.SQLiteDatabase openOrCreateDatabase(java.lang.String, int, android.database.sqlite.SQLiteDatabase.CursorFactory, android.database.DatabaseErrorHandler); method public android.graphics.drawable.Drawable peekWallpaper(); method public android.content.Intent registerReceiver(android.content.BroadcastReceiver, android.content.IntentFilter); + method public android.content.Intent registerReceiver(android.content.BroadcastReceiver, android.content.IntentFilter, boolean); method public android.content.Intent registerReceiver(android.content.BroadcastReceiver, android.content.IntentFilter, java.lang.String, android.os.Handler); + method public android.content.Intent registerReceiver(android.content.BroadcastReceiver, android.content.IntentFilter, java.lang.String, android.os.Handler, boolean); method public void removeStickyBroadcast(android.content.Intent); method public void removeStickyBroadcastAsUser(android.content.Intent, android.os.UserHandle); method public void revokeUriPermission(android.net.Uri, int); @@ -41071,11 +41148,10 @@ package android.text { } public final class FontConfig implements android.os.Parcelable { - ctor public FontConfig(); - ctor public FontConfig(android.text.FontConfig); + ctor public FontConfig(android.text.FontConfig.Family[], android.text.FontConfig.Alias[]); method public int describeContents(); - method public java.util.List<android.text.FontConfig.Alias> getAliases(); - method public java.util.List<android.text.FontConfig.Family> getFamilies(); + method public android.text.FontConfig.Alias[] getAliases(); + method public android.text.FontConfig.Family[] getFamilies(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.text.FontConfig> CREATOR; } @@ -41100,22 +41176,22 @@ package android.text { } public static final class FontConfig.Family implements android.os.Parcelable { - ctor public FontConfig.Family(java.lang.String, java.util.List<android.text.FontConfig.Font>, java.lang.String, java.lang.String); - ctor public FontConfig.Family(android.text.FontConfig.Family); + ctor public FontConfig.Family(java.lang.String, android.text.FontConfig.Font[], java.lang.String, int); method public int describeContents(); - method public java.util.List<android.text.FontConfig.Font> getFonts(); + method public android.text.FontConfig.Font[] getFonts(); method public java.lang.String getLanguage(); method public java.lang.String getName(); - method public java.lang.String getVariant(); + method public int getVariant(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.text.FontConfig.Family> CREATOR; + field public static final int VARIANT_COMPACT = 1; // 0x1 + field public static final int VARIANT_DEFAULT = 0; // 0x0 + field public static final int VARIANT_ELEGANT = 2; // 0x2 } public static final class FontConfig.Font implements android.os.Parcelable { - ctor public FontConfig.Font(java.lang.String, int, java.util.List<android.text.FontConfig.Axis>, int, boolean); - ctor public FontConfig.Font(android.text.FontConfig.Font); method public int describeContents(); - method public java.util.List<android.text.FontConfig.Axis> getAxes(); + method public android.text.FontConfig.Axis[] getAxes(); method public android.os.ParcelFileDescriptor getFd(); method public java.lang.String getFontName(); method public int getTtcIndex(); @@ -45451,7 +45527,7 @@ package android.view { method public float getAlpha(); method public android.view.animation.Animation getAnimation(); method public android.os.IBinder getApplicationWindowToken(); - method public int getAutofillHint(); + method public java.lang.String[] getAutofillHint(); method public int getAutofillMode(); method public int getAutofillType(); method public android.view.autofill.AutofillValue getAutofillValue(); @@ -45775,7 +45851,7 @@ package android.view { method public void setActivated(boolean); method public void setAlpha(float); method public void setAnimation(android.view.animation.Animation); - method public void setAutofillHint(int); + method public void setAutofillHint(java.lang.String...); method public void setAutofillMode(int); method public void setBackground(android.graphics.drawable.Drawable); method public void setBackgroundColor(int); @@ -45918,20 +45994,19 @@ package android.view { field public static final int ACCESSIBILITY_LIVE_REGION_NONE = 0; // 0x0 field public static final int ACCESSIBILITY_LIVE_REGION_POLITE = 1; // 0x1 field public static final android.util.Property<android.view.View, java.lang.Float> ALPHA; - field public static final int AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_DATE = 512; // 0x200 - field public static final int AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_DAY = 4096; // 0x1000 - field public static final int AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_MONTH = 1024; // 0x400 - field public static final int AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_YEAR = 2048; // 0x800 - field public static final int AUTOFILL_HINT_CREDIT_CARD_NUMBER = 128; // 0x80 - field public static final int AUTOFILL_HINT_CREDIT_CARD_SECURITY_CODE = 256; // 0x100 - field public static final int AUTOFILL_HINT_EMAIL_ADDRESS = 1; // 0x1 - field public static final int AUTOFILL_HINT_NAME = 2; // 0x2 - field public static final int AUTOFILL_HINT_NONE = 0; // 0x0 - field public static final int AUTOFILL_HINT_PASSWORD = 8; // 0x8 - field public static final int AUTOFILL_HINT_PHONE = 16; // 0x10 - field public static final int AUTOFILL_HINT_POSTAL_ADDRESS = 32; // 0x20 - field public static final int AUTOFILL_HINT_POSTAL_CODE = 64; // 0x40 - field public static final int AUTOFILL_HINT_USERNAME = 4; // 0x4 + field public static final java.lang.String AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_DATE = "creditCardExpirationDate"; + field public static final java.lang.String AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_DAY = "creditCardExpirationDay"; + field public static final java.lang.String AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_MONTH = "creditCardExpirationMonth"; + field public static final java.lang.String AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_YEAR = "creditCardExpirationYear"; + field public static final java.lang.String AUTOFILL_HINT_CREDIT_CARD_NUMBER = "creditCardNumber"; + field public static final java.lang.String AUTOFILL_HINT_CREDIT_CARD_SECURITY_CODE = "creditCardSecurityCode"; + field public static final java.lang.String AUTOFILL_HINT_EMAIL_ADDRESS = "emailAddress"; + field public static final java.lang.String AUTOFILL_HINT_NAME = "name"; + field public static final java.lang.String AUTOFILL_HINT_PASSWORD = "password"; + field public static final java.lang.String AUTOFILL_HINT_PHONE = "phone"; + field public static final java.lang.String AUTOFILL_HINT_POSTAL_ADDRESS = "postalAddress"; + field public static final java.lang.String AUTOFILL_HINT_POSTAL_CODE = "postalCode"; + field public static final java.lang.String AUTOFILL_HINT_USERNAME = "username"; field public static final int AUTOFILL_MODE_AUTO = 1; // 0x1 field public static final int AUTOFILL_MODE_INHERIT = 0; // 0x0 field public static final int AUTOFILL_MODE_MANUAL = 2; // 0x2 @@ -46597,7 +46672,7 @@ package android.view { method public abstract void setAccessibilityFocused(boolean); method public abstract void setActivated(boolean); method public abstract void setAlpha(float); - method public abstract void setAutofillHint(int); + method public abstract void setAutofillHint(java.lang.String[]); method public abstract void setAutofillOptions(java.lang.String[]); method public abstract void setAutofillType(int); method public abstract void setAutofillValue(android.view.autofill.AutofillValue); @@ -46608,6 +46683,7 @@ package android.view { method public abstract void setClickable(boolean); method public abstract void setContentDescription(java.lang.CharSequence); method public abstract void setContextClickable(boolean); + method public abstract void setDataIsSensitive(boolean); method public abstract void setDimens(int, int, int, int, int, int); method public abstract void setElevation(float); method public abstract void setEnabled(boolean); @@ -46618,7 +46694,6 @@ package android.view { method public abstract void setInputType(int); method public abstract void setLongClickable(boolean); method public abstract void setOpaque(boolean); - method public abstract void setSanitized(boolean); method public abstract void setSelected(boolean); method public abstract void setText(java.lang.CharSequence); method public abstract void setText(java.lang.CharSequence, int, int); @@ -47863,17 +47938,22 @@ package android.view.autofill { } public final class AutofillManager { + method public void cancel(); + method public void commit(); + method public boolean isEnabled(); + method public void notifyValueChanged(android.view.View); + method public void notifyViewEntered(android.view.View); + method public void notifyViewExited(android.view.View); + method public void notifyVirtualValueChanged(android.view.View, int, android.view.autofill.AutofillValue); + 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 reset(); - method public void startAutofillRequest(android.view.View); - method public void startAutofillRequestOnVirtualView(android.view.View, int, android.graphics.Rect); - method public void stopAutofillRequest(android.view.View); - method public void stopAutofillRequestOnVirtualView(android.view.View, int); + 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); - method public void valueChanged(android.view.View); - method public void virtualValueChanged(android.view.View, int, android.view.autofill.AutofillValue); 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 { @@ -47894,6 +47974,10 @@ package android.view.autofill { method public int getListValue(); method public java.lang.CharSequence getTextValue(); method public boolean getToggleValue(); + method public boolean isDate(); + method public boolean isList(); + method public boolean isText(); + method public boolean isToggle(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.view.autofill.AutofillValue> CREATOR; } diff --git a/cmds/idmap/Android.mk b/cmds/idmap/Android.mk index 50ccb07a3826..aeb8a0c001ec 100644 --- a/cmds/idmap/Android.mk +++ b/cmds/idmap/Android.mk @@ -17,7 +17,7 @@ include $(CLEAR_VARS) LOCAL_SRC_FILES := idmap.cpp create.cpp scan.cpp inspect.cpp -LOCAL_SHARED_LIBRARIES := liblog libutils libandroidfw +LOCAL_SHARED_LIBRARIES := liblog libutils libandroidfw libcutils LOCAL_MODULE := idmap diff --git a/cmds/idmap/idmap.cpp b/cmds/idmap/idmap.cpp index 3ab191553625..3a237ffda775 100644 --- a/cmds/idmap/idmap.cpp +++ b/cmds/idmap/idmap.cpp @@ -49,8 +49,8 @@ OPTIONS \n\ --path: create idmap for target package 'target' (path to apk) and overlay package \n\ 'overlay' (path to apk); write results to 'idmap' (path). \n\ \n\ - --scan: non-recursively search directory 'dir-to-scan' (path) for overlay packages with \n\ - target package 'target-package-name-to-look-for' (package name) present at\n\ + --scan: non-recursively search directory 'dir-to-scan' (path) for static overlay packages \n\ + with target package 'target-package-name-to-look-for' (package name) present at\n\ 'path-to-target-apk' (path to apk). For each overlay package found, create an\n\ idmap file in 'dir-to-hold-idmaps' (path). \n\ \n\ diff --git a/cmds/idmap/scan.cpp b/cmds/idmap/scan.cpp index 8122395d8061..67874a8b9c02 100644 --- a/cmds/idmap/scan.cpp +++ b/cmds/idmap/scan.cpp @@ -9,6 +9,7 @@ #include <androidfw/ResourceTypes.h> #include <androidfw/StreamingZipInflater.h> #include <androidfw/ZipFileRO.h> +#include <cutils/jstring.h> #include <private/android_filesystem_config.h> // for AID_SYSTEM #include <utils/SortedVector.h> #include <utils/String16.h> @@ -81,7 +82,8 @@ namespace { return String8(tmp); } - int parse_overlay_tag(const ResXMLTree& parser, const char *target_package_name) + int parse_overlay_tag(const ResXMLTree& parser, const char *target_package_name, + bool* is_static_overlay) { const size_t N = parser.getAttributeCount(); String16 target; @@ -102,6 +104,11 @@ namespace { return -1; } } + } else if (key == String16("isStatic")) { + Res_value v; + if (parser.getAttributeValue(i, &v) == sizeof(Res_value)) { + *is_static_overlay = (v.data != 0); + } } } if (target == String16(target_package_name)) { @@ -110,6 +117,28 @@ namespace { return NO_OVERLAY_TAG; } + String16 parse_package_name(const ResXMLTree& parser) + { + const size_t N = parser.getAttributeCount(); + String16 package_name; + for (size_t i = 0; i < N; ++i) { + size_t len; + String16 key(parser.getAttributeName(i, &len)); + if (key == String16("package")) { + const char16_t *p = parser.getAttributeStringValue(i, &len); + if (p != NULL) { + package_name = String16(p, len); + } + } + } + return package_name; + } + + bool isValidStaticOverlayPackage(const String16& package_name) { + // TODO(b/35742444): Need to support selection method based on a package name. + return package_name.size() > 0; + } + int parse_manifest(const void *data, size_t size, const char *target_package_name) { ResXMLTree parser; @@ -120,17 +149,26 @@ namespace { } ResXMLParser::event_code_t type; + String16 package_name; + bool is_static_overlay = false; + int priority = NO_OVERLAY_TAG; do { type = parser.next(); if (type == ResXMLParser::START_TAG) { size_t len; String16 tag(parser.getElementName(&len)); - if (tag == String16("overlay")) { - return parse_overlay_tag(parser, target_package_name); + if (tag == String16("manifest")) { + package_name = parse_package_name(parser); + } else if (tag == String16("overlay")) { + priority = parse_overlay_tag(parser, target_package_name, &is_static_overlay); + break; } } } while (type != ResXMLParser::BAD_DOCUMENT && type != ResXMLParser::END_DOCUMENT); + if (is_static_overlay && isValidStaticOverlayPackage(package_name)) { + return priority; + } return NO_OVERLAY_TAG; } diff --git a/cmds/uiautomator/library/testrunner-src/com/android/uiautomator/core/ShellUiAutomatorBridge.java b/cmds/uiautomator/library/testrunner-src/com/android/uiautomator/core/ShellUiAutomatorBridge.java index b0ab2355adb6..653851546d01 100644 --- a/cmds/uiautomator/library/testrunner-src/com/android/uiautomator/core/ShellUiAutomatorBridge.java +++ b/cmds/uiautomator/library/testrunner-src/com/android/uiautomator/core/ShellUiAutomatorBridge.java @@ -101,7 +101,7 @@ public class ShellUiAutomatorBridge extends UiAutomatorBridge { IWindowManager.Stub.asInterface(ServiceManager.getService(Context.WINDOW_SERVICE)); int ret = -1; try { - ret = wm.getRotation(); + ret = wm.getDefaultDisplayRotation(); } catch (RemoteException e) { Log.e(LOG_TAG, "Error getting screen rotation", e); throw new RuntimeException(e); diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index 1969f8bb3a48..78c29e840b9a 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -1783,7 +1783,7 @@ public class Activity extends ContextThemeWrapper mTranslucentCallback = null; mCalled = true; if (isFinishing() && mAutoFillResetNeeded) { - getSystemService(AutofillManager.class).reset(); + getSystemService(AutofillManager.class).commit(); } } diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java index fa64a0f55337..b36b664d6a93 100644 --- a/core/java/android/app/ActivityManagerInternal.java +++ b/core/java/android/app/ActivityManagerInternal.java @@ -25,6 +25,7 @@ import android.content.res.Configuration; import android.os.Bundle; import android.os.IBinder; import android.service.voice.IVoiceInteractionSession; +import android.util.SparseIntArray; import com.android.internal.app.IVoiceInteractor; @@ -47,9 +48,9 @@ public abstract class ActivityManagerInternal { /** * Type for {@link #notifyAppTransitionStarting}: The transition was started because we drew - * the starting window. + * the splash screen. */ - public static final int APP_TRANSITION_STARTING_WINDOW = 1; + public static final int APP_TRANSITION_SPLASH_SCREEN = 1; /** * Type for {@link #notifyAppTransitionStarting}: The transition was started because we all @@ -64,6 +65,12 @@ public abstract class ActivityManagerInternal { public static final int APP_TRANSITION_TIMEOUT = 3; /** + * Type for {@link #notifyAppTransitionStarting}: The transition was started because of a + * we drew a task snapshot. + */ + public static final int APP_TRANSITION_SNAPSHOT = 4; + + /** * Grant Uri permissions from one app to another. This method only extends * permission grants if {@code callingUid} has permission to them. */ @@ -122,19 +129,13 @@ public abstract class ActivityManagerInternal { IVoiceInteractor mInteractor); /** - * Callback for window manager to let activity manager know that the starting window has been - * drawn - */ - public abstract void notifyStartingWindowDrawn(); - - /** * Callback for window manager to let activity manager know that we are finally starting the * app transition; * - * @param reason The reason why the app transition started. Must be one of the APP_TRANSITION_* - * values. + * @param reasons A map from stack id to a reason integer why the transition was started,, which + * must be one of the APP_TRANSITION_* values. */ - public abstract void notifyAppTransitionStarting(int reason); + public abstract void notifyAppTransitionStarting(SparseIntArray reasons); /** * Callback for window manager to let activity manager know that the app transition was diff --git a/core/java/android/app/ActivityTransitionCoordinator.java b/core/java/android/app/ActivityTransitionCoordinator.java index a5123504a4a1..21a7ca733eb9 100644 --- a/core/java/android/app/ActivityTransitionCoordinator.java +++ b/core/java/android/app/ActivityTransitionCoordinator.java @@ -28,6 +28,7 @@ import android.transition.TransitionListenerAdapter; import android.transition.TransitionSet; import android.transition.Visibility; import android.util.ArrayMap; +import android.util.ArraySet; import android.view.GhostView; import android.view.View; import android.view.ViewGroup; @@ -394,6 +395,60 @@ abstract class ActivityTransitionCoordinator extends ResultReceiver { return transition; } + /** + * Looks through the transition to see which Views have been included and which have been + * excluded. {@code views} will be modified to contain only those Views that are included + * in the transition. If {@code transition} is a TransitionSet, it will search through all + * contained Transitions to find targeted Views. + * + * @param transition The transition to look through for inclusion of Views + * @param views The list of Views that are to be checked for inclusion. Will be modified + * to remove all excluded Views, possibly leaving an empty list. + */ + protected static void removeExcludedViews(Transition transition, ArrayList<View> views) { + ArraySet<View> included = new ArraySet<>(); + findIncludedViews(transition, views, included); + views.clear(); + views.addAll(included); + } + + /** + * Looks through the transition to see which Views have been included. Only {@code views} + * will be examined for inclusion. If {@code transition} is a TransitionSet, it will search + * through all contained Transitions to find targeted Views. + * + * @param transition The transition to look through for inclusion of Views + * @param views The list of Views that are to be checked for inclusion. + * @param included Modified to contain all Views in views that have at least one Transition + * that affects it. + */ + private static void findIncludedViews(Transition transition, ArrayList<View> views, + ArraySet<View> included) { + if (transition instanceof TransitionSet) { + TransitionSet set = (TransitionSet) transition; + ArrayList<View> includedViews = new ArrayList<>(); + final int numViews = views.size(); + for (int i = 0; i < numViews; i++) { + final View view = views.get(i); + if (transition.isValidTarget(view)) { + includedViews.add(view); + } + } + final int count = set.getTransitionCount(); + for (int i = 0; i < count; i++) { + findIncludedViews(set.getTransitionAt(i), includedViews, included); + } + } else { + final int numViews = views.size(); + for (int i = 0; i < numViews; i++) { + final View view = views.get(i); + if (transition.isValidTarget(view)) { + included.add(view); + } + } + } + } + protected static Transition mergeTransitions(Transition transition1, Transition transition2) { if (transition1 == null) { return transition2; diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java index 97992cafee0e..55407e6d7365 100644 --- a/core/java/android/app/ApplicationPackageManager.java +++ b/core/java/android/app/ApplicationPackageManager.java @@ -1814,6 +1814,15 @@ public class ApplicationPackageManager extends PackageManager { } @Override + public void setUpdateAvailable(String packageName, boolean updateAvailable) { + try { + mPM.setUpdateAvailable(packageName, updateAvailable); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + @Override public String getInstallerPackageName(String packageName) { try { return mPM.getInstallerPackageName(packageName); diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index 0ab4b808af95..ede9281264b3 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -1327,21 +1327,34 @@ class ContextImpl extends Context { @Override public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter, + boolean visibleToInstantApps) { + return registerReceiver(receiver, filter, null, null, visibleToInstantApps); + } + + @Override + public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter, String broadcastPermission, Handler scheduler) { return registerReceiverInternal(receiver, getUserId(), - filter, broadcastPermission, scheduler, getOuterContext()); + filter, broadcastPermission, scheduler, getOuterContext(), false); + } + + @Override + public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter, + String broadcastPermission, Handler scheduler, boolean visibleToInstantApps) { + return registerReceiverInternal(receiver, getUserId(), + filter, broadcastPermission, scheduler, getOuterContext(), visibleToInstantApps); } @Override public Intent registerReceiverAsUser(BroadcastReceiver receiver, UserHandle user, IntentFilter filter, String broadcastPermission, Handler scheduler) { return registerReceiverInternal(receiver, user.getIdentifier(), - filter, broadcastPermission, scheduler, getOuterContext()); + filter, broadcastPermission, scheduler, getOuterContext(), false); } private Intent registerReceiverInternal(BroadcastReceiver receiver, int userId, IntentFilter filter, String broadcastPermission, - Handler scheduler, Context context) { + Handler scheduler, Context context, boolean visibleToInstantApps) { IIntentReceiver rd = null; if (receiver != null) { if (mPackageInfo != null && context != null) { @@ -1362,7 +1375,7 @@ class ContextImpl extends Context { try { final Intent intent = ActivityManager.getService().registerReceiver( mMainThread.getApplicationThread(), mBasePackageName, rd, filter, - broadcastPermission, userId); + broadcastPermission, userId, visibleToInstantApps); if (intent != null) { intent.setExtrasClassLoader(getClassLoader()); intent.prepareToEnterProcess(); diff --git a/core/java/android/app/EnterTransitionCoordinator.java b/core/java/android/app/EnterTransitionCoordinator.java index 445b6871ddac..ab847fd562a4 100644 --- a/core/java/android/app/EnterTransitionCoordinator.java +++ b/core/java/android/app/EnterTransitionCoordinator.java @@ -132,7 +132,9 @@ class EnterTransitionCoordinator extends ActivityTransitionCoordinator { super.viewsReady(sharedElements); mIsReadyForTransition = true; hideViews(mSharedElements); - if (getViewsTransition() != null && mTransitioningViews != null) { + Transition viewsTransition = getViewsTransition(); + if (viewsTransition != null && mTransitioningViews != null) { + removeExcludedViews(viewsTransition, mTransitioningViews); stripOffscreenViews(); hideViews(mTransitioningViews); } diff --git a/core/java/android/app/ExitTransitionCoordinator.java b/core/java/android/app/ExitTransitionCoordinator.java index 29e10d81c95d..df31da9183f1 100644 --- a/core/java/android/app/ExitTransitionCoordinator.java +++ b/core/java/android/app/ExitTransitionCoordinator.java @@ -321,6 +321,10 @@ class ExitTransitionCoordinator extends ActivityTransitionCoordinator { Transition viewsTransition = null; if (mTransitioningViews != null && !mTransitioningViews.isEmpty()) { viewsTransition = configureTransition(getViewsTransition(), true); + removeExcludedViews(viewsTransition, mTransitioningViews); + if (mTransitioningViews.isEmpty()) { + viewsTransition = null; + } } if (viewsTransition == null) { viewsTransitionComplete(); diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl index 081dae2298d0..77edaeacfd2a 100644 --- a/core/java/android/app/IActivityManager.aidl +++ b/core/java/android/app/IActivityManager.aidl @@ -100,7 +100,7 @@ interface IActivityManager { boolean finishActivity(in IBinder token, int code, in Intent data, int finishTask); Intent registerReceiver(in IApplicationThread caller, in String callerPackage, in IIntentReceiver receiver, in IntentFilter filter, - in String requiredPermission, int userId); + in String requiredPermission, int userId, boolean visibleToInstantApps); void unregisterReceiver(in IIntentReceiver receiver); int broadcastIntent(in IApplicationThread caller, in Intent intent, in String resolvedType, in IIntentReceiver resultTo, int resultCode, diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java index be38f42490e5..77c4c7eea811 100644 --- a/core/java/android/app/LoadedApk.java +++ b/core/java/android/app/LoadedApk.java @@ -597,8 +597,7 @@ public final class LoadedApk { // Avoid the binder call when the package is the current application package. // The activity manager will perform ensure that dexopt is performed before // spinning up the process. - if (!Objects.equals(mPackageName, ActivityThread.currentPackageName())) { - VMRuntime.getRuntime().vmInstructionSet(); + if (!Objects.equals(mPackageName, ActivityThread.currentPackageName()) && mIncludeCode) { try { ActivityThread.getPackageManager().notifyPackageUse(mPackageName, PackageManager.NOTIFY_PACKAGE_USE_CROSS_PACKAGE); diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java index d37e2099c1d7..2296838eddb3 100644 --- a/core/java/android/app/NotificationManager.java +++ b/core/java/android/app/NotificationManager.java @@ -424,7 +424,8 @@ public class NotificationManager * This is a no-op for channels that already exist. * * @param channel the channel to create. Note that the created channel may differ from this - * value. If the channel already exists, it will not be modified. + * value. If the provided channel is malformed, a RemoteException will be + * thrown. If the channel already exists, it will not be modified. */ public void createNotificationChannel(@NonNull NotificationChannel channel) { createNotificationChannels(Arrays.asList(channel)); diff --git a/core/java/android/app/TimePickerDialog.java b/core/java/android/app/TimePickerDialog.java index b219f2afd016..8a4f8a626c71 100644 --- a/core/java/android/app/TimePickerDialog.java +++ b/core/java/android/app/TimePickerDialog.java @@ -164,6 +164,15 @@ public class TimePickerDialog extends AlertDialog implements OnClickListener, @Override public void onClick(DialogInterface dialog, int which) { switch (which) { + case BUTTON_POSITIVE: + // Note this skips input validation and just uses the last valid time and hour + // entry. This will only be invoked programmatically. User clicks on BUTTON_POSITIVE + // are handled in show(). + if (mTimeSetListener != null) { + mTimeSetListener.onTimeSet(mTimePicker, mTimePicker.getCurrentHour(), + mTimePicker.getCurrentMinute()); + } + break; case BUTTON_NEGATIVE: cancel(); break; diff --git a/core/java/android/app/UiAutomationConnection.java b/core/java/android/app/UiAutomationConnection.java index 2c1ee8e13624..9960df63b537 100644 --- a/core/java/android/app/UiAutomationConnection.java +++ b/core/java/android/app/UiAutomationConnection.java @@ -367,7 +367,7 @@ public final class UiAutomationConnection extends IUiAutomationConnection.Stub { if (mWindowManager.isRotationFrozen()) { // Calling out with a lock held is fine since if the system // process is gone the client calling in will be killed. - mInitialFrozenRotation = mWindowManager.getRotation(); + mInitialFrozenRotation = mWindowManager.getDefaultDisplayRotation(); } } catch (RemoteException re) { /* ignore */ diff --git a/core/java/android/app/assist/AssistStructure.java b/core/java/android/app/assist/AssistStructure.java index 1f2ed00fa79d..b1fbc8f18818 100644 --- a/core/java/android/app/assist/AssistStructure.java +++ b/core/java/android/app/assist/AssistStructure.java @@ -1,5 +1,6 @@ package android.app.assist; +import android.annotation.Nullable; import android.app.Activity; import android.content.ComponentName; import android.graphics.Matrix; @@ -590,7 +591,7 @@ public class AssistStructure implements Parcelable { // fields (viewId and childId) of the field. AutofillId mAutofillId; @View.AutofillType int mAutofillType; - @View.AutofillHint int mAutofillHint; + @Nullable String[] mAutofillHint; AutofillValue mAutofillValue; String[] mAutofillOptions; boolean mSanitized; @@ -676,7 +677,7 @@ public class AssistStructure implements Parcelable { mSanitized = in.readInt() == 1; mAutofillId = in.readParcelable(null); mAutofillType = in.readInt(); - mAutofillHint = in.readInt(); + mAutofillHint = in.readStringArray(); mAutofillValue = in.readParcelable(null); mAutofillOptions = in.readStringArray(); } @@ -810,7 +811,7 @@ public class AssistStructure implements Parcelable { out.writeInt(mSanitized ? 1 : 0); out.writeParcelable(mAutofillId, 0); out.writeInt(mAutofillType); - out.writeInt(mAutofillHint); + out.writeStringArray(mAutofillHint); final AutofillValue sanitizedValue = writeSensitive ? mAutofillValue : null; out.writeParcelable(sanitizedValue, 0); out.writeStringArray(mAutofillOptions); @@ -949,7 +950,7 @@ public class AssistStructure implements Parcelable { * * @return The hint for this view */ - @View.AutofillHint public int getAutoFillHint() { + @Nullable public String[] getAutoFillHint() { return mAutofillHint; } @@ -1012,9 +1013,8 @@ public class AssistStructure implements Parcelable { mAutofillValue = value; // TODO(b/33197203, b/33802548): decide whether to set text as well (so it would work // with "legacy" views) or just the autofill value - final CharSequence text = value.getTextValue(); - if (text != null) { - mText.mText = text; + if (value.isText()) { + mText.mText = value.getTextValue(); } } @@ -1663,7 +1663,7 @@ public class AssistStructure implements Parcelable { } @Override - public void setAutofillHint(@View.AutofillHint int hint) { + public void setAutofillHint(@Nullable String[] hint) { mNode.mAutofillHint = hint; } @@ -1683,8 +1683,8 @@ public class AssistStructure implements Parcelable { } @Override - public void setSanitized(boolean sanitized) { - mNode.mSanitized = sanitized; + public void setDataIsSensitive(boolean sensitive) { + mNode.mSanitized = !sensitive; } @Override @@ -1812,7 +1812,7 @@ public class AssistStructure implements Parcelable { + ", type=" + node.getAutofillType() + ", options=" + Arrays.toString(node.getAutofillOptions()) + ", inputType=" + node.getInputType() - + ", hint=" + Integer.toHexString(node.getAutoFillHint()) + + ", hint=" + Arrays.toString(node.getAutoFillHint()) + ", value=" + node.getAutofillValue() + ", sanitized=" + node.isSanitized()); } diff --git a/core/java/android/app/usage/IStorageStatsManager.aidl b/core/java/android/app/usage/IStorageStatsManager.aidl index 76c0293566bb..5d1550f1a557 100644 --- a/core/java/android/app/usage/IStorageStatsManager.aidl +++ b/core/java/android/app/usage/IStorageStatsManager.aidl @@ -24,6 +24,7 @@ interface IStorageStatsManager { boolean isQuotaSupported(String volumeUuid, String callingPackage); long getTotalBytes(String volumeUuid, String callingPackage); long getFreeBytes(String volumeUuid, String callingPackage); + long getCacheQuotaBytes(String volumeUuid, int uid, String callingPackage); StorageStats queryStatsForPackage(String volumeUuid, String packageName, int userId, String callingPackage); StorageStats queryStatsForUid(String volumeUuid, int uid, String callingPackage); StorageStats queryStatsForUser(String volumeUuid, int userId, String callingPackage); diff --git a/core/java/android/app/usage/StorageStatsManager.java b/core/java/android/app/usage/StorageStatsManager.java index 081ccd956d6e..b808c2b75203 100644 --- a/core/java/android/app/usage/StorageStatsManager.java +++ b/core/java/android/app/usage/StorageStatsManager.java @@ -81,9 +81,9 @@ public class StorageStatsManager { /** * Return the free space on the requested storage volume. * <p> - * The free space is equivalent to {@link File#getFreeSpace()} plus the size - * of any cached data that can be automatically deleted by the system as - * additional space is needed. + * The free space is equivalent to {@link File#getUsableSpace()} plus the + * size of any cached data that can be automatically deleted by the system + * as additional space is needed. * <p> * This method may take several seconds to calculate the requested values, * so it should only be called from a worker thread. @@ -195,4 +195,13 @@ public class StorageStatsManager { throw e.rethrowFromSystemServer(); } } + + /** {@hide} */ + public long getCacheQuotaBytes(String volumeUuid, int uid) { + try { + return mService.getCacheQuotaBytes(volumeUuid, uid, mContext.getOpPackageName()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } } diff --git a/core/java/android/content/ContentProviderNative.java b/core/java/android/content/ContentProviderNative.java index d428a3a857b7..2f87633a39d3 100644 --- a/core/java/android/content/ContentProviderNative.java +++ b/core/java/android/content/ContentProviderNative.java @@ -24,6 +24,7 @@ import android.database.Cursor; import android.database.CursorToBulkCursorAdaptor; import android.database.DatabaseUtils; import android.database.IContentObserver; +import android.database.PageViewCursor; import android.net.Uri; import android.os.Binder; import android.os.Bundle; @@ -103,6 +104,7 @@ abstract public class ContentProviderNative extends Binder implements IContentPr if (cursor != null) { CursorToBulkCursorAdaptor adaptor = null; + cursor = PageViewCursor.wrap(cursor, queryArgs); try { adaptor = new CursorToBulkCursorAdaptor(cursor, observer, getProviderName()); diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java index 5579c9aed925..98ae132d9ed7 100644 --- a/core/java/android/content/ContentResolver.java +++ b/core/java/android/content/ContentResolver.java @@ -2835,6 +2835,11 @@ public abstract class ContentResolver { } } + /** {@hide} */ + public int getTargetSdkVersion() { + return mTargetSdkVersion; + } + /** * Returns sampling percentage for a given duration. * diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 3cf96ed5a5fa..3a8a4206e105 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -2383,6 +2383,38 @@ public abstract class Context { IntentFilter filter); /** + * Register to receive intent broadcasts, with the receiver optionally being + * exposed to Instant Apps. See + * {@link #registerReceiver(BroadcastReceiver, IntentFilter)} for more + * information. By default Instant Apps cannot interact with receivers in other + * applications, this allows you to expose a receiver that Instant Apps can + * interact with. + * + * <p>See {@link BroadcastReceiver} for more information on Intent broadcasts. + * + * <p>As of {@link android.os.Build.VERSION_CODES#ICE_CREAM_SANDWICH}, receivers + * registered with this method will correctly respect the + * {@link Intent#setPackage(String)} specified for an Intent being broadcast. + * Prior to that, it would be ignored and delivered to all matching registered + * receivers. Be careful if using this for security.</p> + * + * @param receiver The BroadcastReceiver to handle the broadcast. + * @param filter Selects the Intent broadcasts to be received. + * @param visibleToInstantApps If the receiver accepts broadcasts from Instant Apps. + * + * @return The first sticky intent found that matches <var>filter</var>, + * or null if there are none. + * + * @see #registerReceiver(BroadcastReceiver, IntentFilter) + * @see #sendBroadcast + * @see #unregisterReceiver + */ + @Nullable + public abstract Intent registerReceiver(@Nullable BroadcastReceiver receiver, + IntentFilter filter, + boolean visibleToInstantApps); + + /** * Register to receive intent broadcasts, to run in the context of * <var>scheduler</var>. See * {@link #registerReceiver(BroadcastReceiver, IntentFilter)} for more @@ -2419,6 +2451,43 @@ public abstract class Context { @Nullable Handler scheduler); /** + * Register to receive intent broadcasts, with the receiver optionally being + * exposed to Instant Apps. See + * {@link #registerReceiver(BroadcastReceiver, IntentFilter, boolean)} and + * {@link #registerReceiver(BroadcastReceiver, IntentFilter, String, Handler)} + * for more information. + * + * <p>See {@link BroadcastReceiver} for more information on Intent broadcasts. + * + * <p>As of {@link android.os.Build.VERSION_CODES#ICE_CREAM_SANDWICH}, receivers + * registered with this method will correctly respect the + * {@link Intent#setPackage(String)} specified for an Intent being broadcast. + * Prior to that, it would be ignored and delivered to all matching registered + * receivers. Be careful if using this for security.</p> + * + * @param receiver The BroadcastReceiver to handle the broadcast. + * @param filter Selects the Intent broadcasts to be received. + * @param broadcastPermission String naming a permissions that a + * broadcaster must hold in order to send an Intent to you. If null, + * no permission is required. + * @param scheduler Handler identifying the thread that will receive + * the Intent. If null, the main thread of the process will be used. + * @param visibleToInstantApps If the receiver accepts broadcasts from Instant Apps. + * + * @return The first sticky intent found that matches <var>filter</var>, + * or null if there are none. + * + * @see #registerReceiver(BroadcastReceiver, IntentFilter, boolean) + * @see #registerReceiver(BroadcastReceiver, IntentFilter, String, Handler) + * @see #sendBroadcast + * @see #unregisterReceiver + */ + @Nullable + public abstract Intent registerReceiver(BroadcastReceiver receiver, + IntentFilter filter, @Nullable String broadcastPermission, + @Nullable Handler scheduler, boolean visibleToInstantApps); + + /** * @hide * Same as {@link #registerReceiver(BroadcastReceiver, IntentFilter, String, Handler) * but for a specific user. This receiver will receiver broadcasts that diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java index c932b2317e5a..6b0bbfaedc3e 100644 --- a/core/java/android/content/ContextWrapper.java +++ b/core/java/android/content/ContextWrapper.java @@ -605,12 +605,26 @@ public class ContextWrapper extends Context { @Override public Intent registerReceiver( + BroadcastReceiver receiver, IntentFilter filter, boolean visibleToInstantApps) { + return mBase.registerReceiver(receiver, filter, visibleToInstantApps); + } + + @Override + public Intent registerReceiver( BroadcastReceiver receiver, IntentFilter filter, String broadcastPermission, Handler scheduler) { return mBase.registerReceiver(receiver, filter, broadcastPermission, scheduler); } + @Override + public Intent registerReceiver( + BroadcastReceiver receiver, IntentFilter filter, + String broadcastPermission, Handler scheduler, boolean visibleToInstantApps) { + return mBase.registerReceiver(receiver, filter, broadcastPermission, + scheduler, visibleToInstantApps); + } + /** @hide */ @Override public Intent registerReceiverAsUser( diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index f62621b42d99..1f01e28ea0b0 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -5005,6 +5005,14 @@ public class Intent implements Parcelable, Cloneable { public static final int FLAG_RECEIVER_FROM_SHELL = 0x00400000; /** + * If set, the broadcast will be visible to receivers in Instant Apps. By default Instant Apps + * will not receive broadcasts. + * + * <em>This flag has no effect when used by an Instant App.</em> + */ + public static final int FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS = 0x00200000; + + /** * @hide Flags that can't be changed with PendingIntent. */ public static final int IMMUTABLE_FLAGS = FLAG_GRANT_READ_URI_PERMISSION diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl index 147b3e13b998..4de64c41e913 100644 --- a/core/java/android/content/pm/IPackageManager.aidl +++ b/core/java/android/content/pm/IPackageManager.aidl @@ -608,6 +608,12 @@ interface IPackageManager { boolean setRequiredForSystemUser(String packageName, boolean systemUserApp); + /** + * Sets whether or not an update is available. Ostensibly for instant apps + * to force exteranl resolution. + */ + void setUpdateAvailable(String packageName, boolean updateAvaialble); + String getServicesSystemSharedLibraryPackageName(); String getSharedSystemSharedLibraryPackageName(); diff --git a/core/java/android/content/pm/PackageInfo.java b/core/java/android/content/pm/PackageInfo.java index 5d5696b5a54e..8ff2f352a362 100644 --- a/core/java/android/content/pm/PackageInfo.java +++ b/core/java/android/content/pm/PackageInfo.java @@ -271,6 +271,9 @@ public class PackageInfo implements Parcelable { */ public String overlayTarget; + /** @hide */ + public boolean isStaticOverlay; + public PackageInfo() { } @@ -323,6 +326,7 @@ public class PackageInfo implements Parcelable { dest.writeString(restrictedAccountType); dest.writeString(requiredAccountType); dest.writeString(overlayTarget); + dest.writeInt(isStaticOverlay ? 1 : 0); } public static final Parcelable.Creator<PackageInfo> CREATOR @@ -372,6 +376,7 @@ public class PackageInfo implements Parcelable { restrictedAccountType = source.readString(); requiredAccountType = source.readString(); overlayTarget = source.readString(); + isStaticOverlay = source.readInt() != 0; // The component lists were flattened with the redundant ApplicationInfo // instances omitted. Distribute the canonical one here as appropriate. diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java index 76c29bcd5ae6..320c7334b3c6 100644 --- a/core/java/android/content/pm/PackageInstaller.java +++ b/core/java/android/content/pm/PackageInstaller.java @@ -36,7 +36,9 @@ import android.os.Parcel; import android.os.ParcelFileDescriptor; import android.os.Parcelable; import android.os.RemoteException; -import android.annotation.IntRange; +import android.os.SystemProperties; +import android.system.ErrnoException; +import android.system.Os; import android.util.ExceptionUtils; import com.android.internal.util.IndentingPrintWriter; @@ -79,6 +81,10 @@ import java.util.List; public class PackageInstaller { private static final String TAG = "PackageInstaller"; + /** {@hide} */ + public static final boolean ENABLE_REVOCABLE_FD = + SystemProperties.getBoolean("fw.revocable_fd", false); + /** * Activity Action: Show details about a particular install session. This * may surface actions such as pause, resume, or cancel. @@ -753,15 +759,21 @@ public class PackageInstaller { public @NonNull OutputStream openWrite(@NonNull String name, long offsetBytes, long lengthBytes) throws IOException { try { - final ParcelFileDescriptor clientSocket = mSession.openWrite(name, - offsetBytes, lengthBytes); - return new FileBridge.FileBridgeOutputStream(clientSocket); + if (ENABLE_REVOCABLE_FD) { + return new ParcelFileDescriptor.AutoCloseOutputStream( + mSession.openWrite(name, offsetBytes, lengthBytes)); + } else { + final ParcelFileDescriptor clientSocket = mSession.openWrite(name, + offsetBytes, lengthBytes); + return new FileBridge.FileBridgeOutputStream(clientSocket); + } } catch (RuntimeException e) { ExceptionUtils.maybeUnwrapIOException(e); throw e; } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } + } /** @@ -770,10 +782,22 @@ public class PackageInstaller { * {@link #openWrite(String, long, long)}. */ public void fsync(@NonNull OutputStream out) throws IOException { - if (out instanceof FileBridge.FileBridgeOutputStream) { - ((FileBridge.FileBridgeOutputStream) out).fsync(); + if (ENABLE_REVOCABLE_FD) { + if (out instanceof ParcelFileDescriptor.AutoCloseOutputStream) { + try { + Os.fsync(((ParcelFileDescriptor.AutoCloseOutputStream) out).getFD()); + } catch (ErrnoException e) { + throw e.rethrowAsIOException(); + } + } else { + throw new IllegalArgumentException("Unrecognized stream"); + } } else { - throw new IllegalArgumentException("Unrecognized stream"); + if (out instanceof FileBridge.FileBridgeOutputStream) { + ((FileBridge.FileBridgeOutputStream) out).fsync(); + } else { + throw new IllegalArgumentException("Unrecognized stream"); + } } } diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index 3a875bc79aa8..33f57e025473 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -5286,6 +5286,11 @@ public abstract class PackageManager { public abstract void setInstallerPackageName(String targetPackage, String installerPackageName); + /** @hide */ + @SystemApi + @RequiresPermission(Manifest.permission.INSTALL_PACKAGES) + public abstract void setUpdateAvailable(String packageName, boolean updateAvaialble); + /** * Attempts to delete a package. Since this may take a little while, the * result will be posted back to the given observer. A deletion will fail if diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index a1c325ac26ea..e15a0e240711 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -603,6 +603,7 @@ public class PackageParser { pi.restrictedAccountType = p.mRestrictedAccountType; pi.requiredAccountType = p.mRequiredAccountType; pi.overlayTarget = p.mOverlayTarget; + pi.isStaticOverlay = p.mIsStaticOverlay; pi.firstInstallTime = firstInstallTime; pi.lastUpdateTime = lastUpdateTime; if ((flags&PackageManager.GET_GIDS) != 0) { @@ -2097,6 +2098,9 @@ public class PackageParser { com.android.internal.R.styleable.AndroidManifestResourceOverlay); pkg.mOverlayTarget = sa.getString( com.android.internal.R.styleable.AndroidManifestResourceOverlay_targetPackage); + pkg.mIsStaticOverlay = sa.getBoolean( + com.android.internal.R.styleable.AndroidManifestResourceOverlay_isStatic, + false); sa.recycle(); if (pkg.mOverlayTarget == null) { @@ -2104,6 +2108,9 @@ public class PackageParser { mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED; return null; } + if (pkg.mIsStaticOverlay) { + // TODO(b/35742444): Need to support selection method based on a package name. + } XmlUtils.skipCurrentTag(parser); } else if (tagName.equals(TAG_KEY_SETS)) { @@ -5580,6 +5587,7 @@ public class PackageParser { public String mRequiredAccountType; public String mOverlayTarget; + public boolean mIsStaticOverlay; public boolean mTrustedOverlay; /** @@ -6056,6 +6064,7 @@ public class PackageParser { mRestrictedAccountType = dest.readString(); mRequiredAccountType = dest.readString(); mOverlayTarget = dest.readString(); + mIsStaticOverlay = (dest.readInt() == 1); mTrustedOverlay = (dest.readInt() == 1); mSigningKeys = (ArraySet<PublicKey>) dest.readArraySet(boot); mUpgradeKeySets = (ArraySet<String>) dest.readArraySet(boot); @@ -6171,6 +6180,7 @@ public class PackageParser { dest.writeString(mRestrictedAccountType); dest.writeString(mRequiredAccountType); dest.writeString(mOverlayTarget); + dest.writeInt(mIsStaticOverlay ? 1 : 0); dest.writeInt(mTrustedOverlay ? 1 : 0); dest.writeArraySet(mSigningKeys); dest.writeArraySet(mUpgradeKeySets); diff --git a/core/java/android/content/res/FontResourcesParser.java b/core/java/android/content/res/FontResourcesParser.java index 50fc3443f5a6..091cc263f5b4 100644 --- a/core/java/android/content/res/FontResourcesParser.java +++ b/core/java/android/content/res/FontResourcesParser.java @@ -16,7 +16,8 @@ package android.content.res; import com.android.internal.R; -import android.text.FontConfig; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.util.AttributeSet; import android.util.Xml; @@ -35,7 +36,81 @@ public class FontResourcesParser { private static final int NORMAL_WEIGHT = 400; private static final int ITALIC = 1; - public static FontConfig parse(XmlPullParser parser, Resources resources) + // A class represents single entry of font-family in xml file. + public interface FamilyResourceEntry {} + + // A class represents font provider based font-family element in xml file. + public static final class ProviderResourceEntry implements FamilyResourceEntry { + private final @NonNull String mProviderAuthority; + private final @NonNull String mProviderPackage; + private final @NonNull String mQuery; + + public ProviderResourceEntry(@NonNull String authority, @NonNull String pkg, + @NonNull String query) { + mProviderAuthority = authority; + mProviderPackage = pkg; + mQuery = query; + } + + public @NonNull String getAuthority() { + return mProviderAuthority; + } + + public @NonNull String getPackage() { + return mProviderPackage; + } + + public @NonNull String getQuery() { + return mQuery; + } + } + + // A class represents font element in xml file which points a file in resource. + public static final class FontFileResourceEntry { + private final @NonNull String mFileName; + private int mWeight; + private boolean mItalic; + private int mResourceId; + + public FontFileResourceEntry(@NonNull String fileName, int weight, boolean italic, + int resourceId) { + mFileName = fileName; + mWeight = weight; + mItalic = italic; + mResourceId = resourceId; + } + + public @NonNull String getFileName() { + return mFileName; + } + + public int getWeight() { + return mWeight; + } + + public boolean isItalic() { + return mItalic; + } + + public int getResourceId() { + return mResourceId; + } + } + + // A class represents file based font-family element in xml file. + public static final class FontFamilyFilesResourceEntry implements FamilyResourceEntry { + private final @NonNull FontFileResourceEntry[] mEntries; + + public FontFamilyFilesResourceEntry(@NonNull FontFileResourceEntry[] entries) { + mEntries = entries; + } + + public @NonNull FontFileResourceEntry[] getEntries() { + return mEntries; + } + } + + public static @Nullable FamilyResourceEntry parse(XmlPullParser parser, Resources resources) throws XmlPullParserException, IOException { int type; while ((type=parser.next()) != XmlPullParser.START_TAG @@ -49,21 +124,21 @@ public class FontResourcesParser { return readFamilies(parser, resources); } - private static FontConfig readFamilies(XmlPullParser parser, Resources resources) - throws XmlPullParserException, IOException { - FontConfig config = new FontConfig(); + private static @Nullable FamilyResourceEntry readFamilies(XmlPullParser parser, + Resources resources) throws XmlPullParserException, IOException { parser.require(XmlPullParser.START_TAG, null, "font-family"); String tag = parser.getName(); + FamilyResourceEntry result = null; if (tag.equals("font-family")) { - config.getFamilies().add(readFamily(parser, resources)); + return readFamily(parser, resources); } else { skip(parser); + return null; } - return config; } - private static FontConfig.Family readFamily(XmlPullParser parser, Resources resources) - throws XmlPullParserException, IOException { + private static @Nullable FamilyResourceEntry readFamily(XmlPullParser parser, + Resources resources) throws XmlPullParserException, IOException { AttributeSet attrs = Xml.asAttributeSet(parser); TypedArray array = resources.obtainAttributes(attrs, R.styleable.FontFamily); String authority = array.getString(R.styleable.FontFamily_fontProviderAuthority); @@ -74,9 +149,9 @@ public class FontResourcesParser { while (parser.next() != XmlPullParser.END_TAG) { skip(parser); } - return new FontConfig.Family(authority, providerPackage, query); + return new ProviderResourceEntry(authority, providerPackage, query); } - List<FontConfig.Font> fonts = new ArrayList<>(); + List<FontFileResourceEntry> fonts = new ArrayList<>(); while (parser.next() != XmlPullParser.END_TAG) { if (parser.getEventType() != XmlPullParser.START_TAG) continue; String tag = parser.getName(); @@ -86,10 +161,14 @@ public class FontResourcesParser { skip(parser); } } - return new FontConfig.Family(null, fonts, null, null); + if (fonts.isEmpty()) { + return null; + } + return new FontFamilyFilesResourceEntry(fonts.toArray( + new FontFileResourceEntry[fonts.size()])); } - private static FontConfig.Font readFont(XmlPullParser parser, Resources resources) + private static FontFileResourceEntry readFont(XmlPullParser parser, Resources resources) throws XmlPullParserException, IOException { AttributeSet attrs = Xml.asAttributeSet(parser); TypedArray array = resources.obtainAttributes(attrs, R.styleable.FontFamilyFont); @@ -101,7 +180,7 @@ public class FontResourcesParser { while (parser.next() != XmlPullParser.END_TAG) { skip(parser); } - return new FontConfig.Font(filename, 0, null, weight, isItalic, resourceId); + return new FontFileResourceEntry(filename, weight, isItalic, resourceId); } private static void skip(XmlPullParser parser) throws XmlPullParserException, IOException { diff --git a/core/java/android/content/res/ResourcesImpl.java b/core/java/android/content/res/ResourcesImpl.java index 38efa4901553..949d64458ae5 100644 --- a/core/java/android/content/res/ResourcesImpl.java +++ b/core/java/android/content/res/ResourcesImpl.java @@ -769,8 +769,13 @@ public class ResourcesImpl { if (file.endsWith("xml")) { final XmlResourceParser rp = loadXmlResourceParser( file, id, value.assetCookie, "font"); - final FontConfig config = FontResourcesParser.parse(rp, wrapper); - return Typeface.createFromResources(config, mAssets, file); + final FontResourcesParser.FamilyResourceEntry familyEntry = + FontResourcesParser.parse(rp, wrapper); + if (familyEntry == null) { + Log.e(TAG, "Failed to find font-family tag"); + return null; + } + return Typeface.createFromResources(familyEntry, mAssets, file); } return Typeface.createFromResources(mAssets, file, value.assetCookie); } catch (XmlPullParserException e) { @@ -796,20 +801,23 @@ public class ResourcesImpl { Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, file); try { + // TODO: Stop re-ussing font-family xml tag structure and use ResourceArray instead. final XmlResourceParser rp = loadXmlResourceParser( file, id, value.assetCookie, "font"); - final FontConfig config = FontResourcesParser.parse(rp, wrapper); - final List<FontConfig.Family> families = config.getFamilies(); - if (families == null || families.isEmpty()) { + final FontResourcesParser.FamilyResourceEntry familyEntry = + FontResourcesParser.parse(rp, wrapper); + if (familyEntry == null) { + Log.e(TAG, "failed to find font-family tag"); return; } - for (int j = 0; j < families.size(); j++) { - final FontConfig.Family family = families.get(j); - final List<FontConfig.Font> fonts = family.getFonts(); - for (int i = 0; i < fonts.size(); i++) { - int resourceId = fonts.get(i).getResourceId(); - wrapper.getFont(resourceId); - } + if (familyEntry instanceof FontResourcesParser.ProviderResourceEntry) { + throw new IllegalArgumentException("Provider based fonts can not be used."); + } + final FontResourcesParser.FontFamilyFilesResourceEntry filesEntry = + (FontResourcesParser.FontFamilyFilesResourceEntry) familyEntry; + for (FontResourcesParser.FontFileResourceEntry fileEntry : filesEntry.getEntries()) { + int resourceId = fileEntry.getResourceId(); + wrapper.getFont(resourceId); } } catch (XmlPullParserException e) { Log.e(TAG, "Failed to parse xml resource " + file, e); diff --git a/core/java/android/database/PageViewCursor.java b/core/java/android/database/PageViewCursor.java new file mode 100644 index 000000000000..fbd039d91742 --- /dev/null +++ b/core/java/android/database/PageViewCursor.java @@ -0,0 +1,245 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.database; + +import static com.android.internal.util.Preconditions.checkArgument; + +import android.annotation.Nullable; +import android.content.ContentResolver; +import android.os.Bundle; +import android.util.Log; +import android.util.MathUtils; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.ArrayUtils; + +/** + * Cursor wrapper that provides visibility into a subset of a wrapped cursor. + * + * The window is specified by offset and limit. + * + * @hide + */ +public final class PageViewCursor extends CrossProcessCursorWrapper { + + /** + * An extra added to results that are auto-paged using the wrapper. + */ + public static final String EXTRA_AUTO_PAGED = "android.content.extra.AUTO_PAGED"; + + private static final String TAG = "PageViewCursor"; + private static final boolean DEBUG = false; + private static final boolean VERBOSE = false; + + private final int mOffset; // aka first index + private final int mCount; + private final Bundle mExtras; + + private int mPos = -1; + + /** + * @see PageViewCursor#wrap(Cursor, Bundle) + */ + @VisibleForTesting + public PageViewCursor(Cursor cursor, int offset, int limit) { + super(cursor); + + checkArgument(offset > -1); + checkArgument(limit > -1); + + mOffset = offset; + + mExtras = new Bundle(); + Bundle extras = cursor.getExtras(); + if (extras != null) { + mExtras.putAll(extras); + } + mExtras.putBoolean(EXTRA_AUTO_PAGED, true); + + // We need a mutable bundle so we can add QUERY_RESULT_SIZE. + // Direct equality check is correct here. Bundle.EMPTY is a specific instance + // of Bundle that is immutable by way of implementation. + // mExtras = (extras == Bundle.EMPTY) ? new Bundle() : extras; + + // When we're wrapping another cursor, it should not already be "paged". + checkArgument(!mExtras.containsKey(ContentResolver.EXTRA_TOTAL_SIZE)); + + int count = mCursor.getCount(); + mExtras.putInt(ContentResolver.EXTRA_TOTAL_SIZE, count); + + mCount = MathUtils.constrain(count - offset, 0, limit); + + if (DEBUG) Log.d(TAG, "Wrapped cursor" + + " offset: " + mOffset + + ", limit: " + limit + + ", delegate_size: " + count + + ", paged_count: " + mCount); + } + + @Override + public Bundle getExtras() { + return mExtras; + } + + @Override + public int getPosition() { + return mPos; + } + + @Override + public boolean isBeforeFirst() { + if (mCount == 0) { + return true; + } + return mPos == -1; + } + + @Override + public boolean isAfterLast() { + if (mCount == 0) { + return true; + } + return mPos == mCount; + } + + @Override + public boolean isFirst() { + return mPos == 0; + } + + @Override + public boolean isLast() { + return mPos == mCount - 1; + } + + @Override + public boolean moveToFirst() { + return moveToPosition(0); + } + + @Override + public boolean moveToLast() { + return moveToPosition(mCount - 1); + } + + @Override + public boolean moveToNext() { + return move(1); + } + + @Override + public boolean moveToPrevious() { + return move(-1); + } + + @Override + public boolean move(int offset) { + return moveToPosition(mPos + offset); + } + + @Override + public boolean moveToPosition(int position) { + if (position >= mCount) { + if (VERBOSE) Log.v(TAG, "Invalid Positon: " + position + " >= count: " + mCount + + ". Moving to last record."); + mPos = mCount; + super.moveToPosition(mOffset + mPos); // move into "after last" state. + return false; + } + + // Make sure position isn't before the beginning of the cursor + if (position < 0) { + if (VERBOSE) Log.v(TAG, "Ignoring invalid move to position: " + position); + mPos = -1; + super.moveToPosition(mPos); + return false; + } + + if (position == mPos) { + if (VERBOSE) Log.v(TAG, "Ignoring no-op move to position: " + position); + return true; + } + + int delegatePosition = position + mOffset; + if (VERBOSE) Log.v(TAG, "Moving delegate cursor to position: " + delegatePosition); + if (super.moveToPosition(delegatePosition)) { + mPos = position; + return true; + } else { + mPos = -1; + super.moveToPosition(-1); + return false; + } + } + + @Override + public boolean onMove(int oldPosition, int newPosition) { + throw new UnsupportedOperationException("Not supported."); + } + + @Override + public int getCount() { + return mCount; + } + + /** + * Wraps the cursor such that it will honor paging args (if present), AND if the cursor + * does not report paging size. + * + * <p>No-op if cursor already contains paging or is less than specified page size. + */ + public static Cursor wrap(Cursor cursor, @Nullable Bundle queryArgs) { + + boolean hasPagingArgs = + queryArgs != null + && (queryArgs.containsKey(ContentResolver.QUERY_ARG_OFFSET) + || queryArgs.containsKey(ContentResolver.QUERY_ARG_LIMIT)); + + if (!hasPagingArgs) { + if (VERBOSE) Log.d(TAG, "No-wrap: No paging args in request."); + return cursor; + } + + if (hasPagedResponseDetails(cursor.getExtras())) { + if (VERBOSE) Log.d(TAG, "No-wrap. Cursor has paging details."); + return cursor; + } + + return new PageViewCursor( + cursor, + queryArgs.getInt(ContentResolver.QUERY_ARG_OFFSET, 0), + queryArgs.getInt(ContentResolver.QUERY_ARG_LIMIT, Integer.MAX_VALUE)); + } + + /** + * @return true if the extras contains information indicating the associated + * cursor is paged. + */ + private static boolean hasPagedResponseDetails(@Nullable Bundle extras) { + if (extras != null && extras.containsKey(ContentResolver.EXTRA_TOTAL_SIZE)) { + return true; + } + + String[] honoredArgs = extras.getStringArray(ContentResolver.EXTRA_HONORED_ARGS); + if (honoredArgs != null && ( + ArrayUtils.contains(honoredArgs, ContentResolver.QUERY_ARG_OFFSET) + || ArrayUtils.contains(honoredArgs, ContentResolver.QUERY_ARG_LIMIT))) { + return true; + } + + return false; + } +} diff --git a/core/java/android/hardware/LegacySensorManager.java b/core/java/android/hardware/LegacySensorManager.java index f95909385fb9..f5cf3f74bc38 100644 --- a/core/java/android/hardware/LegacySensorManager.java +++ b/core/java/android/hardware/LegacySensorManager.java @@ -16,6 +16,8 @@ package android.hardware; +import static android.view.Display.DEFAULT_DISPLAY; + import android.os.RemoteException; import android.os.ServiceManager; import android.view.IRotationWatcher; @@ -57,8 +59,7 @@ final class LegacySensorManager { public void onRotationChanged(int rotation) { LegacySensorManager.onRotationChanged(rotation); } - } - ); + }, DEFAULT_DISPLAY); } catch (RemoteException e) { } } diff --git a/core/java/android/net/ConnectivityMetricsEvent.java b/core/java/android/net/ConnectivityMetricsEvent.java index 6fdc739a5f54..4faff62c2655 100644 --- a/core/java/android/net/ConnectivityMetricsEvent.java +++ b/core/java/android/net/ConnectivityMetricsEvent.java @@ -19,39 +19,40 @@ package android.net; import android.os.Parcel; import android.os.Parcelable; -/** {@hide} */ +/** + * Represents a core networking event defined in package android.net.metrics. + * Logged by IpConnectivityLog and managed by ConnectivityMetrics service. + * {@hide} + * */ public final class ConnectivityMetricsEvent implements Parcelable { - /** The time when this event was collected, as returned by System.currentTimeMillis(). */ - final public long timestamp; - - /** The subsystem that generated the event. One of the COMPONENT_TAG_xxx constants. */ - final public int componentTag; - - /** The subsystem-specific event ID. */ - final public int eventTag; - + /** Time when this event was collected, as returned by System.currentTimeMillis(). */ + public long timestamp; + /** Transports of the network associated with the event, as defined in NetworkCapabilities. */ + public long transports; + /** Network id of the network associated with the event, or 0 if unspecified. */ + public int netId; + /** Name of the network interface associated with the event, or null if unspecified. */ + public String ifname; /** Opaque event-specific data. */ - final public Parcelable data; + public Parcelable data; - public ConnectivityMetricsEvent(long timestamp, int componentTag, - int eventTag, Parcelable data) { - this.timestamp = timestamp; - this.componentTag = componentTag; - this.eventTag = eventTag; - this.data = data; + public ConnectivityMetricsEvent() { + } + + private ConnectivityMetricsEvent(Parcel in) { + timestamp = in.readLong(); + transports = in.readLong(); + netId = in.readInt(); + ifname = in.readString(); + data = in.readParcelable(null); } /** Implement the Parcelable interface */ public static final Parcelable.Creator<ConnectivityMetricsEvent> CREATOR = new Parcelable.Creator<ConnectivityMetricsEvent> (){ public ConnectivityMetricsEvent createFromParcel(Parcel source) { - final long timestamp = source.readLong(); - final int componentTag = source.readInt(); - final int eventTag = source.readInt(); - final Parcelable data = source.readParcelable(null); - return new ConnectivityMetricsEvent(timestamp, componentTag, - eventTag, data); + return new ConnectivityMetricsEvent(source); } public ConnectivityMetricsEvent[] newArray(int size) { @@ -59,7 +60,6 @@ public final class ConnectivityMetricsEvent implements Parcelable { } }; - /** Implement the Parcelable interface */ @Override public int describeContents() { return 0; @@ -68,13 +68,15 @@ public final class ConnectivityMetricsEvent implements Parcelable { @Override public void writeToParcel(Parcel dest, int flags) { dest.writeLong(timestamp); - dest.writeInt(componentTag); - dest.writeInt(eventTag); + dest.writeLong(transports); + dest.writeInt(netId); + dest.writeString(ifname); dest.writeParcelable(data, 0); } + @Override public String toString() { - return String.format("ConnectivityMetricsEvent(%tT.%tL, %d, %d): %s", - timestamp, timestamp, componentTag, eventTag, data); + // TODO: add transports, netId, ifname + return String.format("ConnectivityMetricsEvent(%tT.%tL): %s", timestamp, timestamp, data); } } diff --git a/core/java/android/net/NetworkScoreManager.java b/core/java/android/net/NetworkScoreManager.java index e6fe0d0df76a..70ecf89dc155 100644 --- a/core/java/android/net/NetworkScoreManager.java +++ b/core/java/android/net/NetworkScoreManager.java @@ -419,6 +419,7 @@ public class NetworkScoreManager { * to connect to * @throws SecurityException if the caller does not hold the * {@link android.Manifest.permission#REQUEST_NETWORK_SCORES} permission. + * @hide */ public RecommendationResult requestRecommendation(RecommendationRequest request) throws SecurityException { diff --git a/core/java/android/net/metrics/ApfProgramEvent.java b/core/java/android/net/metrics/ApfProgramEvent.java index c2795a2ab956..ad4588f139f9 100644 --- a/core/java/android/net/metrics/ApfProgramEvent.java +++ b/core/java/android/net/metrics/ApfProgramEvent.java @@ -47,23 +47,19 @@ public final class ApfProgramEvent implements Parcelable { @Retention(RetentionPolicy.SOURCE) public @interface Flags {} - public final long lifetime; // Lifetime of the program in seconds - public final int filteredRas; // Number of RAs filtered by the APF program - public final int currentRas; // Total number of current RAs at generation time - public final int programLength; // Length of the APF program in bytes - public final int flags; // Bitfield compound of FLAG_* constants - - public ApfProgramEvent( - long lifetime, int filteredRas, int currentRas, int programLength, @Flags int flags) { - this.lifetime = lifetime; - this.filteredRas = filteredRas; - this.currentRas = currentRas; - this.programLength = programLength; - this.flags = flags; + public long lifetime; // Maximum computed lifetime of the program in seconds + public long actualLifetime; // Effective program lifetime in seconds + public int filteredRas; // Number of RAs filtered by the APF program + public int currentRas; // Total number of current RAs at generation time + public int programLength; // Length of the APF program in bytes + public int flags; // Bitfield compound of FLAG_* constants + + public ApfProgramEvent() { } private ApfProgramEvent(Parcel in) { this.lifetime = in.readLong(); + this.actualLifetime = in.readLong(); this.filteredRas = in.readInt(); this.currentRas = in.readInt(); this.programLength = in.readInt(); @@ -73,6 +69,7 @@ public final class ApfProgramEvent implements Parcelable { @Override public void writeToParcel(Parcel out, int flags) { out.writeLong(lifetime); + out.writeLong(actualLifetime); out.writeInt(filteredRas); out.writeInt(currentRas); out.writeInt(programLength); @@ -87,8 +84,8 @@ public final class ApfProgramEvent implements Parcelable { @Override public String toString() { String lifetimeString = (lifetime < Long.MAX_VALUE) ? lifetime + "s" : "forever"; - return String.format("ApfProgramEvent(%d/%d RAs %dB %s %s)", - filteredRas, currentRas, programLength, lifetimeString, namesOf(flags)); + return String.format("ApfProgramEvent(%d/%d RAs %dB %ds/%s %s)", filteredRas, currentRas, + programLength, actualLifetime, lifetimeString, namesOf(flags)); } public static final Parcelable.Creator<ApfProgramEvent> CREATOR diff --git a/core/java/android/net/metrics/ApfStats.java b/core/java/android/net/metrics/ApfStats.java index f8d7fa9d2e8d..3b0dc7efc45e 100644 --- a/core/java/android/net/metrics/ApfStats.java +++ b/core/java/android/net/metrics/ApfStats.java @@ -25,25 +25,28 @@ import android.os.Parcelable; */ public final class ApfStats implements Parcelable { - public final long durationMs; // time interval in milliseconds these stastistics covers - public final int receivedRas; // number of received RAs - public final int matchingRas; // number of received RAs matching a known RA - public final int droppedRas; // number of received RAs ignored due to the MAX_RAS limit - public final int zeroLifetimeRas; // number of received RAs with a minimum lifetime of 0 - public final int parseErrors; // number of received RAs that could not be parsed - public final int programUpdates; // number of APF program updates - public final int maxProgramSize; // maximum APF program size advertised by hardware + /** time interval in milliseconds these stastistics covers. */ + public long durationMs; + /** number of received RAs. */ + public int receivedRas; + /** number of received RAs matching a known RA. */ + public int matchingRas; + /** number of received RAs ignored due to the MAX_RAS limit. */ + public int droppedRas; + /** number of received RAs with a minimum lifetime of 0. */ + public int zeroLifetimeRas; + /** number of received RAs that could not be parsed. */ + public int parseErrors; + /** number of APF program updates from receiving RAs.. */ + public int programUpdates; + /** total number of APF program updates. */ + public int programUpdatesAll; + /** number of APF program updates from allowing multicast traffic. */ + public int programUpdatesAllowingMulticast; + /** maximum APF program size advertised by hardware. */ + public int maxProgramSize; - public ApfStats(long durationMs, int receivedRas, int matchingRas, int droppedRas, - int zeroLifetimeRas, int parseErrors, int programUpdates, int maxProgramSize) { - this.durationMs = durationMs; - this.receivedRas = receivedRas; - this.matchingRas = matchingRas; - this.droppedRas = droppedRas; - this.zeroLifetimeRas = zeroLifetimeRas; - this.parseErrors = parseErrors; - this.programUpdates = programUpdates; - this.maxProgramSize = maxProgramSize; + public ApfStats() { } private ApfStats(Parcel in) { @@ -54,6 +57,8 @@ public final class ApfStats implements Parcelable { this.zeroLifetimeRas = in.readInt(); this.parseErrors = in.readInt(); this.programUpdates = in.readInt(); + this.programUpdatesAll = in.readInt(); + this.programUpdatesAllowingMulticast = in.readInt(); this.maxProgramSize = in.readInt(); } @@ -66,6 +71,8 @@ public final class ApfStats implements Parcelable { out.writeInt(zeroLifetimeRas); out.writeInt(parseErrors); out.writeInt(programUpdates); + out.writeInt(programUpdatesAll); + out.writeInt(programUpdatesAllowingMulticast); out.writeInt(maxProgramSize); } @@ -83,8 +90,9 @@ public final class ApfStats implements Parcelable { .append(String.format("%d matching, ", matchingRas)) .append(String.format("%d dropped, ", droppedRas)) .append(String.format("%d zero lifetime, ", zeroLifetimeRas)) - .append(String.format("%d parse errors, ", parseErrors)) - .append(String.format("%d program updates})", programUpdates)) + .append(String.format("%d parse errors}, ", parseErrors)) + .append(String.format("updates: {all: %d, RAs: %d, allow multicast: %d})", + programUpdatesAll, programUpdates, programUpdatesAllowingMulticast)) .toString(); } diff --git a/core/java/android/net/metrics/IpConnectivityLog.java b/core/java/android/net/metrics/IpConnectivityLog.java index 173e5fd0fbc1..79094c02b272 100644 --- a/core/java/android/net/metrics/IpConnectivityLog.java +++ b/core/java/android/net/metrics/IpConnectivityLog.java @@ -60,21 +60,23 @@ public class IpConnectivityLog { } /** - * Log an IpConnectivity event. - * @param timestamp is the epoch timestamp of the event in ms. - * @param data is a Parcelable instance representing the event. + * Log a ConnectivityMetricsEvent. + * @param ev the event to log. If the event timestamp is 0, + * the timestamp is set to the current time in milliseconds. * @return true if the event was successfully logged. */ - public boolean log(long timestamp, Parcelable data) { + public boolean log(ConnectivityMetricsEvent ev) { if (!checkLoggerService()) { if (DBG) { Log.d(TAG, SERVICE_NAME + " service was not ready"); } return false; } - + if (ev.timestamp == 0) { + ev.timestamp = System.currentTimeMillis(); + } try { - int left = mService.logEvent(new ConnectivityMetricsEvent(timestamp, 0, 0, data)); + int left = mService.logEvent(ev); return left >= 0; } catch (RemoteException e) { Log.e(TAG, "Error logging event", e); @@ -82,7 +84,31 @@ public class IpConnectivityLog { } } - public void log(Parcelable event) { - log(System.currentTimeMillis(), event); + /** + * Log an IpConnectivity event. + * @param timestamp is the epoch timestamp of the event in ms. + * If the timestamp is 0, the timestamp is set to the current time in milliseconds. + * @param data is a Parcelable instance representing the event. + * @return true if the event was successfully logged. + */ + public boolean log(long timestamp, Parcelable data) { + ConnectivityMetricsEvent ev = makeEv(data); + ev.timestamp = timestamp; + return log(ev); + } + + /** + * Log an IpConnectivity event. + * @param data is a Parcelable instance representing the event. + * @return true if the event was successfully logged. + */ + public boolean log(Parcelable data) { + return log(makeEv(data)); + } + + private static ConnectivityMetricsEvent makeEv(Parcelable data) { + ConnectivityMetricsEvent ev = new ConnectivityMetricsEvent(); + ev.data = data; + return ev; } } diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java index dc170ed259de..29884b132e5a 100644 --- a/core/java/android/os/BatteryStats.java +++ b/core/java/android/os/BatteryStats.java @@ -2042,8 +2042,8 @@ public abstract class BatteryStats implements Parcelable { public static final String[] HISTORY_EVENT_NAMES = new String[] { "null", "proc", "fg", "top", "sync", "wake_lock_in", "job", "user", "userfg", "conn", - "active", "pkginst", "pkgunin", "alarm", "stats", "inactive", "active", "tmpwhitelist", - "screenwake", "wakeupap", "longwake", "est_capacity" + "active", "pkginst", "pkgunin", "alarm", "stats", "pkginactive", "pkgactive", + "tmpwhitelist", "screenwake", "wakeupap", "longwake", "est_capacity" }; public static final String[] HISTORY_EVENT_CHECKIN_NAMES = new String[] { diff --git a/core/java/android/os/FileBridge.java b/core/java/android/os/FileBridge.java index 0acf24bac52a..3ac88c564f41 100644 --- a/core/java/android/os/FileBridge.java +++ b/core/java/android/os/FileBridge.java @@ -41,7 +41,9 @@ import java.util.Arrays; * hands-off. * * @hide + * @deprecated replaced by {@link RevocableFileDescriptor} */ +@Deprecated public class FileBridge extends Thread { private static final String TAG = "FileBridge"; diff --git a/core/java/android/os/RemoteCallbackList.java b/core/java/android/os/RemoteCallbackList.java index 9db58eec3e1a..6656b00d457c 100644 --- a/core/java/android/os/RemoteCallbackList.java +++ b/core/java/android/os/RemoteCallbackList.java @@ -18,6 +18,8 @@ package android.os; import android.util.ArrayMap; +import java.util.function.Consumer; + /** * Takes care of the grunt work of maintaining a list of remote interfaces, * typically for the use of performing callbacks from a @@ -308,6 +310,23 @@ public class RemoteCallbackList<E extends IInterface> { } /** + * Performs {@code action} on each callback, calling + * {@link #beginBroadcast()}/{@link #finishBroadcast()} before/after looping + * + * @hide + */ + public void broadcast(Consumer<E> action) { + int itemCount = beginBroadcast(); + try { + for (int i = 0; i < itemCount; i++) { + action.accept(getBroadcastItem(i)); + } + } finally { + finishBroadcast(); + } + } + + /** * Returns the number of registered callbacks. Note that the number of registered * callbacks may differ from the value returned by {@link #beginBroadcast()} since * the former returns the number of callbacks registered at the time of the call diff --git a/core/java/android/os/RevocableFileDescriptor.java b/core/java/android/os/RevocableFileDescriptor.java new file mode 100644 index 000000000000..a750ce6158fb --- /dev/null +++ b/core/java/android/os/RevocableFileDescriptor.java @@ -0,0 +1,161 @@ +/* + * 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.os; + +import android.content.Context; +import android.os.storage.StorageManager; +import android.system.ErrnoException; +import android.system.Os; +import android.system.OsConstants; +import android.util.Slog; + +import libcore.io.IoUtils; + +import java.io.File; +import java.io.FileDescriptor; +import java.io.IOException; +import java.io.InterruptedIOException; + +/** + * Variant of {@link FileDescriptor} that allows its creator to revoke all + * access to the underlying resource. + * <p> + * This is useful when the code that originally opened a file needs to strongly + * assert that any clients are completely hands-off for security purposes. + * + * @hide + */ +public class RevocableFileDescriptor { + private static final String TAG = "RevocableFileDescriptor"; + private static final boolean DEBUG = true; + + private FileDescriptor mInner; + private ParcelFileDescriptor mOuter; + + private volatile boolean mRevoked; + + /** {@hide} */ + public RevocableFileDescriptor() { + } + + /** + * Create an instance that references the given {@link File}. + */ + public RevocableFileDescriptor(Context context, File file) throws IOException { + try { + init(context, Os.open(file.getAbsolutePath(), + OsConstants.O_CREAT | OsConstants.O_RDWR, 0700)); + } catch (ErrnoException e) { + throw e.rethrowAsIOException(); + } + } + + /** + * Create an instance that references the given {@link FileDescriptor}. + */ + public RevocableFileDescriptor(Context context, FileDescriptor fd) throws IOException { + init(context, fd); + } + + /** {@hide} */ + public void init(Context context, FileDescriptor fd) throws IOException { + mInner = fd; + mOuter = context.getSystemService(StorageManager.class) + .openProxyFileDescriptor(ParcelFileDescriptor.MODE_READ_WRITE, mCallback); + } + + /** + * Return a {@link ParcelFileDescriptor} which can safely be passed to an + * untrusted process. After {@link #revoke()} is called, all operations will + * fail with {@link OsConstants#EPERM}. + */ + public ParcelFileDescriptor getRevocableFileDescriptor() { + return mOuter; + } + + /** + * Revoke all future access to the {@link ParcelFileDescriptor} returned by + * {@link #getRevocableFileDescriptor()}. From this point forward, all + * operations will fail with {@link OsConstants#EPERM}. + */ + public void revoke() { + mRevoked = true; + IoUtils.closeQuietly(mInner); + } + + public boolean isRevoked() { + return mRevoked; + } + + private final ProxyFileDescriptorCallback mCallback = new ProxyFileDescriptorCallback() { + private void checkRevoked() throws ErrnoException { + if (mRevoked) { + throw new ErrnoException(TAG, OsConstants.EPERM); + } + } + + @Override + public long onGetSize() throws ErrnoException { + checkRevoked(); + return Os.fstat(mInner).st_size; + } + + @Override + public int onRead(long offset, int size, byte[] data) throws ErrnoException { + checkRevoked(); + int n = 0; + while (n < size) { + try { + n += Os.pread(mInner, data, n, size - n, offset + n); + break; + } catch (InterruptedIOException e) { + n += e.bytesTransferred; + } + } + return n; + } + + @Override + public int onWrite(long offset, int size, byte[] data) throws ErrnoException { + checkRevoked(); + int n = 0; + while (n < size) { + try { + n += Os.pwrite(mInner, data, n, size - n, offset + n); + break; + } catch (InterruptedIOException e) { + n += e.bytesTransferred; + } + } + return n; + } + + @Override + public void onFsync() throws ErrnoException { + if (DEBUG) Slog.v(TAG, "onFsync()"); + checkRevoked(); + Os.fsync(mInner); + } + + @Override + public void onRelease() { + if (DEBUG) Slog.v(TAG, "onRelease()"); + mRevoked = true; + IoUtils.closeQuietly(mInner); + } + }; +} diff --git a/core/java/android/os/SystemProperties.java b/core/java/android/os/SystemProperties.java index b525193c1b9c..8632194f2b02 100644 --- a/core/java/android/os/SystemProperties.java +++ b/core/java/android/os/SystemProperties.java @@ -35,6 +35,12 @@ public class SystemProperties { private static final String TAG = "SystemProperties"; private static final boolean TRACK_KEY_ACCESS = false; + /** + * Android O removed the property name length limit, but com.amazon.kindle 7.8.1.5 + * uses reflection to read this whenever text is selected (http://b/36095274). + */ + public static final int PROP_NAME_MAX = Integer.MAX_VALUE; + public static final int PROP_VALUE_MAX = 91; private static final ArrayList<Runnable> sChangeCallbacks = new ArrayList<Runnable>(); diff --git a/core/java/android/os/storage/StorageVolume.java b/core/java/android/os/storage/StorageVolume.java index 46f2d387ac52..1fc0b820cf06 100644 --- a/core/java/android/os/storage/StorageVolume.java +++ b/core/java/android/os/storage/StorageVolume.java @@ -373,8 +373,7 @@ public final class StorageVolume implements Parcelable { } /** {@hide} */ - // TODO(b/26742218): find out where toString() is called internally and replace these calls by - // dump(). + // TODO: find out where toString() is called internally and replace these calls by dump(). public String dump() { final CharArrayWriter writer = new CharArrayWriter(); dump(new IndentingPrintWriter(writer, " ", 80)); diff --git a/core/java/android/preference/Preference.java b/core/java/android/preference/Preference.java index 443a3e968b9c..d9c749a65d0f 100644 --- a/core/java/android/preference/Preference.java +++ b/core/java/android/preference/Preference.java @@ -81,6 +81,7 @@ import java.util.Set; * @attr ref android.R.styleable#Preference_persistent * @attr ref android.R.styleable#Preference_defaultValue * @attr ref android.R.styleable#Preference_shouldDisableView + * @attr ref android.R.styleable#Preference_recycleEnabled */ public class Preference implements Comparable<Preference> { /** @@ -131,6 +132,7 @@ public class Preference implements Comparable<Preference> { private Object mDefaultValue; private boolean mDependencyMet = true; private boolean mParentDependencyMet = true; + private boolean mRecycleEnabled = true; /** * @see #setShouldDisableView(boolean) @@ -139,7 +141,6 @@ public class Preference implements Comparable<Preference> { private int mLayoutResId = com.android.internal.R.layout.preference; private int mWidgetLayoutResId; - private boolean mCanRecycleLayout = true; private OnPreferenceChangeInternalListener mListener; @@ -291,15 +292,13 @@ public class Preference implements Comparable<Preference> { case com.android.internal.R.styleable.Preference_shouldDisableView: mShouldDisableView = a.getBoolean(attr, mShouldDisableView); break; + + case com.android.internal.R.styleable.Preference_recycleEnabled: + mRecycleEnabled = a.getBoolean(attr, mRecycleEnabled); + break; } } a.recycle(); - - if (!getClass().getName().startsWith("android.preference") - && !getClass().getName().startsWith("com.android")) { - // For non-framework subclasses, assume the worst and don't cache views. - mCanRecycleLayout = false; - } } /** @@ -458,8 +457,8 @@ public class Preference implements Comparable<Preference> { } /** - * Return the extras Bundle object associated with this preference, - * returning null if there is not currently one. + * Return the extras Bundle object associated with this preference, returning {@code null} if + * there is not currently one. */ public Bundle peekExtras() { return mExtras; @@ -482,7 +481,7 @@ public class Preference implements Comparable<Preference> { public void setLayoutResource(@LayoutRes int layoutResId) { if (layoutResId != mLayoutResId) { // Layout changed - mCanRecycleLayout = false; + mRecycleEnabled = false; } mLayoutResId = layoutResId; @@ -511,7 +510,7 @@ public class Preference implements Comparable<Preference> { public void setWidgetLayoutResource(@LayoutRes int widgetLayoutResId) { if (widgetLayoutResId != mWidgetLayoutResId) { // Layout changed - mCanRecycleLayout = false; + mRecycleEnabled = false; } mWidgetLayoutResId = widgetLayoutResId; } @@ -653,15 +652,13 @@ public class Preference implements Comparable<Preference> { } /** - * Sets the order of this Preference with respect to other - * Preference objects on the same level. If this is not specified, the - * default behavior is to sort alphabetically. The - * {@link PreferenceGroup#setOrderingAsAdded(boolean)} can be used to order - * Preference objects based on the order they appear in the XML. + * Sets the order of this Preference with respect to other Preference objects on the same level. + * If this is not specified, the default behavior is to sort alphabetically. The + * {@link PreferenceGroup#setOrderingAsAdded(boolean)} can be used to order Preference objects + * based on the order they appear in the XML. * - * @param order The order for this Preference. A lower value will be shown - * first. Use {@link #DEFAULT_ORDER} to sort alphabetically or - * allow ordering from XML. + * @param order the order for this Preference. A lower value will be shown first. Use + * {@link #DEFAULT_ORDER} to sort alphabetically or allow ordering from XML * @see PreferenceGroup#setOrderingAsAdded(boolean) * @see #DEFAULT_ORDER */ @@ -669,16 +666,15 @@ public class Preference implements Comparable<Preference> { if (order != mOrder) { mOrder = order; - // Reorder the list + // Reorder the list notifyHierarchyChanged(); } } /** - * Gets the order of this Preference with respect to other Preference objects - * on the same level. + * Gets the order of this Preference with respect to other Preference objects on the same level. * - * @return The order of this Preference. + * @return the order of this Preference * @see #setOrder(int) */ public int getOrder() { @@ -686,12 +682,10 @@ public class Preference implements Comparable<Preference> { } /** - * Sets the title for this Preference with a CharSequence. - * This title will be placed into the ID - * {@link android.R.id#title} within the View created by - * {@link #onCreateView(ViewGroup)}. + * Sets the title for this Preference with a CharSequence. This title will be placed into the ID + * {@link android.R.id#title} within the View created by {@link #onCreateView(ViewGroup)}. * - * @param title The title for this Preference. + * @param title the title for this Preference */ public void setTitle(CharSequence title) { if (title == null && mTitle != null || title != null && !title.equals(mTitle)) { @@ -702,10 +696,10 @@ public class Preference implements Comparable<Preference> { } /** - * Sets the title for this Preference with a resource ID. + * Sets the title for this Preference with a resource ID. * * @see #setTitle(CharSequence) - * @param titleResId The title as a resource ID. + * @param titleResId the title as a resource ID */ public void setTitle(@StringRes int titleResId) { setTitle(mContext.getString(titleResId)); @@ -713,10 +707,10 @@ public class Preference implements Comparable<Preference> { } /** - * Returns the title resource ID of this Preference. If the title did - * not come from a resource, 0 is returned. + * Returns the title resource ID of this Preference. If the title did not come from a resource, + * {@code 0} is returned. * - * @return The title resource. + * @return the title resource * @see #setTitle(int) */ @StringRes @@ -727,7 +721,7 @@ public class Preference implements Comparable<Preference> { /** * Returns the title of this Preference. * - * @return The title. + * @return the title * @see #setTitle(CharSequence) */ public CharSequence getTitle() { @@ -735,12 +729,10 @@ public class Preference implements Comparable<Preference> { } /** - * Sets the icon for this Preference with a Drawable. - * This icon will be placed into the ID - * {@link android.R.id#icon} within the View created by - * {@link #onCreateView(ViewGroup)}. + * Sets the icon for this Preference with a Drawable. This icon will be placed into the ID + * {@link android.R.id#icon} within the View created by {@link #onCreateView(ViewGroup)}. * - * @param icon The optional icon for this Preference. + * @param icon the optional icon for this Preference */ public void setIcon(Drawable icon) { if ((icon == null && mIcon != null) || (icon != null && mIcon != icon)) { @@ -751,10 +743,10 @@ public class Preference implements Comparable<Preference> { } /** - * Sets the icon for this Preference with a resource ID. + * Sets the icon for this Preference with a resource ID. * * @see #setIcon(Drawable) - * @param iconResId The icon as a resource ID. + * @param iconResId the icon as a resource ID */ public void setIcon(@DrawableRes int iconResId) { if (mIconResId != iconResId) { @@ -766,7 +758,7 @@ public class Preference implements Comparable<Preference> { /** * Returns the icon of this Preference. * - * @return The icon. + * @return the icon * @see #setIcon(Drawable) */ public Drawable getIcon() { @@ -779,7 +771,7 @@ public class Preference implements Comparable<Preference> { /** * Returns the summary of this Preference. * - * @return The summary. + * @return the summary * @see #setSummary(CharSequence) */ public CharSequence getSummary() { @@ -787,9 +779,9 @@ public class Preference implements Comparable<Preference> { } /** - * Sets the summary for this Preference with a CharSequence. + * Sets the summary for this Preference with a CharSequence. * - * @param summary The summary for the preference. + * @param summary the summary for the preference */ public void setSummary(CharSequence summary) { if (summary == null && mSummary != null || summary != null && !summary.equals(mSummary)) { @@ -799,10 +791,10 @@ public class Preference implements Comparable<Preference> { } /** - * Sets the summary for this Preference with a resource ID. + * Sets the summary for this Preference with a resource ID. * * @see #setSummary(CharSequence) - * @param summaryResId The summary as a resource. + * @param summaryResId the summary as a resource */ public void setSummary(@StringRes int summaryResId) { setSummary(mContext.getString(summaryResId)); @@ -812,7 +804,7 @@ public class Preference implements Comparable<Preference> { * Sets whether this Preference is enabled. If disabled, it will * not handle clicks. * - * @param enabled Set true to enable it. + * @param enabled set {@code true} to enable it */ public void setEnabled(boolean enabled) { if (mEnabled != enabled) { @@ -828,7 +820,7 @@ public class Preference implements Comparable<Preference> { /** * Checks whether this Preference should be enabled in the list. * - * @return True if this Preference is enabled, false otherwise. + * @return {@code true} if this Preference is enabled, false otherwise */ public boolean isEnabled() { return mEnabled && mDependencyMet && mParentDependencyMet; @@ -837,7 +829,7 @@ public class Preference implements Comparable<Preference> { /** * Sets whether this Preference is selectable. * - * @param selectable Set true to make it selectable. + * @param selectable set {@code true} to make it selectable */ public void setSelectable(boolean selectable) { if (mSelectable != selectable) { @@ -849,22 +841,21 @@ public class Preference implements Comparable<Preference> { /** * Checks whether this Preference should be selectable in the list. * - * @return True if it is selectable, false otherwise. + * @return {@code true} if it is selectable, {@code false} otherwise */ public boolean isSelectable() { return mSelectable; } /** - * Sets whether this Preference should disable its view when it gets - * disabled. - * <p> - * For example, set this and {@link #setEnabled(boolean)} to false for - * preferences that are only displaying information and 1) should not be - * clickable 2) should not have the view set to the disabled state. + * Sets whether this Preference should disable its view when it gets disabled. * - * @param shouldDisableView Set true if this preference should disable its view - * when the preference is disabled. + * <p>For example, set this and {@link #setEnabled(boolean)} to false for preferences that are + * only displaying information and 1) should not be clickable 2) should not have the view set to + * the disabled state. + * + * @param shouldDisableView set {@code true} if this preference should disable its view when + * the preference is disabled */ public void setShouldDisableView(boolean shouldDisableView) { mShouldDisableView = shouldDisableView; @@ -873,14 +864,45 @@ public class Preference implements Comparable<Preference> { /** * Checks whether this Preference should disable its view when it's action is disabled. + * * @see #setShouldDisableView(boolean) - * @return True if it should disable the view. + * @return {@code true} if it should disable the view */ public boolean getShouldDisableView() { return mShouldDisableView; } /** + * Sets whether this Preference has enabled to have its view recycled when used in the list + * view. By default the recycling is enabled. + * + * <p>The value can be changed only before this preference is added to the preference hierarchy. + * + * <p>If view recycling is not allowed then each time the list view populates this preference + * the {@link #getView(View, ViewGroup)} method receives a {@code null} convert view and needs + * to recreate the view. Otherwise view gets recycled and only {@link #onBindView(View)} gets + * called. + * + * @param enabled set {@code true} if this preference view should be recycled + */ + @CallSuper + public void setRecycleEnabled(boolean enabled) { + mRecycleEnabled = enabled; + notifyChanged(); + } + + /** + * Checks whether this Preference has enabled to have its view recycled when used in the list + * view. + * + * @see #setRecycleEnabled(boolean) + * @return {@code true} if this preference view should be recycled + */ + public boolean isRecycleEnabled() { + return mRecycleEnabled; + } + + /** * Returns a unique ID for this Preference. This ID should be unique across all * Preference objects in a hierarchy. * @@ -974,7 +996,7 @@ public class Preference implements Comparable<Preference> { * the persistent {@link SharedPreferences} storage by default or into * {@link PreferenceDataStore} if assigned. * - * @param persistent Set true if it should store its value(s) into the {@link SharedPreferences}. + * @param persistent set {@code true} if it should store its value(s) into the storage. */ public void setPersistent(boolean persistent) { mPersistent = persistent; @@ -1035,7 +1057,7 @@ public class Preference implements Comparable<Preference> { * * @param preferenceScreen A {@link PreferenceScreen} whose hierarchy click * listener should be called in the proper order (between other - * processing). May be null. + * processing). May be {@code null}. * @hide */ public void performClick(PreferenceScreen preferenceScreen) { @@ -1102,9 +1124,9 @@ public class Preference implements Comparable<Preference> { * {@link SharedPreferences}, this is intended behavior to improve * performance. * - * @return The {@link SharedPreferences} where this Preference reads its value(s), or null if it - * isn't attached to a Preference hierarchy or if {@link PreferenceDataStore} is used - * instead. + * @return the {@link SharedPreferences} where this Preference reads its value(s). If + * this preference isn't attached to a Preference hierarchy or if + * a {@link PreferenceDataStore} has been set, this method returns {@code null}. * @see #getEditor() * @see #setPreferenceDataStore(PreferenceDataStore) */ @@ -1129,9 +1151,9 @@ public class Preference implements Comparable<Preference> { * not show up in the SharedPreferences, this is intended behavior to * improve performance. * - * @return A {@link SharedPreferences.Editor} where this preference saves its value(s), or null - * if it isn't attached to a Preference hierarchy or if {@link PreferenceDataStore} is - * used instead. + * @return a {@link SharedPreferences.Editor} where this preference saves its value(s). If + * this preference isn't attached to a Preference hierarchy or if + * a {@link PreferenceDataStore} has been set, this method returns {@code null}. * @see #shouldCommit() * @see #getSharedPreferences() * @see #setPreferenceDataStore(PreferenceDataStore) @@ -1149,7 +1171,7 @@ public class Preference implements Comparable<Preference> { * {@link #getEditor()}. This may return false in situations where batch * committing is being done (by the manager) to improve performance. * - * <p>If this preference is using {@link PreferenceDataStore} this value should be irrelevant. + * <p>If this preference is using {@link PreferenceDataStore} this value is irrelevant. * * @return Whether the Preference should commit its saved value(s). * @see #getEditor() @@ -1253,10 +1275,10 @@ public class Preference implements Comparable<Preference> { } /** - * Assigns a {@link PreferenceGroup} as the parent of this Preference. Set null to remove - * the current parent. + * Assigns a {@link PreferenceGroup} as the parent of this Preference. Set {@code null} to + * remove the current parent. * - * @param parentGroup Parent preference group of this Preference or null if none. + * @param parentGroup Parent preference group of this Preference or {@code null} if none. */ void assignParent(@Nullable PreferenceGroup parentGroup) { mParentGroup = parentGroup; @@ -1425,10 +1447,10 @@ public class Preference implements Comparable<Preference> { } /** - * Returns the {@link PreferenceGroup} which is this Preference assigned to or null if this - * preference is not assigned to any group or is a root Preference. + * Returns the {@link PreferenceGroup} which is this Preference assigned to or {@code null} if + * this preference is not assigned to any group or is a root Preference. * - * @return The parent PreferenceGroup or null if not attached to any. + * @return the parent PreferenceGroup or {@code null} if not attached to any */ @Nullable public PreferenceGroup getParent() { @@ -1483,7 +1505,7 @@ public class Preference implements Comparable<Preference> { * if {@link #shouldPersist()} is true). * * <p>In case of using {@link PreferenceDataStore}, the <var>restorePersistedValue</var> is - * always false. But the default value (if provided) is set. + * always {@code true}. But the default value (if provided) is set. * * <p>This may not always be called. One example is if it should not persist * but there is no default value given. @@ -1831,10 +1853,6 @@ public class Preference implements Comparable<Preference> { return mPreferenceManager.getSharedPreferences().getBoolean(mKey, defaultReturnValue); } - boolean canRecycleLayout() { - return mCanRecycleLayout; - } - @Override public String toString() { return getFilterableStringBuilder().toString(); @@ -1910,9 +1928,9 @@ public class Preference implements Comparable<Preference> { * state. This state should only contain information that is not persistent * or can be reconstructed later. * - * @return A Parcelable object containing the current dynamic state of - * this Preference, or null if there is nothing interesting to save. - * The default implementation returns null. + * @return A Parcelable object containing the current dynamic state of this Preference, or + * {@code null} if there is nothing interesting to save. The default implementation + * returns {@code null}. * @see #onRestoreInstanceState * @see #saveHierarchyState */ @@ -1958,9 +1976,9 @@ public class Preference implements Comparable<Preference> { } /** - * Hook allowing a Preference to re-apply a representation of its internal - * state that had previously been generated by {@link #onSaveInstanceState}. - * This function will never be called with a null state. + * Hook allowing a Preference to re-apply a representation of its internal state that had + * previously been generated by {@link #onSaveInstanceState}. This function will never be called + * with a {@code null} state. * * @param state The saved state that had previously been returned by * {@link #onSaveInstanceState}. diff --git a/core/java/android/preference/PreferenceGroupAdapter.java b/core/java/android/preference/PreferenceGroupAdapter.java index 5a0b9e953c16..bee45ab0fe32 100644 --- a/core/java/android/preference/PreferenceGroupAdapter.java +++ b/core/java/android/preference/PreferenceGroupAdapter.java @@ -164,7 +164,7 @@ public class PreferenceGroupAdapter extends BaseAdapter preferences.add(preference); - if (!mHasReturnedViewTypeCount && preference.canRecycleLayout()) { + if (!mHasReturnedViewTypeCount && preference.isRecycleEnabled()) { addPreferenceClassName(preference); } @@ -296,7 +296,7 @@ public class PreferenceGroupAdapter extends BaseAdapter } final Preference preference = this.getItem(position); - if (!preference.canRecycleLayout()) { + if (!preference.isRecycleEnabled()) { return IGNORE_ITEM_VIEW_TYPE; } diff --git a/core/java/android/preference/PreferenceManager.java b/core/java/android/preference/PreferenceManager.java index 14b748a9c6df..ea32dfd7f919 100644 --- a/core/java/android/preference/PreferenceManager.java +++ b/core/java/android/preference/PreferenceManager.java @@ -93,8 +93,8 @@ public class PreferenceManager { private SharedPreferences mSharedPreferences; /** - * Data store to be used by the Preferences or null if {@link android.content.SharedPreferences} - * should be used. + * Data store to be used by the Preferences or {@code null} if + * {@link android.content.SharedPreferences} should be used. */ @Nullable private PreferenceDataStore mPreferenceDataStore; @@ -484,11 +484,16 @@ public class PreferenceManager { /** * Gets a {@link SharedPreferences} instance that preferences managed by this will use. * - * @return A {@link SharedPreferences} instance pointing to the file that contains the values of - * preferences that are managed by this or null if {@link PreferenceDataStore} is used instead. + * @return a {@link SharedPreferences} instance pointing to the file that contains the values of + * preferences that are managed by this PreferenceManager. If a + * {@link PreferenceDataStore} has been set, this method returns {@code null}. */ public SharedPreferences getSharedPreferences() { - if (mSharedPreferences == null && getPreferenceDataStore() == null) { + if (mPreferenceDataStore != null) { + return null; + } + + if (mSharedPreferences == null) { final Context storageContext; switch (mStorage) { case STORAGE_DEVICE_PROTECTED: @@ -564,8 +569,8 @@ public class PreferenceManager { /** * Finds a {@link Preference} based on its key. * - * @param key The key of the preference to retrieve. - * @return The {@link Preference} with the key, or null. + * @param key the key of the preference to retrieve + * @return the {@link Preference} with the key, or {@code null} * @see PreferenceGroup#findPreference(CharSequence) */ @Nullable @@ -659,11 +664,11 @@ public class PreferenceManager { /** * Returns an editor to use when modifying the shared preferences. - * <p> - * Do NOT commit unless {@link #shouldCommit()} returns true. * - * @return An editor to use to write to shared preferences or null if - * {@link PreferenceDataStore} is used instead. + * <p>Do NOT commit unless {@link #shouldCommit()} returns true. + * + * @return an editor to use to write to shared preferences. If a {@link PreferenceDataStore} + * has been set, this method returns {@code null}. * @see #shouldCommit() */ SharedPreferences.Editor getEditor() { @@ -687,6 +692,8 @@ public class PreferenceManager { * {@link #getEditor()}. This will return false in cases where the writes * should be batched, for example when inflating preferences from XML. * + * <p>If preferences are using {@link PreferenceDataStore} this value is irrelevant. + * * @return Whether the client should commit. */ boolean shouldCommit() { @@ -711,8 +718,8 @@ public class PreferenceManager { * Returns the activity that shows the preferences. This is useful for doing * managed queries, but in most cases the use of {@link #getContext()} is * preferred. - * <p> - * This will return null if this class was instantiated with a Context + * + * <p>This will return {@code null} if this class was instantiated with a Context * instead of Activity. For example, when setting the default values. * * @return The activity that shows the preferences. diff --git a/core/java/android/provider/ContactsContract.java b/core/java/android/provider/ContactsContract.java index a0d16bc2d9b0..dac835463c02 100644 --- a/core/java/android/provider/ContactsContract.java +++ b/core/java/android/provider/ContactsContract.java @@ -44,7 +44,6 @@ import android.text.TextUtils; import android.util.DisplayMetrics; import android.util.Pair; import android.view.View; - import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; @@ -8913,11 +8912,15 @@ public final class ContactsContract { * ambiguous then the activity should prompt the user for the recipient to send the message * to. * <p> + * Voice Assistant may provide additional information to messaging app about which account + * to use for sending a message by populating {@link #EXTRA_SENDER_ACCOUNT_HASH}. + * <p> * Output: nothing * * @see #EXTRA_RECIPIENT_CONTACT_URI * @see #EXTRA_RECIPIENT_CONTACT_CHAT_ID * @see #EXTRA_RECIPIENT_CONTACT_NAME + * @see #EXTRA_SENDER_ACCOUNT_HASH * @see #METADATA_ACCOUNT_TYPE * @see #METADATA_MIMETYPE */ @@ -8975,6 +8978,16 @@ public final class ContactsContract { "android.provider.extra.RECIPIENT_CONTACT_NAME"; /** + * This optional extra specifies the hash of the account that should be used by messaging + * app for sending voice message with {@link #ACTION_VOICE_SEND_MESSAGE_TO_CONTACTS}. The + * value of this extra is a {@code String} and should be the value of {@link + * android.accounts.Account#hashCode()} for some account returned by {@link + * android.accounts.AccountManager#getAccounts()}. + */ + public static final String EXTRA_SENDER_ACCOUNT_HASH = + "android.provider.extra.SENDER_ACCOUNT_HASH"; + + /** * A string associated with an {@link #ACTION_VOICE_SEND_MESSAGE_TO_CONTACTS} activity * describing {@link RawContacts#ACCOUNT_TYPE} for the corresponding Contacts Provider * implementation. diff --git a/core/java/android/provider/DocumentsContract.java b/core/java/android/provider/DocumentsContract.java index a95ccbf0854e..56d4ff79eb04 100644 --- a/core/java/android/provider/DocumentsContract.java +++ b/core/java/android/provider/DocumentsContract.java @@ -18,7 +18,6 @@ package android.provider; import static android.net.TrafficStats.KB_IN_BYTES; import static android.system.OsConstants.SEEK_SET; - import static com.android.internal.util.Preconditions.checkArgument; import static com.android.internal.util.Preconditions.checkCollectionElementsNotNull; import static com.android.internal.util.Preconditions.checkCollectionNotEmpty; @@ -29,6 +28,7 @@ import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.IntentSender; +import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.res.AssetFileDescriptor; import android.database.Cursor; @@ -38,6 +38,7 @@ import android.graphics.Matrix; import android.graphics.Point; import android.media.ExifInterface; import android.net.Uri; +import android.os.Build; import android.os.Bundle; import android.os.CancellationSignal; import android.os.OperationCanceledException; @@ -45,6 +46,7 @@ import android.os.Parcel; import android.os.ParcelFileDescriptor; import android.os.ParcelFileDescriptor.OnCloseListener; import android.os.Parcelable; +import android.os.ParcelableException; import android.os.RemoteException; import android.os.storage.StorageVolume; import android.system.ErrnoException; @@ -147,9 +149,9 @@ public final class DocumentsContract { /** * Action of intent issued by DocumentsUI when user wishes to open/configure/manage a particular * document in the provider application. - * + * * <p>When issued, the intent will include the URI of the document as the intent data. - * + * * <p>A provider wishing to provide support for this action should do two things. * <li>Add an {@code <intent-filter>} matching this action. * <li>When supplying information in {@link DocumentsProvider#queryChildDocuments}, include @@ -1023,7 +1025,8 @@ public final class DocumentsContract { * android.os.CancellationSignal) */ public static Bitmap getDocumentThumbnail( - ContentResolver resolver, Uri documentUri, Point size, CancellationSignal signal) { + ContentResolver resolver, Uri documentUri, Point size, CancellationSignal signal) + throws FileNotFoundException { final ContentProviderClient client = resolver.acquireUnstableContentProviderClient( documentUri.getAuthority()); try { @@ -1032,6 +1035,7 @@ public final class DocumentsContract { if (!(e instanceof OperationCanceledException)) { Log.w(TAG, "Failed to load thumbnail for " + documentUri + ": " + e); } + rethrowIfNecessary(resolver, e); return null; } finally { ContentProviderClient.releaseQuietly(client); @@ -1113,20 +1117,20 @@ public final class DocumentsContract { /** * Create a new document with given MIME type and display name. * - * @param parentDocumentUri directory with - * {@link Document#FLAG_DIR_SUPPORTS_CREATE} + * @param parentDocumentUri directory with {@link Document#FLAG_DIR_SUPPORTS_CREATE} * @param mimeType MIME type of new document * @param displayName name of new document * @return newly created document, or {@code null} if failed */ public static Uri createDocument(ContentResolver resolver, Uri parentDocumentUri, - String mimeType, String displayName) { + String mimeType, String displayName) throws FileNotFoundException { final ContentProviderClient client = resolver.acquireUnstableContentProviderClient( parentDocumentUri.getAuthority()); try { return createDocument(client, parentDocumentUri, mimeType, displayName); } catch (Exception e) { Log.w(TAG, "Failed to create document", e); + rethrowIfNecessary(resolver, e); return null; } finally { ContentProviderClient.releaseQuietly(client); @@ -1177,13 +1181,14 @@ public final class DocumentsContract { * failed. */ public static Uri renameDocument(ContentResolver resolver, Uri documentUri, - String displayName) { + String displayName) throws FileNotFoundException { final ContentProviderClient client = resolver.acquireUnstableContentProviderClient( documentUri.getAuthority()); try { return renameDocument(client, documentUri, displayName); } catch (Exception e) { Log.w(TAG, "Failed to rename document", e); + rethrowIfNecessary(resolver, e); return null; } finally { ContentProviderClient.releaseQuietly(client); @@ -1208,7 +1213,8 @@ public final class DocumentsContract { * @param documentUri document with {@link Document#FLAG_SUPPORTS_DELETE} * @return if the document was deleted successfully. */ - public static boolean deleteDocument(ContentResolver resolver, Uri documentUri) { + public static boolean deleteDocument(ContentResolver resolver, Uri documentUri) + throws FileNotFoundException { final ContentProviderClient client = resolver.acquireUnstableContentProviderClient( documentUri.getAuthority()); try { @@ -1216,6 +1222,7 @@ public final class DocumentsContract { return true; } catch (Exception e) { Log.w(TAG, "Failed to delete document", e); + rethrowIfNecessary(resolver, e); return false; } finally { ContentProviderClient.releaseQuietly(client); @@ -1240,13 +1247,14 @@ public final class DocumentsContract { * @return the copied document, or {@code null} if failed. */ public static Uri copyDocument(ContentResolver resolver, Uri sourceDocumentUri, - Uri targetParentDocumentUri) { + Uri targetParentDocumentUri) throws FileNotFoundException { final ContentProviderClient client = resolver.acquireUnstableContentProviderClient( sourceDocumentUri.getAuthority()); try { return copyDocument(client, sourceDocumentUri, targetParentDocumentUri); } catch (Exception e) { Log.w(TAG, "Failed to copy document", e); + rethrowIfNecessary(resolver, e); return null; } finally { ContentProviderClient.releaseQuietly(client); @@ -1274,7 +1282,7 @@ public final class DocumentsContract { * @return the moved document, or {@code null} if failed. */ public static Uri moveDocument(ContentResolver resolver, Uri sourceDocumentUri, - Uri sourceParentDocumentUri, Uri targetParentDocumentUri) { + Uri sourceParentDocumentUri, Uri targetParentDocumentUri) throws FileNotFoundException { final ContentProviderClient client = resolver.acquireUnstableContentProviderClient( sourceDocumentUri.getAuthority()); try { @@ -1282,6 +1290,7 @@ public final class DocumentsContract { targetParentDocumentUri); } catch (Exception e) { Log.w(TAG, "Failed to move document", e); + rethrowIfNecessary(resolver, e); return null; } finally { ContentProviderClient.releaseQuietly(client); @@ -1311,7 +1320,7 @@ public final class DocumentsContract { * @return true if the document was removed successfully. */ public static boolean removeDocument(ContentResolver resolver, Uri documentUri, - Uri parentDocumentUri) { + Uri parentDocumentUri) throws FileNotFoundException { final ContentProviderClient client = resolver.acquireUnstableContentProviderClient( documentUri.getAuthority()); try { @@ -1319,6 +1328,7 @@ public final class DocumentsContract { return true; } catch (Exception e) { Log.w(TAG, "Failed to remove document", e); + rethrowIfNecessary(resolver, e); return false; } finally { ContentProviderClient.releaseQuietly(client); @@ -1379,7 +1389,8 @@ public final class DocumentsContract { * @return the path of the document, or {@code null} if failed. * @see DocumentsProvider#findDocumentPath(String, String) */ - public static Path findDocumentPath(ContentResolver resolver, Uri treeUri) { + public static Path findDocumentPath(ContentResolver resolver, Uri treeUri) + throws FileNotFoundException { checkArgument(isTreeUri(treeUri), treeUri + " is not a tree uri."); final ContentProviderClient client = resolver.acquireUnstableContentProviderClient( @@ -1388,6 +1399,7 @@ public final class DocumentsContract { return findDocumentPath(client, treeUri); } catch (Exception e) { Log.w(TAG, "Failed to find path", e); + rethrowIfNecessary(resolver, e); return null; } finally { ContentProviderClient.releaseQuietly(client); @@ -1473,13 +1485,14 @@ public final class DocumentsContract { * @see Intent#EXTRA_EMAIL */ public static IntentSender createWebLinkIntent(ContentResolver resolver, Uri uri, - Bundle options) { + Bundle options) throws FileNotFoundException { final ContentProviderClient client = resolver.acquireUnstableContentProviderClient( uri.getAuthority()); try { return createWebLinkIntent(client, uri, options); } catch (Exception e) { Log.w(TAG, "Failed to create a web link intent", e); + rethrowIfNecessary(resolver, e); return null; } finally { ContentProviderClient.releaseQuietly(client); @@ -1544,6 +1557,20 @@ public final class DocumentsContract { return new AssetFileDescriptor(pfd, 0, AssetFileDescriptor.UNKNOWN_LENGTH, extras); } + private static void rethrowIfNecessary(ContentResolver resolver, Exception e) + throws FileNotFoundException { + // We only want to throw applications targetting O and above + if (resolver.getTargetSdkVersion() >= Build.VERSION_CODES.O) { + if (e instanceof ParcelableException) { + ((ParcelableException) e).maybeRethrow(FileNotFoundException.class); + } else if (e instanceof RemoteException) { + ((RemoteException) e).rethrowAsRuntimeException(); + } else if (e instanceof RuntimeException) { + throw (RuntimeException) e; + } + } + } + /** * Holds a path from a document to a particular document under it. It * may also contains the root ID where the path resides. diff --git a/core/java/android/provider/DocumentsProvider.java b/core/java/android/provider/DocumentsProvider.java index 89a80f054ded..620d33a5e915 100644 --- a/core/java/android/provider/DocumentsProvider.java +++ b/core/java/android/provider/DocumentsProvider.java @@ -38,6 +38,7 @@ import static android.provider.DocumentsContract.isTreeUri; import android.Manifest; import android.annotation.CallSuper; import android.annotation.Nullable; +import android.app.RecoverableSecurityException; import android.content.ClipDescription; import android.content.ContentProvider; import android.content.ContentResolver; @@ -57,6 +58,7 @@ import android.os.CancellationSignal; import android.os.OperationCanceledException; import android.os.ParcelFileDescriptor; import android.os.ParcelFileDescriptor.OnCloseListener; +import android.os.ParcelableException; import android.provider.DocumentsContract.Document; import android.provider.DocumentsContract.Path; import android.provider.DocumentsContract.Root; @@ -233,6 +235,10 @@ public abstract class DocumentsProvider extends ContentProvider { * {@link Document#COLUMN_DOCUMENT_ID}. You must allocate a new * {@link Document#COLUMN_DOCUMENT_ID} to represent the document, which must * not change once returned. + * <p> + * {@link RecoverableSecurityException} can be thrown if more input is required + * from the user (such as insufficient permission), but it is not guaranteed that + * the client will handle this properly. * * @param parentDocumentId the parent directory to create the new document * under. @@ -256,6 +262,10 @@ public abstract class DocumentsProvider extends ContentProvider { * URI permission grants will be updated to point at the new document. If * the original {@link Document#COLUMN_DOCUMENT_ID} is still valid after the * rename, return {@code null}. + * <p> + * {@link RecoverableSecurityException} can be thrown if more input is required + * from the user (such as insufficient permission), but it is not guaranteed that + * the client will handle this properly. * * @param documentId the document to rename. * @param displayName the updated display name of the document. The provider @@ -276,6 +286,10 @@ public abstract class DocumentsProvider extends ContentProvider { * call (such as documents inside a directory) the implementor is * responsible for revoking those permissions using * {@link #revokeDocumentPermission(String)}. + * <p> + * {@link RecoverableSecurityException} can be thrown if more input is required + * from the user (such as insufficient permission), but it is not guaranteed that + * the client will handle this properly. * * @param documentId the document to delete. */ @@ -291,6 +305,10 @@ public abstract class DocumentsProvider extends ContentProvider { * the same document provider. Upon completion returns the document id of * the copied document at the target destination. {@code null} must never * be returned. + * <p> + * {@link RecoverableSecurityException} can be thrown if more input is required + * from the user (such as insufficient permission), but it is not guaranteed that + * the client will handle this properly. * * @param sourceDocumentId the document to copy. * @param targetParentDocumentId the target document to be copied into as a child. @@ -311,6 +329,10 @@ public abstract class DocumentsProvider extends ContentProvider { * * <p>It's the responsibility of the provider to revoke grants if the document * is no longer accessible using <code>sourceDocumentId</code>. + * <p> + * {@link RecoverableSecurityException} can be thrown if more input is required + * from the user (such as insufficient permission), but it is not guaranteed that + * the client will handle this properly. * * @param sourceDocumentId the document to move. * @param sourceParentDocumentId the parent of the document to move. @@ -333,6 +355,9 @@ public abstract class DocumentsProvider extends ContentProvider { * <p>It's the responsibility of the provider to revoke grants if the document is * removed from the last parent, and effectively the document is deleted. * + * <p>{@link RecoverableSecurityException} can be thrown if more input is required + * from the user (such as insufficient permission), but it is not guaranteed that + * the client will handle this properly. * @param documentId the document to remove. * @param parentDocumentId the parent of the document to move. */ @@ -352,6 +377,10 @@ public abstract class DocumentsProvider extends ContentProvider { * <p>This API assumes that document ID has enough info to infer the root. * Different roots should use different document ID to refer to the same * document. + * <p>{@link RecoverableSecurityException} can be thrown if more input is required + * from the user (such as insufficient permission), but it is not guaranteed that + * the client will handle this properly.perly. + * * * @param parentDocumentId the document from which the path starts if not null, * or null to indicate a path from the root is requested. @@ -368,9 +397,11 @@ public abstract class DocumentsProvider extends ContentProvider { /** * Creates an intent sender for a web link, if the document is web linkable. * <p> - * Before any new permissions are granted for the linked document, a visible - * UI must be shown, so the user can explicitly confirm whether the permission - * grants are expected. The user must be able to cancel the operation. + * {@link RecoverableSecurityException} can be thrown if user does not have + * sufficient permission for the linked document. Before any new permissions + * are granted for the linked document, a visible UI must be shown, so the + * user can explicitly confirm whether the permission grants are expected. + * The user must be able to cancel the operation. * <p> * Options passed as an argument may include a list of recipients, such * as email addresses. The provider should reflect these options if possible, @@ -404,6 +435,10 @@ public abstract class DocumentsProvider extends ContentProvider { * If this set of roots changes, you must call {@link ContentResolver#notifyChange(Uri, * android.database.ContentObserver, boolean)} with * {@link DocumentsContract#buildRootsUri(String)} to notify the system. + * <p> + * {@link RecoverableSecurityException} can be thrown if more input is required + * from the user (such as insufficient permission), or returned as part of + * Cursor's bundle. It is not guaranteed that the client will handle this properly. * * @param projection list of {@link Root} columns to put into the cursor. If * {@code null} all supported columns should be included. @@ -417,6 +452,10 @@ public abstract class DocumentsProvider extends ContentProvider { * sorted by {@link Document#COLUMN_LAST_MODIFIED} in descending order, and * limited to only return the 64 most recently modified documents. * <p> + * {@link RecoverableSecurityException} can be thrown if more input is required + * from the user (such as insufficient permission), or returned as part of + * Cursor's bundle. It is not guaranteed that the client will handle this properly. + * <p> * Recent documents do not support change notifications. * * @param projection list of {@link Document} columns to put into the @@ -433,6 +472,11 @@ public abstract class DocumentsProvider extends ContentProvider { /** * Return metadata for the single requested document. You should avoid * making network requests to keep this request fast. + * <p> + * {@link RecoverableSecurityException} can be thrown if more input is required + * from the user (such as insufficient permission), or returned as part of + * Cursor's bundle. It is not guaranteed that the client will handle this properly. + * * * @param documentId the document to return. * @param projection list of {@link Document} columns to put into the @@ -465,6 +509,11 @@ public abstract class DocumentsProvider extends ContentProvider { * you can call {@link ContentResolver#notifyChange(Uri, * android.database.ContentObserver, boolean)} with that Uri to send change * notifications. + * <p> + * {@link RecoverableSecurityException} can be thrown if more input is required + * from the user (such as insufficient permission), or returned as part of + * Cursor's bundle. It is not guaranteed that the client will handle this properly. + * * * @param parentDocumentId the directory to return children for. * @param projection list of {@link Document} columns to put into the @@ -503,6 +552,10 @@ public abstract class DocumentsProvider extends ContentProvider { * you can call {@link ContentResolver#notifyChange(Uri, * android.database.ContentObserver, boolean)} with that Uri to send change * notifications. + * <p> + * {@link RecoverableSecurityException} can be thrown if more input is required + * from the user (such as insufficient permission), or returned as part of + * Cursor's bundle. It is not guaranteed that the client will handle this properly. * * @param parentDocumentId the directory to return children for. * @param projection list of {@link Document} columns to put into the @@ -556,6 +609,10 @@ public abstract class DocumentsProvider extends ContentProvider { * String, String)}. Then you can call {@link ContentResolver#notifyChange(Uri, * android.database.ContentObserver, boolean)} with that Uri to send change * notifications. + * <p> + * {@link RecoverableSecurityException} can be thrown if more input is required + * from the user (such as insufficient permission), or returned as part of + * Cursor's bundle. It is not guaranteed that the client will handle this properly. * * @param rootId the root to search under. * @param query string to match documents against. @@ -583,6 +640,10 @@ public abstract class DocumentsProvider extends ContentProvider { * of {@link Document#COLUMN_MIME_TYPE} for this document. The default * implementation queries {@link #queryDocument(String, String[])}, so * providers may choose to override this as an optimization. + * <p> + * {@link RecoverableSecurityException} can be thrown if more input is required + * from the user (such as insufficient permission), but it is not guaranteed that + * the client will handle this properly. */ public String getDocumentType(String documentId) throws FileNotFoundException { final Cursor cursor = queryDocument(documentId, null); @@ -608,6 +669,10 @@ public abstract class DocumentsProvider extends ContentProvider { * <p> * If you block while downloading content, you should periodically check * {@link CancellationSignal#isCanceled()} to abort abandoned open requests. + * <p> + * {@link RecoverableSecurityException} can be thrown if more input is required + * from the user (such as insufficient permission), but it is not guaranteed that + * the client will handle this properly. * * @param documentId the document to return. * @param mode the mode to open with, such as 'r', 'w', or 'rw'. @@ -632,6 +697,10 @@ public abstract class DocumentsProvider extends ContentProvider { * If you perform expensive operations to download or generate a thumbnail, * you should periodically check {@link CancellationSignal#isCanceled()} to * abort abandoned thumbnail requests. + * <p> + * {@link RecoverableSecurityException} can be thrown if more input is required + * from the user (such as insufficient permission), but it is not guaranteed that + * the client will handle this properly. * * @param documentId the document to return. * @param sizeHint hint of the optimal thumbnail dimensions. @@ -654,6 +723,10 @@ public abstract class DocumentsProvider extends ContentProvider { * matching the specified MIME type filter. * <p> * Virtual documents must have at least one streamable format. + * <p> + * {@link RecoverableSecurityException} can be thrown if more input is required + * from the user (such as insufficient permission), but it is not guaranteed that + * the client will handle this properly. * * @param documentId the document to return. * @param mimeTypeFilter the MIME type filter for the requested format. May @@ -877,7 +950,7 @@ public abstract class DocumentsProvider extends ContentProvider { try { return callUnchecked(method, arg, extras); } catch (FileNotFoundException e) { - throw new IllegalStateException("Failed call " + method, e); + throw new ParcelableException(e); } } diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 52aa1d58d69c..391ee835e92a 100755 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -506,8 +506,6 @@ public final class Settings { * Input: Nothing. * <p> * Output: Nothing. - * - * @hide */ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) public static final String ACTION_NIGHT_DISPLAY_SETTINGS = @@ -3852,6 +3850,17 @@ public final class Settings { }; /** + * Setting to determine whether or not to show the battery percentage in the status bar. + * 0 - Don't show percentage + * 1 - Show percentage + * @hide + */ + public static final String SHOW_BATTERY_PERCENT = "status_bar_show_battery_percent"; + + /** @hide */ + private static final Validator SHOW_BATTERY_PERCENT_VALIDATOR = sBooleanValidator; + + /** * IMPORTANT: If you add a new public settings you also have to add it to * PUBLIC_SETTINGS below. If the new setting is hidden you have to add * it to PRIVATE_SETTINGS below. Also add a validator that can validate @@ -3913,7 +3922,8 @@ public final class Settings { RINGTONE, LOCK_TO_APP_ENABLED, NOTIFICATION_SOUND, - ACCELEROMETER_ROTATION + ACCELEROMETER_ROTATION, + SHOW_BATTERY_PERCENT }; /** @@ -4013,6 +4023,7 @@ public final class Settings { PRIVATE_SETTINGS.add(POINTER_SPEED); PRIVATE_SETTINGS.add(LOCK_TO_APP_ENABLED); PRIVATE_SETTINGS.add(EGG_MODE); + PRIVATE_SETTINGS.add(SHOW_BATTERY_PERCENT); } /** @@ -4090,6 +4101,7 @@ public final class Settings { VALIDATORS.put(WIFI_STATIC_NETMASK, WIFI_STATIC_NETMASK_VALIDATOR); VALIDATORS.put(WIFI_STATIC_DNS1, WIFI_STATIC_DNS1_VALIDATOR); VALIDATORS.put(WIFI_STATIC_DNS2, WIFI_STATIC_DNS2_VALIDATOR); + VALIDATORS.put(SHOW_BATTERY_PERCENT, SHOW_BATTERY_PERCENT_VALIDATOR); } /** @@ -5593,10 +5605,10 @@ public final class Settings { "accessibility_web_content_key_bindings"; /** - * Setting that specifies whether the display magnification is enabled. - * Display magnifications allows the user to zoom in the display content - * and is targeted to low vision users. The current magnification scale - * is controlled by {@link #ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE}. + * Setting that specifies whether the display magnification is enabled via a system-wide + * triple tap gesture. Display magnifications allows the user to zoom in the display content + * and is targeted to low vision users. The current magnification scale is controlled by + * {@link #ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE}. * * @hide */ @@ -5604,11 +5616,23 @@ public final class Settings { "accessibility_display_magnification_enabled"; /** + * Setting that specifies whether the display magnification is enabled via a shortcut + * affordance within the system's navigation area. Display magnifications allows the user to + * zoom in the display content and is targeted to low vision users. The current + * magnification scale is controlled by {@link #ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE}. + * + * @hide + */ + public static final String ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED = + "accessibility_display_magnification_navbar_enabled"; + + /** * Setting that specifies what the display magnification scale is. * Display magnifications allows the user to zoom in the display * content and is targeted to low vision users. Whether a display * magnification is performed is controlled by - * {@link #ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED} + * {@link #ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED} and + * {@link #ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED} * * @hide */ @@ -6938,6 +6962,7 @@ public final class Settings { ACCESSIBILITY_DISPLAY_DALTONIZER, ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED, ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED, + ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED, ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE, ACCESSIBILITY_SCRIPT_INJECTION, ACCESSIBILITY_WEB_CONTENT_KEY_BINDINGS, @@ -7654,12 +7679,14 @@ public final class Settings { public static final String HDMI_CONTROL_ENABLED = "hdmi_control_enabled"; /** - * Whether HDMI system audio is enabled. If enabled, TV internal speaker is muted, - * and the output is redirected to AV Receiver connected via - * {@Global#HDMI_SYSTEM_AUDIO_OUTPUT}. + * Whether HDMI System Audio Control feature is enabled. If enabled, TV will try to turn on + * system audio mode if there's a connected CEC-enabled AV Receiver. Then audio stream will + * be played on AVR instead of TV spaeker. If disabled, the system audio mode will never be + * activated. * @hide */ - public static final String HDMI_SYSTEM_AUDIO_ENABLED = "hdmi_system_audio_enabled"; + public static final String HDMI_SYSTEM_AUDIO_CONTROL_ENABLED = + "hdmi_system_audio_control_enabled"; /** * Whether TV will automatically turn on upon reception of the CEC command diff --git a/core/java/android/service/autofill/AutofillService.java b/core/java/android/service/autofill/AutofillService.java index 29e207358e70..709e5f9e7ef7 100644 --- a/core/java/android/service/autofill/AutofillService.java +++ b/core/java/android/service/autofill/AutofillService.java @@ -30,6 +30,7 @@ import android.os.IBinder; import android.os.ICancellationSignal; import android.os.Looper; import android.util.Log; +import android.view.autofill.AutofillManager; import com.android.internal.os.SomeArgs; @@ -90,6 +91,8 @@ public abstract class AutofillService extends Service { private static final int MSG_ON_FILL_REQUEST = 3; private static final int MSG_ON_SAVE_REQUEST = 4; + private static final int UNUSED_ARG = -1; + private final IAutoFillService mInterface = new IAutoFillService.Stub() { @Override public void onInit(IAutoFillServiceConnection connection) { @@ -102,14 +105,14 @@ public abstract class AutofillService extends Service { @Override public void onFillRequest(AssistStructure structure, Bundle extras, - IFillCallback callback) { + IFillCallback callback, int flags) { ICancellationSignal transport = CancellationSignal.createTransport(); try { callback.onCancellable(transport); } catch (RemoteException e) { e.rethrowFromSystemServer(); } - mHandlerCaller.obtainMessageOOOO(MSG_ON_FILL_REQUEST, structure, + mHandlerCaller.obtainMessageIIOOOO(MSG_ON_FILL_REQUEST, flags, UNUSED_ARG, structure, CancellationSignal.fromTransport(transport), extras, callback) .sendToTarget(); } @@ -135,8 +138,9 @@ public abstract class AutofillService extends Service { final Bundle extras = (Bundle) args.arg3; final IFillCallback callback = (IFillCallback) args.arg4; final FillCallback fillCallback = new FillCallback(callback); + final int flags = msg.arg1; args.recycle(); - onFillRequest(structure, extras, cancellation, fillCallback); + onFillRequest(structure, extras, flags, cancellation, fillCallback); break; } case MSG_ON_SAVE_REQUEST: { final SomeArgs args = (SomeArgs) msg.obj; @@ -188,7 +192,6 @@ public abstract class AutofillService extends Service { * <p>You should generally do initialization here rather than in {@link #onCreate}. */ public void onConnected() { - //TODO(b/33197203): is not called anymore, fix it! } /** @@ -206,11 +209,25 @@ public abstract class AutofillService extends Service { * as well as when filling different sections of the UI as the system will try to * aggressively unbind from the service to conserve resources. See {@link * FillResponse} Javadoc for examples of multiple-sections requests. + * @param flags either {@code 0} or {@link AutofillManager#FLAG_MANUAL_REQUEST}. * @param cancellationSignal signal for observing cancellation requests. The system will use * this to notify you that the fill result is no longer needed and you should stop * handling this fill request in order to save resources. * @param callback object used to notify the result of the request. */ + public void onFillRequest(@NonNull AssistStructure structure, @Nullable Bundle data, int flags, + @NonNull CancellationSignal cancellationSignal, @NonNull FillCallback callback) { + //TODO(b/33197203): make non-abstract once older method is removed + onFillRequest(structure, data, cancellationSignal, callback); + } + + /** + * @hide + * @deprecated - use {@link #onFillRequest(AssistStructure, Bundle, int, + * CancellationSignal, FillCallback)} instead + */ + //TODO(b/33197203): remove once clients are not using anymore + @Deprecated public abstract void onFillRequest(@NonNull AssistStructure structure, @Nullable Bundle data, @NonNull CancellationSignal cancellationSignal, @NonNull FillCallback callback); @@ -238,7 +255,6 @@ public abstract class AutofillService extends Service { * <p> At this point this service may no longer be an active {@link AutofillService}. */ public void onDisconnected() { - //TODO(b/33197203): is not called anymore, fix it! } /** diff --git a/core/java/android/service/autofill/AutofillServiceInfo.java b/core/java/android/service/autofill/AutofillServiceInfo.java index d2200528aedb..f6d40dbf3414 100644 --- a/core/java/android/service/autofill/AutofillServiceInfo.java +++ b/core/java/android/service/autofill/AutofillServiceInfo.java @@ -78,14 +78,12 @@ public final class AutofillServiceInfo { // TODO(b/35956626): inline newSettingsActivity once clients migrate final String newSettingsActivity = metaDataArray.getString(R.styleable.AutofillService_settingsActivity); - System.out.println(">>> NEW CRAP MAN: " + newSettingsActivity); // TODO(felipeal): tmp if (newSettingsActivity != null) { mSettingsActivity = newSettingsActivity; } else { mSettingsActivity = metaDataArray.getString(R.styleable.AutoFillService_settingsActivity); } - System.out.println(">>> FINAL CRAP MAN: " + mSettingsActivity); // TODO(felipeal): tmp metaDataArray.recycle(); } else { mSettingsActivity = null; diff --git a/core/java/android/service/autofill/Dataset.java b/core/java/android/service/autofill/Dataset.java index 246194706989..ebe02c2aa9b6 100644 --- a/core/java/android/service/autofill/Dataset.java +++ b/core/java/android/service/autofill/Dataset.java @@ -167,6 +167,7 @@ public final class Dataset implements Parcelable { public @NonNull Builder setValue(@NonNull AutoFillId id, @NonNull AutoFillValue value) { return setValue(id.getDaRealId(), value.getDaRealValue()); } + /** * Sets the value of a field. * diff --git a/core/java/android/service/autofill/FillCallback.java b/core/java/android/service/autofill/FillCallback.java index 00b206c23ba9..e8ad14f55261 100644 --- a/core/java/android/service/autofill/FillCallback.java +++ b/core/java/android/service/autofill/FillCallback.java @@ -37,7 +37,7 @@ public final class FillCallback { /** * Notifies the Android System that an * {@link AutofillService#onFillRequest(android.app.assist.AssistStructure, Bundle, - * android.os.CancellationSignal, FillCallback)} was successfully fulfilled by the service. + * int, android.os.CancellationSignal, FillCallback)} was successfully fulfilled by the service. * * @param response autofill information for that activity, or {@code null} when the activity * cannot be autofilled (for example, if it only contains read-only fields). See @@ -56,7 +56,7 @@ public final class FillCallback { /** * Notifies the Android System that an * {@link AutofillService#onFillRequest(android.app.assist.AssistStructure, - * Bundle, android.os.CancellationSignal, FillCallback)} + * Bundle, int, android.os.CancellationSignal, FillCallback)} * could not be fulfilled by the service. * * @param message error message to be displayed to the user. diff --git a/core/java/android/service/autofill/FillResponse.java b/core/java/android/service/autofill/FillResponse.java index 069e83c6c15c..c43019dc297d 100644 --- a/core/java/android/service/autofill/FillResponse.java +++ b/core/java/android/service/autofill/FillResponse.java @@ -31,7 +31,7 @@ import java.util.ArrayList; /** * Response for a {@link * AutofillService#onFillRequest(android.app.assist.AssistStructure, - * Bundle, android.os.CancellationSignal, FillCallback)}. + * Bundle, int, android.os.CancellationSignal, FillCallback)}. * * <p>The response typically contains one or more {@link Dataset}s, each representing a set of * fields that can be autofilled together, and the Android system displays a dataset picker UI @@ -44,8 +44,8 @@ import java.util.ArrayList; * <pre class="prettyprint"> * new FillResponse.Builder() * .add(new Dataset.Builder(createPresentation()) - * .setTextFieldValue(id1, "homer") - * .setTextFieldValue(id2, "D'OH!") + * .setValue(id1, AutofillValue.forText("homer")) + * .setValue(id2, AutofillValue.forText("D'OH!")) * .build()) * .build(); * </pre> @@ -55,48 +55,19 @@ import java.util.ArrayList; * <pre class="prettyprint"> * new FillResponse.Builder() * .add(new Dataset.Builder(createFirstPresentation()) - * .setTextFieldValue(id1, "homer") - * .setTextFieldValue(id2, "D'OH!") + * .setValue(id1, AutofillValue.forText("homer")) + * .setValue(id2, AutofillValue.forText("D'OH!")) * .build()) * .add(new Dataset.Builder(createSecondPresentation()) - * .setTextFieldValue(id1, "elbarto") - * .setTextFieldValue(id2, "cowabonga") + * .setValue(id1, AutofillValue.forText("elbarto") + * .setValue(id2, AutofillValue.forText("cowabonga") * .build()) * .build(); * </pre> * - * <p>If the user does not have any data associated with this {@link android.app.Activity} but - * the service wants to offer the user the option to save the data that was entered, then the - * service could populate the response with a {@link SaveInfo} instead of {@link Dataset}s: - * - * <pre class="prettyprint"> - * new FillResponse.Builder() - * .setSaveInfo(new SaveInfo.Builder(SaveInfo.SAVE_INFO_TYPE_CREDENTIALS) - * .addSavableFields(id1, id2)) - * .build(); - * </pre> - * - * <p>Similarly, there might be cases where the user data on the service is enough to populate some - * fields but not all, and the service would still be interested on saving the other fields. In this - * scenario, the service could populate the response with both {@link Dataset}s and - * {@link SaveInfo}: - * - * <pre class="prettyprint"> - * new FillResponse.Builder() - * .add(new Dataset.Builder(createPresentation()) - * .setTextFieldValue(id1, "Homer") // first name - * .setTextFieldValue(id2, "Simpson") // last name - * .setTextFieldValue(id3, "742 Evergreen Terrace") // street - * .setTextFieldValue(id4, "Springfield") // city - * .build()) - * .setSaveInfo(new SaveInfo.Builder(SaveInfo.SAVE_INFO_TYPE_ADDRESS) - * .addSavableFields(id5, id6)) // state and zipcode - * .build(); - * - * </pre> - * - * <p>Notice that the ids that are part of a dataset (ids 1 to 4, in this example) are automatically - * added to the {@code savableIds} list. + * If the service is interested on saving the user-edited data back, it must set a {@link SaveInfo} + * in the {@link FillResponse}. Typically, the {@link SaveInfo} contains the same ids as the + * {@link Dataset}, but other combinations are possible - see {@link SaveInfo} for more details * * <p>If the service has multiple {@link Dataset}s for different sections of the activity, * for example, a user section for which there are two datasets followed by an address @@ -113,12 +84,12 @@ import java.util.ArrayList; * <pre class="prettyprint"> * new FillResponse.Builder() * .add(new Dataset.Builder(createFirstPresentation()) - * .setTextFieldValue(id1, "Homer") - * .setTextFieldValue(id2, "Simpson") + * .setValue(id1, AutofillValue.forText("Homer")) + * .setValue(id2, AutofillValue.forText("Simpson")) * .build()) * .add(new Dataset.Builder(createSecondPresentation()) - * .setTextFieldValue(id1, "Bart") - * .setTextFieldValue(id2, "Simpson") + * .setValue(id1, AutofillValue.forText("Bart")) + * .setValue(id2, AutofillValue.forText("Simpson")) * .build()) * .build(); * </pre> @@ -129,12 +100,12 @@ import java.util.ArrayList; * <pre class="prettyprint"> * new FillResponse.Builder() * .add(new Dataset.Builder(createThirdPresentation()) - * .setTextFieldValue(id3, "742 Evergreen Terrace") - * .setTextFieldValue(id4, "Springfield") + * .setValue(id3, AutofillValue.forText("742 Evergreen Terrace")) + * .setValue(id4, AutofillValue.forText("Springfield")) * .build()) * .add(new Dataset.Builder(createFourthPresentation()) - * .setTextFieldValue(id3, "Springfield Power Plant") - * .setTextFieldValue(id4, "Springfield") + * .setValue(id3, AutofillValue.forText("Springfield Power Plant")) + * .setValue(id4, AutofillValue.forText("Springfield")) * .build()) * .build(); * </pre> @@ -167,16 +138,7 @@ public final class FillResponse implements Parcelable { private FillResponse(@NonNull Builder builder) { mDatasets = builder.mDatasets; - mSaveInfo = builder.mSaveInfo; - if (mSaveInfo != null) { - mSaveInfo.addSavableIds(mDatasets); - if (mSaveInfo.getSavableIds() == null) { - throw new IllegalArgumentException( - "need to provide at least one savable id on SaveInfo"); - } - } - mExtras = builder.mExtras; mPresentation = builder.mPresentation; mAuthentication = builder.mAuthentication; @@ -307,8 +269,8 @@ public final class FillResponse implements Parcelable { * Sets a {@link Bundle} that will be passed to subsequent APIs that * manipulate this response. For example, they are passed to subsequent * calls to {@link AutofillService#onFillRequest( - * android.app.assist.AssistStructure, Bundle, android.os.CancellationSignal, - * FillCallback)} and {@link AutofillService#onSaveRequest( + * android.app.assist.AssistStructure, Bundle, int, + * android.os.CancellationSignal, FillCallback)} and {@link AutofillService#onSaveRequest( * android.app.assist.AssistStructure, Bundle, SaveCallback)}. * * @param extras The response extras. diff --git a/core/java/android/service/autofill/IAutoFillService.aidl b/core/java/android/service/autofill/IAutoFillService.aidl index 80685d87a788..9f296c60c6d4 100644 --- a/core/java/android/service/autofill/IAutoFillService.aidl +++ b/core/java/android/service/autofill/IAutoFillService.aidl @@ -31,7 +31,7 @@ import com.android.internal.os.IResultReceiver; oneway interface IAutoFillService { void onInit(in IAutoFillServiceConnection connection); void onFillRequest(in AssistStructure structure, in Bundle extras, - in IFillCallback callback); + in IFillCallback callback, int flags); void onSaveRequest(in AssistStructure structure, in Bundle extras, in ISaveCallback callback); } diff --git a/core/java/android/service/autofill/SaveInfo.java b/core/java/android/service/autofill/SaveInfo.java index a8e3ff8e7a81..6663f03a6383 100644 --- a/core/java/android/service/autofill/SaveInfo.java +++ b/core/java/android/service/autofill/SaveInfo.java @@ -25,28 +25,86 @@ import android.content.IntentSender; import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; -import android.util.ArraySet; import android.view.autofill.AutoFillId; import android.view.autofill.AutofillId; +import android.view.autofill.AutofillManager; +import android.view.autofill.AutofillValue; + +import com.android.internal.util.Preconditions; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; -import java.util.ArrayList; +import java.util.Arrays; /** - * Information used to indicate that a service is interested on saving the user-inputed data for - * future use. + * Information used to indicate that an {@link AutofillService} is interested on saving the + * user-inputed data for future use, through a + * {@link AutofillService#onSaveRequest(android.app.assist.AssistStructure, Bundle, SaveCallback)} + * call. + * + * <p>A {@link SaveInfo} is always associated with a {@link FillResponse}, and it contains at least + * two pieces of information: + * + * <ol> + * <li>The type of user data that would be saved (like passoword or credit card info). + * <li>The minimum set of views (represented by their {@link AutofillId}) that need to be changed + * to trigger a save request. + * </ol> + * + * Typically, the {@link SaveInfo} contains the same {@code id}s as the {@link Dataset}: + * + * <pre class="prettyprint"> + * new FillResponse.Builder() + * .add(new Dataset.Builder(createPresentation()) + * .setValue(id1, AutofillValue.forText("homer")) + * .setValue(id2, AutofillValue.forText("D'OH!")) + * .build()) + * .setSaveInfo(new SaveInfo.Builder(SaveInfo.SAVE_INFO_TYPE_PASSWORD, new int[] {id1, id2}) + * .build()) + * .build(); + * </pre> + * + * There might be cases where the {@link AutofillService} knows how to fill the + * {@link android.app.Activity}, but the user has no data for it. In that case, the + * {@link FillResponse} should contain just the {@link SaveInfo}, but no {@link Dataset}s: * - * <p>A {@link SaveInfo} is always associated with a {@link FillResponse}. + * <pre class="prettyprint"> + * new FillResponse.Builder() + * .setSaveInfo(new SaveInfo.Builder(SaveInfo.SAVE_INFO_TYPE_PASSWORD, new int[] {id1, id2}) + * .build()) + * .build(); + * </pre> * - * <p>A {@link SaveInfo} must define the type it represents, and contain at least one - * {@code savableId}. A {@code savableId} is the {@link AutofillId} of a view the service is - * interested to save in a {@code onSaveRequest()}; the ids of all {@link Dataset} present in the - * {@link FillResponse} associated with this {@link SaveInfo} are already marked as savable, - * but additional ids can be added through {@link Builder#addSavableIds(AutofillId...)}. + * <p>There might be cases where the user data in the {@link AutofillService} is enough + * to populate some fields but not all, and the service would still be interested on saving the + * other fields. In this scenario, the service could set the + * {@link SaveInfo.Builder#setOptionalIds(AutofillId[])} as well: * - * <p>See {@link AutofillService#onSaveRequest(android.app.assist.AssistStructure, Bundle, - * SaveCallback)} and {@link FillResponse} for more info. + * <pre class="prettyprint"> + * new FillResponse.Builder() + * .add(new Dataset.Builder(createPresentation()) + * .setValue(id1, AutofillValue.forText("742 Evergreen Terrace")) // street + * .setValue(id2, AutofillValue.forText("Springfield")) // city + * .build()) + * .setSaveInfo(new SaveInfo.Builder(SaveInfo.SAVE_INFO_TYPE_ADDRESS, new int[] {id1, id2}) + * .setOptionalIds(new int[] {id3, id4}) // state and zipcode + * .build()) + * .build(); + * </pre> + * + * The + * {@link AutofillService#onSaveRequest(android.app.assist.AssistStructure, Bundle, SaveCallback)} + * is triggered after a call to {@link AutofillManager#commit()}, but only when all conditions + * below are met: + * + * <ol> + * <li>The {@link SaveInfo} associated with the {@link FillResponse} is not {@code null}. + * <li>The {@link AutofillValue} of all required views (as set by the {@code requiredIds} passed + * to {@link SaveInfo.Builder} constructor are not empty. + * <li>The {@link AutofillValue} of at least one view (be it required or optional) has changed + * (i.e., it's not the same value passed in a {@link Dataset}). + * <li>The user explicitly tapped the affordance asking to save data for autofill. + * </ol> */ public final class SaveInfo implements Parcelable { @@ -61,7 +119,6 @@ public final class SaveInfo implements Parcelable { */ public static final int SAVE_DATA_TYPE_PASSWORD = 1; - /** * Type used on when the {@link FillResponse} represents a physical address (such as street, * city, state, etc). @@ -74,9 +131,10 @@ public final class SaveInfo implements Parcelable { public static final int SAVE_DATA_TYPE_CREDIT_CARD = 3; private final @SaveDataType int mType; - private CharSequence mNegativeActionTitle; - private IntentSender mNegativeActionListener; - private ArraySet<AutofillId> mSavableIds; + private final CharSequence mNegativeActionTitle; + private final IntentSender mNegativeActionListener; + private final AutofillId[] mRequiredIds; + private final AutofillId[] mOptionalIds; private final CharSequence mDescription; /** @hide */ @@ -94,7 +152,8 @@ public final class SaveInfo implements Parcelable { mType = builder.mType; mNegativeActionTitle = builder.mNegativeActionTitle; mNegativeActionListener = builder.mNegativeActionListener; - mSavableIds = builder.mSavableIds; + mRequiredIds = builder.mRequiredIds; + mOptionalIds = builder.mOptionalIds; mDescription = builder.mDescription; } @@ -109,8 +168,13 @@ public final class SaveInfo implements Parcelable { } /** @hide */ - public @Nullable ArraySet<AutofillId> getSavableIds() { - return mSavableIds; + public AutofillId[] getRequiredIds() { + return mRequiredIds; + } + + /** @hide */ + public @Nullable AutofillId[] getOptionalIds() { + return mOptionalIds; } /** @hide */ @@ -123,25 +187,6 @@ public final class SaveInfo implements Parcelable { return mDescription; } - /** @hide */ - public void addSavableIds(@Nullable ArrayList<Dataset> datasets) { - if (datasets != null) { - for (Dataset dataset : datasets) { - final ArrayList<AutofillId> ids = dataset.getFieldIds(); - if (ids != null) { - final int fieldCount = ids.size(); - for (int i = 0; i < fieldCount; i++) { - final AutofillId id = ids.get(i); - if (mSavableIds == null) { - mSavableIds = new ArraySet<>(); - } - mSavableIds.add(id); - } - } - } - } - } - /** * A builder for {@link SaveInfo} objects. */ @@ -150,7 +195,9 @@ public final class SaveInfo implements Parcelable { private final @SaveDataType int mType; private CharSequence mNegativeActionTitle; private IntentSender mNegativeActionListener; - private ArraySet<AutofillId> mSavableIds; + // TODO(b/33197203): make mRequiredIds final once addSavableIds() is gone + private AutofillId[] mRequiredIds; + private AutofillId[] mOptionalIds; private CharSequence mDescription; private boolean mDestroyed; @@ -161,8 +208,15 @@ public final class SaveInfo implements Parcelable { * be {@link SaveInfo#SAVE_DATA_TYPE_GENERIC}, {@link SaveInfo#SAVE_DATA_TYPE_PASSWORD}, * {@link SaveInfo#SAVE_DATA_TYPE_ADDRESS}, or {@link SaveInfo#SAVE_DATA_TYPE_CREDIT_CARD}; * otherwise it will assume {@link SaveInfo#SAVE_DATA_TYPE_GENERIC}. + * @param requiredIds ids of all required views that will trigger a save request. + * + * <p>See {@link SaveInfo} for more info. + * + * @throws IllegalArgumentException if {@code requiredIds} is {@code null} or empty. */ - public Builder(@SaveDataType int type) { + public Builder(@SaveDataType int type, @NonNull AutofillId[] requiredIds) { + 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,63 @@ public final class SaveInfo implements Parcelable { default: mType = SAVE_DATA_TYPE_GENERIC; } + mRequiredIds = requiredIds; + } + + /** + * @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; } /** - * Adds ids of additional views the service would be interested to save, but were not - * indirectly set through {@link FillResponse.Builder#addDataset(Dataset)}. + * Sets the ids of additional, optional views the service would be interested to save. * - * @param ids The savable ids. - * @return This builder. + * <p>See {@link SaveInfo} for more info. * - * @see FillResponse + * @param ids The ids of the optional views. + * @return This builder. */ - public @NonNull Builder addSavableIds(@Nullable AutofillId... ids) { + public @NonNull Builder setOptionalIds(@Nullable AutofillId[] ids) { throwIfDestroyed(); + if (ids != null && ids.length != 0) { + mOptionalIds = ids; + } + return this; + } - if (ids == null) { + + /** + * @hide + */ + // TODO(b/33197203): temporary fix to runtime crash + public @NonNull Builder addSavableIds(@Nullable AutoFillId... ids) { + throwIfDestroyed(); + + if (ids == null || ids.length == 0) { return this; } - for (AutofillId id : ids) { - if (mSavableIds == null) { - mSavableIds = new ArraySet<>(); - } - mSavableIds.add(id); + if (mRequiredIds == null) { + mRequiredIds = new AutofillId[ids.length]; + } + for (int i = 0; i < ids.length; i++) { + mRequiredIds[i] = ids[i].getDaRealId(); } return this; } @@ -208,6 +297,7 @@ public final class SaveInfo implements Parcelable { * @return This Builder. */ public @NonNull Builder setDescription(@Nullable CharSequence description) { + throwIfDestroyed(); mDescription = description; return this; } @@ -273,7 +363,9 @@ public final class SaveInfo implements Parcelable { if (!DEBUG) return super.toString(); return new StringBuilder("SaveInfo: [type=").append(mType) - .append(", savableIds=").append(mSavableIds) + .append(", requiredIds=").append(Arrays.toString(mRequiredIds)) + .append(", optionalIds=").append(Arrays.toString(mOptionalIds)) + .append(", description=").append(mDescription) .append("]").toString(); } @@ -289,9 +381,10 @@ public final class SaveInfo implements Parcelable { @Override public void writeToParcel(Parcel parcel, int flags) { parcel.writeInt(mType); + parcel.writeParcelableArray(mRequiredIds, flags); parcel.writeCharSequence(mNegativeActionTitle); parcel.writeParcelable(mNegativeActionListener, flags); - parcel.writeTypedArraySet(mSavableIds, flags); + parcel.writeParcelableArray(mOptionalIds, flags); parcel.writeCharSequence(mDescription); } @@ -301,13 +394,10 @@ public final class SaveInfo implements Parcelable { // Always go through the builder to ensure the data ingested by // the system obeys the contract of the builder to avoid attacks // using specially crafted parcels. - final Builder builder = new Builder(parcel.readInt()); + final Builder builder = new Builder(parcel.readInt(), + parcel.readParcelableArray(null, AutofillId.class)); builder.setNegativeAction(parcel.readCharSequence(), parcel.readParcelable(null)); - final ArraySet<AutofillId> savableIds = parcel.readTypedArraySet(null); - final int savableIdsCount = (savableIds != null) ? savableIds.size() : 0; - for (int i = 0; i < savableIdsCount; i++) { - builder.addSavableIds(savableIds.valueAt(i)); - } + builder.setOptionalIds(parcel.readParcelableArray(null, AutofillId.class)); builder.setDescription(parcel.readCharSequence()); return builder.build(); } diff --git a/core/java/android/text/FontConfig.java b/core/java/android/text/FontConfig.java index 1087851c853f..04596fa5aa3d 100644 --- a/core/java/android/text/FontConfig.java +++ b/core/java/android/text/FontConfig.java @@ -16,42 +16,57 @@ package android.text; +import static java.lang.annotation.RetentionPolicy.SOURCE; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.os.Parcel; import android.os.ParcelFileDescriptor; import android.os.Parcelable; import java.io.IOException; -import java.util.ArrayList; -import java.util.List; +import java.lang.annotation.Retention; +import java.util.Arrays; + /** * Font configuration descriptions for System fonts. */ public final class FontConfig implements Parcelable { - private final List<Family> mFamilies = new ArrayList<>(); - private final List<Alias> mAliases = new ArrayList<>(); + private final @NonNull Family[] mFamilies; + private final @NonNull Alias[] mAliases; - public FontConfig() { + public FontConfig(@NonNull Family[] families, @NonNull Alias[] aliases) { + mFamilies = families; + mAliases = aliases; } - public FontConfig(FontConfig config) { - for (int i = 0; i < config.mFamilies.size(); i++) { - mFamilies.add(new Family(config.mFamilies.get(i))); + /** + * For duplicating file descriptors. + * + * Note that this copy constructor can not be usable for deep copy. + * @hide + */ + public FontConfig(@NonNull FontConfig config) { + mFamilies = new Family[config.mFamilies.length]; + for (int i = 0; i < config.mFamilies.length; ++i) { + mFamilies[i] = new Family(config.mFamilies[i]); } - mAliases.addAll(config.mAliases); + mAliases = Arrays.copyOf(config.mAliases, config.mAliases.length); } /** * Returns the ordered list of families included in the system fonts. */ - public List<Family> getFamilies() { + public @NonNull Family[] getFamilies() { return mFamilies; } /** * Returns the list of aliases defined for the font families in the system fonts. */ - public List<Alias> getAliases() { + public @NonNull Alias[] getAliases() { return mAliases; } @@ -59,33 +74,14 @@ public final class FontConfig implements Parcelable { * @hide */ public FontConfig(Parcel in) { - readFromParcel(in); + mFamilies = in.readTypedArray(Family.CREATOR); + mAliases = in.readTypedArray(Alias.CREATOR); } @Override public void writeToParcel(Parcel out, int flag) { - out.writeInt(mFamilies.size()); - for (int i = 0; i < mFamilies.size(); i++) { - mFamilies.get(i).writeToParcel(out, flag); - } - out.writeInt(mAliases.size()); - for (int i = 0; i < mAliases.size(); i++) { - mAliases.get(i).writeToParcel(out, flag); - } - } - - /** - * @hide - */ - public void readFromParcel(Parcel in) { - int size = in.readInt(); - for (int i = 0; i < size; i++) { - mFamilies.add(new Family(in)); - } - size = in.readInt(); - for (int i = 0; i < size; i++) { - mAliases.add(new Alias(in)); - } + out.writeTypedArray(mFamilies, flag); + out.writeTypedArray(mAliases, flag); } @Override @@ -164,36 +160,37 @@ public final class FontConfig implements Parcelable { * Class that holds information about a Font. */ public static final class Font implements Parcelable { - private String mFontName; + private final @NonNull String mFontName; private final int mTtcIndex; - private final List<Axis> mAxes; + private final @NonNull Axis[] mAxes; private final int mWeight; private final boolean mIsItalic; - private ParcelFileDescriptor mFd; - private final int mResourceId; + private @Nullable ParcelFileDescriptor mFd; /** * @hide */ - public Font(String fontName, int ttcIndex, List<Axis> axes, int weight, boolean isItalic, - int resourceId) { + public Font(@NonNull String fontName, int ttcIndex, @NonNull Axis[] axes, int weight, + boolean isItalic) { mFontName = fontName; mTtcIndex = ttcIndex; mAxes = axes; mWeight = weight; mIsItalic = isItalic; mFd = null; - mResourceId = resourceId; - } - - public Font(String fontName, int ttcIndex, List<Axis> axes, int weight, boolean isItalic) { - this(fontName, ttcIndex, axes, weight, isItalic, 0); } + /** + * This is for duplicating FileDescriptors. + * + * Note that this copy ctor doesn't deep copy the members. + * + * @hide + */ public Font(Font origin) { mFontName = origin.mFontName; mTtcIndex = origin.mTtcIndex; - mAxes = new ArrayList<>(origin.mAxes); + mAxes = origin.mAxes; mWeight = origin.mWeight; mIsItalic = origin.mIsItalic; if (origin.mFd != null) { @@ -203,24 +200,16 @@ public final class FontConfig implements Parcelable { e.printStackTrace(); } } - mResourceId = origin.mResourceId; } /** * Returns the name associated by the system to this font. */ - public String getFontName() { + public @NonNull String getFontName() { return mFontName; } /** - * @hide - */ - public void setFontName(String fontName) { - mFontName = fontName; - } - - /** * Returns the index to be used to access this font when accessing a TTC file. */ public int getTtcIndex() { @@ -230,7 +219,7 @@ public final class FontConfig implements Parcelable { /** * Returns the list of axes associated to this font. */ - public List<Axis> getAxes() { + public @NonNull Axis[] getAxes() { return mAxes; } @@ -251,35 +240,24 @@ public final class FontConfig implements Parcelable { /** * Returns a file descriptor to access the specified font. This should be closed after use. */ - public ParcelFileDescriptor getFd() { + public @Nullable ParcelFileDescriptor getFd() { return mFd; } /** * @hide */ - public void setFd(ParcelFileDescriptor fd) { + public void setFd(@NonNull ParcelFileDescriptor fd) { mFd = fd; } /** * @hide */ - public int getResourceId() { - return mResourceId; - } - - /** - * @hide - */ public Font(Parcel in) { mFontName = in.readString(); mTtcIndex = in.readInt(); - final int numAxes = in.readInt(); - mAxes = new ArrayList<>(); - for (int i = 0; i < numAxes; i++) { - mAxes.add(new Axis(in)); - } + mAxes = in.createTypedArray(Axis.CREATOR); mWeight = in.readInt(); mIsItalic = in.readInt() == 1; if (in.readInt() == 1) { /* has FD */ @@ -287,24 +265,19 @@ public final class FontConfig implements Parcelable { } else { mFd = null; } - mResourceId = in.readInt(); } @Override public void writeToParcel(Parcel out, int flag) { out.writeString(mFontName); out.writeInt(mTtcIndex); - out.writeInt(mAxes.size()); - for (int i = 0; i < mAxes.size(); i++) { - mAxes.get(i).writeToParcel(out, flag); - } + out.writeTypedArray(mAxes, flag); out.writeInt(mWeight); out.writeInt(mIsItalic ? 1 : 0); out.writeInt(mFd == null ? 0 : 1); if (mFd != null) { mFd.writeToParcel(out, flag); } - out.writeInt(mResourceId); } @Override @@ -329,27 +302,27 @@ public final class FontConfig implements Parcelable { * Class that holds information about a Font alias. */ public static final class Alias implements Parcelable { - private final String mName; - private final String mToName; + private final @NonNull String mName; + private final @NonNull String mToName; private final int mWeight; - public Alias(String name, String toName, int weight) { - this.mName = name; - this.mToName = toName; - this.mWeight = weight; + public Alias(@NonNull String name, @NonNull String toName, int weight) { + mName = name; + mToName = toName; + mWeight = weight; } /** * Returns the new name for the alias. */ - public String getName() { + public @NonNull String getName() { return mName; } /** * Returns the existing name to which this alias points to. */ - public String getToName() { + public @NonNull String getToName() { return mToName; } @@ -398,149 +371,110 @@ public final class FontConfig implements Parcelable { * Class that holds information about a Font family. */ public static final class Family implements Parcelable { - private final String mName; - private final List<Font> mFonts; - private final String mLanguage; - private final String mVariant; - private final String mProviderAuthority; - private final String mProviderPackage; - private final String mQuery; - - public Family(String name, List<Font> fonts, String language, String variant) { + private final @NonNull String mName; + private final @NonNull Font[] mFonts; + private final @NonNull String mLanguage; + + /** @hide */ + @Retention(SOURCE) + @IntDef({VARIANT_DEFAULT, VARIANT_COMPACT, VARIANT_ELEGANT}) + public @interface Variant {} + + /** + * Value for font variant. + * + * Indicates the font has no variant attribute. + */ + public static final int VARIANT_DEFAULT = 0; + + /** + * Value for font variant. + * + * Indicates the font is for compact variant. + * @see android.graphics.Paint#setElegantTextHeight + */ + public static final int VARIANT_COMPACT = 1; + + /** + * Value for font variant. + * + * Indiates the font is for elegant variant. + * @see android.graphics.Paint#setElegantTextHeight + */ + public static final int VARIANT_ELEGANT = 2; + + // Must be same with Minikin's variant values. + // See frameworks/minikin/include/minikin/FontFamily.h + private final @Variant int mVariant; + + public Family(@NonNull String name, @NonNull Font[] fonts, @NonNull String language, + @Variant int variant) { mName = name; mFonts = fonts; mLanguage = language; mVariant = variant; - mProviderAuthority = null; - mProviderPackage = null; - mQuery = null; } /** + * For duplicating file descriptor underlying Font object. + * + * This copy constructor is not for deep copying. * @hide */ - public Family(String providerAuthority, String providerPackage, String query) { - mName = null; - mFonts = null; - mLanguage = null; - mVariant = null; - mProviderAuthority = providerAuthority; - mProviderPackage = providerPackage; - mQuery = query; - } - public Family(Family origin) { mName = origin.mName; mLanguage = origin.mLanguage; mVariant = origin.mVariant; - mFonts = new ArrayList<>(); - for (int i = 0; i < origin.mFonts.size(); i++) { - mFonts.add(new Font(origin.mFonts.get(i))); + mFonts = new Font[origin.mFonts.length]; + for (int i = 0; i < origin.mFonts.length; ++i) { + mFonts[i] = new Font(origin.mFonts[i]); } - mProviderAuthority = origin.mProviderAuthority; - mProviderPackage = origin.mProviderPackage; - mQuery = origin.mQuery; } /** * Returns the name given by the system to this font family. */ - public String getName() { + public @Nullable String getName() { return mName; } /** * Returns the list of fonts included in this family. */ - public List<Font> getFonts() { + public @Nullable Font[] getFonts() { return mFonts; } /** * Returns the language for this family. May be null. */ - public String getLanguage() { + public @Nullable String getLanguage() { return mLanguage; } /** * Returns the font variant for this family, e.g. "elegant" or "compact". May be null. */ - public String getVariant() { + public @Variant int getVariant() { return mVariant; } /** * @hide */ - public String getProviderAuthority() { - return mProviderAuthority; - } - - /** - * @hide - */ - public String getProviderPackage() { - return mProviderPackage; - } - - /** - * @hide - */ - public String getQuery() { - return mQuery; - } - - /** - * @hide - */ public Family(Parcel in) { mName = in.readString(); - final int size = in.readInt(); - mFonts = new ArrayList<>(); - for (int i = 0; i < size; i++) { - mFonts.add(new Font(in)); - } + mFonts = in.readTypedArray(Font.CREATOR); mLanguage = in.readString(); - mVariant = in.readString(); - if (in.readInt() == 1) { - mProviderAuthority = in.readString(); - } else { - mProviderAuthority = null; - } - if (in.readInt() == 1) { - mProviderPackage = in.readString(); - } else { - mProviderPackage = null; - } - if (in.readInt() == 1) { - mQuery = in.readString(); - } else { - mQuery = null; - } + mVariant = in.readInt(); } @Override public void writeToParcel(Parcel out, int flag) { out.writeString(mName); - out.writeInt(mFonts.size()); - for (int i = 0; i < mFonts.size(); i++) { - mFonts.get(i).writeToParcel(out, flag); - } + out.writeTypedArray(mFonts, flag); out.writeString(mLanguage); - out.writeString(mVariant); - out.writeInt(mProviderAuthority == null ? 0 : 1); - if (mProviderAuthority != null) { - out.writeString(mProviderAuthority); - } - out.writeInt(mProviderPackage == null ? 0 : 1); - if (mProviderPackage != null) { - out.writeString(mProviderPackage); - } - out.writeInt(mQuery == null ? 0 : 1); - if (mQuery != null) { - out.writeString(mQuery); - } + out.writeInt(mVariant); } @Override diff --git a/core/java/android/text/Hyphenator.java b/core/java/android/text/Hyphenator.java index c2508a6c92ef..ea1100eabf8c 100644 --- a/core/java/android/text/Hyphenator.java +++ b/core/java/android/text/Hyphenator.java @@ -135,6 +135,10 @@ public class Hyphenator { private static Hyphenator loadHyphenator(HyphenationData data) { String patternFilename = "hyph-" + data.mLanguageTag.toLowerCase(Locale.US) + ".hyb"; File patternFile = new File(getSystemHyphenatorLocation(), patternFilename); + if (!patternFile.canRead()) { + Log.e(TAG, "hyphenation patterns for " + patternFile + " not found or unreadable"); + return null; + } try { RandomAccessFile f = new RandomAccessFile(patternFile, "r"); try { diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java index 94c463c8e651..353dfed343e0 100644 --- a/core/java/android/text/StaticLayout.java +++ b/core/java/android/text/StaticLayout.java @@ -1031,6 +1031,7 @@ public class StaticLayout extends Layout { float avail, TextUtils.TruncateAt where, int line, float textWidth, TextPaint paint, boolean forceEllipsis) { + avail -= getTotalInsets(line); if (textWidth <= avail && !forceEllipsis) { // Everything fits! mLines[mColumns * line + ELLIPSIS_START] = 0; @@ -1134,6 +1135,17 @@ public class StaticLayout extends Layout { mLines[mColumns * line + ELLIPSIS_COUNT] = ellipsisCount; } + private float getTotalInsets(int line) { + int totalIndent = 0; + if (mLeftIndents != null) { + totalIndent = mLeftIndents[Math.min(line, mLeftIndents.length - 1)]; + } + if (mRightIndents != null) { + totalIndent += mRightIndents[Math.min(line, mRightIndents.length - 1)]; + } + return totalIndent; + } + // Override the base class so we can directly access our members, // rather than relying on member functions. // The logic mirrors that of Layout.getLineForVertical diff --git a/core/java/android/transition/Transition.java b/core/java/android/transition/Transition.java index af2547e44b9f..255a02938c32 100644 --- a/core/java/android/transition/Transition.java +++ b/core/java/android/transition/Transition.java @@ -799,8 +799,10 @@ public abstract class Transition implements Cloneable { * targetId list. If the target parameter is null, then the target list * is not checked (this is in the case of ListView items, where the * views are ignored and only the ids are used). + * + * @hide */ - boolean isValidTarget(View target) { + public boolean isValidTarget(View target) { if (target == null) { return false; } diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl index dd76fcff6ec8..586b3b225fef 100644 --- a/core/java/android/view/IWindowManager.aidl +++ b/core/java/android/view/IWindowManager.aidl @@ -201,16 +201,18 @@ interface IWindowManager void updateRotation(boolean alwaysSendConfiguration, boolean forceRelayout); /** - * Retrieve the current screen orientation, constants as per - * {@link android.view.Surface}. + * Retrieve the current orientation of the primary screen. + * @return Constant as per {@link android.view.Surface.Rotation}. + * + * @see android.view.Display#DEFAULT_DISPLAY */ - int getRotation(); + int getDefaultDisplayRotation(); /** - * Watch the rotation of the screen. Returns the current rotation, + * Watch the rotation of the specified screen. Returns the current rotation, * calls back when it changes. */ - int watchRotation(IRotationWatcher watcher); + int watchRotation(IRotationWatcher watcher, int displayId); /** * Remove a rotation watcher set using watchRotation. diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl index 1facc106d645..7e6af11a62c7 100644 --- a/core/java/android/view/IWindowSession.aidl +++ b/core/java/android/view/IWindowSession.aidl @@ -98,31 +98,6 @@ interface IWindowSession { out Rect outOutsets, out Rect outBackdropFrame, out Configuration outConfig, out Surface outSurface); - /** - * Position a window relative to it's parent (attached) window without triggering - * a full relayout. This action may be deferred until a given frame number - * for the parent window appears. This allows for synchronizing movement of a child - * to repainting the contents of the parent. - * - * "width" and "height" correspond to the width and height members of - * WindowManager.LayoutParams in the {@link #relayout relayout()} case. - * This may differ from the surface buffer size in the - * case of {@link LayoutParams#FLAG_SCALED} and {@link #relayout relayout()} - * must be used with requestedWidth/height if this must be changed. - * - * @param window The window being modified. Must be attached to a parent window - * or this call will fail. - * @param left The new left position - * @param top The new top position - * @param right The new right position - * @param bottom The new bottom position - * @param deferTransactionUntilFrame Frame number from our parent (attached) to - * defer this action until. - * @param outFrame Rect in which is placed the new position/size on screen. - */ - void repositionChild(IWindow childWindow, int left, int top, int right, int bottom, - long deferTransactionUntilFrame, out Rect outFrame); - /* * Notify the window manager that an application is relaunching and * windows should be prepared for replacement. @@ -134,12 +109,6 @@ interface IWindowSession { void prepareToReplaceWindows(IBinder appToken, boolean childrenOnly); /** - * If a call to relayout() asked to have the surface destroy deferred, - * it must call this once it is okay to destroy that surface. - */ - void performDeferredDestroy(IWindow window); - - /** * Called by a client to report that it ran out of graphics memory. */ boolean outOfMemory(IWindow window); diff --git a/core/java/android/view/LayoutInflater.java b/core/java/android/view/LayoutInflater.java index e3ac40c5da4e..0e06cd32161b 100644 --- a/core/java/android/view/LayoutInflater.java +++ b/core/java/android/view/LayoutInflater.java @@ -833,6 +833,7 @@ public abstract class LayoutInflater { final int depth = parser.getDepth(); int type; + boolean pendingRequestFocus = false; while (((type = parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) { @@ -844,7 +845,8 @@ public abstract class LayoutInflater { final String name = parser.getName(); if (TAG_REQUEST_FOCUS.equals(name)) { - parseRequestFocus(parser, parent); + pendingRequestFocus = true; + consumeChildElements(parser); } else if (TAG_TAG.equals(name)) { parseViewTag(parser, parent, attrs); } else if (TAG_INCLUDE.equals(name)) { @@ -863,23 +865,16 @@ public abstract class LayoutInflater { } } + if (pendingRequestFocus) { + parent.restoreDefaultFocus(); + } + if (finishInflate) { parent.onFinishInflate(); } } /** - * Parses a <code><request-focus></code> element and requests focus on - * the containing View. - */ - private void parseRequestFocus(XmlPullParser parser, View view) - throws XmlPullParserException, IOException { - view.requestFocus(); - - consumeChildElements(parser); - } - - /** * Parses a <code><tag></code> element and sets a keyed tag on the * containing View. */ diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java index 61b12475d542..6d320ef32b0d 100644 --- a/core/java/android/view/SurfaceView.java +++ b/core/java/android/view/SurfaceView.java @@ -500,7 +500,12 @@ public class SurfaceView extends View { // While creating the surface, we will set it's initial // geometry. Outside of that though, we should generally // leave it to the RenderThread. - if (creating || !mRtHandlingPositionUpdates) { + // + // There is one more case when the buffer size changes we aren't yet + // prepared to sync (as even following the transaction applying + // we still need to latch a buffer). + // b/28866173 + if (sizeChanged || creating || !mRtHandlingPositionUpdates) { mSurfaceControl.setPosition(mScreenRect.left, mScreenRect.top); mSurfaceControl.setMatrix(mScreenRect.width() / (float) mSurfaceWidth, 0.0f, 0.0f, diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index aa1cbf2d642e..80f6c3249428 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -978,142 +978,123 @@ public class View implements Drawable.Callback, KeyEvent.Callback, */ public static final int AUTOFILL_MODE_MANUAL = 2; - /** @hide */ - @IntDef({ - AUTOFILL_HINT_NONE, - AUTOFILL_HINT_EMAIL_ADDRESS, - AUTOFILL_HINT_NAME, - AUTOFILL_HINT_POSTAL_ADDRESS, - AUTOFILL_HINT_PASSWORD, - AUTOFILL_HINT_PHONE, - AUTOFILL_HINT_USERNAME, - AUTOFILL_HINT_POSTAL_CODE, - AUTOFILL_HINT_CREDIT_CARD_NUMBER, - AUTOFILL_HINT_CREDIT_CARD_SECURITY_CODE, - AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_DATE, - AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_MONTH, - AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_YEAR, - AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_DAY, - }) - @Retention(RetentionPolicy.SOURCE) - public @interface AutofillHint {} - - /** - * No autofill hint is set. - * - * Use with {@link #setAutofillHint(int)} and <a href="#attr_android:autofillHint"> - * {@code android:autofillHint}. - */ - public static final int AUTOFILL_HINT_NONE = 0; - /** * This view contains an email address. * - * Use with {@link #setAutofillHint(int)} and <a href="#attr_android:autofillHint"> - * {@code android:autofillHint}. + * Use with {@link #setAutofillHint(String[])}, or set "{@value #AUTOFILL_HINT_EMAIL_ADDRESS}" + * to <a href="#attr_android:autofillHint"> {@code android:autofillHint}. */ - public static final int AUTOFILL_HINT_EMAIL_ADDRESS = 0x1; + public static final String AUTOFILL_HINT_EMAIL_ADDRESS = "emailAddress"; /** * The view contains a real name. * - * Use with {@link #setAutofillHint(int)} and <a href="#attr_android:autofillHint"> - * {@code android:autofillHint}. + * Use with {@link #setAutofillHint(String[])}, or set "{@value #AUTOFILL_HINT_NAME}" to + * <a href="#attr_android:autofillHint"> {@code android:autofillHint}. */ - public static final int AUTOFILL_HINT_NAME = 0x2; + public static final String AUTOFILL_HINT_NAME = "name"; /** * The view contains a user name. * - * Use with {@link #setAutofillHint(int)} and <a href="#attr_android:autofillHint"> - * {@code android:autofillHint}. + * Use with {@link #setAutofillHint(String[])}, or set "{@value #AUTOFILL_HINT_USERNAME}" to + * <a href="#attr_android:autofillHint"> {@code android:autofillHint}. */ - public static final int AUTOFILL_HINT_USERNAME = 0x4; + public static final String AUTOFILL_HINT_USERNAME = "username"; /** * The view contains a password. * - * Use with {@link #setAutofillHint(int)} and <a href="#attr_android:autofillHint"> - * {@code android:autofillHint}. + * Use with {@link #setAutofillHint(String[])}, or set "{@value #AUTOFILL_HINT_PASSWORD}" to + * <a href="#attr_android:autofillHint"> {@code android:autofillHint}. */ - public static final int AUTOFILL_HINT_PASSWORD = 0x8; + public static final String AUTOFILL_HINT_PASSWORD = "password"; /** * The view contains a phone number. * - * Use with {@link #setAutofillHint(int)} and <a href="#attr_android:autofillHint"> - * {@code android:autofillHint}. + * Use with {@link #setAutofillHint(String[])}, or set "{@value #AUTOFILL_HINT_PHONE}" to + * <a href="#attr_android:autofillHint"> {@code android:autofillHint}. */ - public static final int AUTOFILL_HINT_PHONE = 0x10; + public static final String AUTOFILL_HINT_PHONE = "phone"; /** * The view contains a postal address. * - * Use with {@link #setAutofillHint(int)} and <a href="#attr_android:autofillHint"> - * {@code android:autofillHint}. + * Use with {@link #setAutofillHint(String[])}, or set "{@value #AUTOFILL_HINT_POSTAL_ADDRESS}" + * to <a href="#attr_android:autofillHint"> {@code android:autofillHint}. */ - public static final int AUTOFILL_HINT_POSTAL_ADDRESS = 0x20; + public static final String AUTOFILL_HINT_POSTAL_ADDRESS = "postalAddress"; /** * The view contains a postal code. * - * Use with {@link #setAutofillHint(int)} and <a href="#attr_android:autofillHint"> - * {@code android:autofillHint}. + * Use with {@link #setAutofillHint(String[])}, or set "{@value #AUTOFILL_HINT_POSTAL_CODE}" to + * <a href="#attr_android:autofillHint"> {@code android:autofillHint}. */ - public static final int AUTOFILL_HINT_POSTAL_CODE = 0x40; + public static final String AUTOFILL_HINT_POSTAL_CODE = "postalCode"; /** * The view contains a credit card number. * - * Use with {@link #setAutofillHint(int)} and <a href="#attr_android:autofillHint"> - * {@code android:autofillHint}. + * Use with {@link #setAutofillHint(String[])}, or set "{@value + * #AUTOFILL_HINT_CREDIT_CARD_NUMBER}" to <a href="#attr_android:autofillHint"> {@code + * android:autofillHint}. */ - public static final int AUTOFILL_HINT_CREDIT_CARD_NUMBER = 0x80; + public static final String AUTOFILL_HINT_CREDIT_CARD_NUMBER = "creditCardNumber"; /** * The view contains a credit card security code. * - * Use with {@link #setAutofillHint(int)} and <a href="#attr_android:autofillHint"> - * {@code android:autofillHint}. + * Use with {@link #setAutofillHint(String[])}, or set "{@value + * #AUTOFILL_HINT_CREDIT_CARD_SECURITY_CODE}" to <a href="#attr_android:autofillHint"> {@code + * android:autofillHint}. */ - public static final int AUTOFILL_HINT_CREDIT_CARD_SECURITY_CODE = 0x100; + public static final String AUTOFILL_HINT_CREDIT_CARD_SECURITY_CODE = "creditCardSecurityCode"; /** * The view contains a credit card expiration date. * - * Use with {@link #setAutofillHint(int)} and <a href="#attr_android:autofillHint"> - * {@code android:autofillHint}. + * Use with {@link #setAutofillHint(String[])}, or set "{@value + * #AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_DATE}" to <a href="#attr_android:autofillHint"> {@code + * android:autofillHint}. */ - public static final int AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_DATE = 0x200; + public static final String AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_DATE = + "creditCardExpirationDate"; /** * The view contains the month a credit card expires. * - * Use with {@link #setAutofillHint(int)} and <a href="#attr_android:autofillHint"> - * {@code android:autofillHint}. + * Use with {@link #setAutofillHint(String[])}, or set "{@value + * #AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_MONTH}" to <a href="#attr_android:autofillHint"> {@code + * android:autofillHint}. */ - public static final int AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_MONTH = 0x400; + public static final String AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_MONTH = + "creditCardExpirationMonth"; /** * The view contains the year a credit card expires. * - * Use with {@link #setAutofillHint(int)} and <a href="#attr_android:autofillHint"> - * {@code android:autofillHint}. + * Use with {@link #setAutofillHint(String[])}, or set "{@value + * #AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_YEAR}" to <a href="#attr_android:autofillHint"> {@code + * android:autofillHint}. */ - public static final int AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_YEAR = 0x800; + public static final String AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_YEAR = + "creditCardExpirationYear"; /** * The view contains the day a credit card expires. * - * Use with {@link #setAutofillHint(int)} and <a href="#attr_android:autofillHint"> - * {@code android:autofillHint}. + * Use with {@link #setAutofillHint(String[])}, or set "{@value + * #AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_DAY}" to <a href="#attr_android:autofillHint"> {@code + * android:autofillHint}. */ - public static final int AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_DAY = 0x1000; + public static final String AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_DAY = "creditCardExpirationDay"; /** - * Hint for the autofill services that describes the content of the view. + * Hintd for the autofill services that describes the content of the view. */ - @AutofillHint private int mAutofillHint; + private @Nullable String[] mAutofillHint; /** @hide */ @IntDef({ @@ -5049,7 +5030,37 @@ public class View implements Drawable.Callback, KeyEvent.Callback, break; case R.styleable.View_autofillHint: if (a.peekValue(attr) != null) { - setAutofillHint(a.getInt(attr, AUTOFILL_HINT_NONE)); + CharSequence[] rawHints = null; + String rawString = null; + + if (a.getType(attr) == TypedValue.TYPE_REFERENCE) { + int resId = a.getResourceId(attr, 0); + + try { + rawHints = a.getTextArray(attr); + } catch (NullPointerException e) { + rawString = getResources().getString(resId); + } + } else { + rawString = a.getString(attr); + } + + if (rawHints == null) { + if (rawString == null) { + throw new IllegalArgumentException( + "Could not resolve autofillHint"); + } else { + rawHints = rawString.split(","); + } + } + + String[] hints = new String[rawHints.length]; + + int numHints = rawHints.length; + for (int rawHintNum = 0; rawHintNum < numHints; rawHintNum++) { + hints[rawHintNum] = rawHints[rawHintNum].toString().trim(); + } + setAutofillHint(hints); } break; case R.styleable.View_importantForAutofill: @@ -6132,6 +6143,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED); + + notifyEnterOrExitForAutoFillIfNeeded(true); + return result; } @@ -6791,14 +6805,18 @@ public class View implements Drawable.Callback, KeyEvent.Callback, mAttachInfo.mKeyDispatchState.reset(this); } + notifyEnterOrExitForAutoFillIfNeeded(gainFocus); + } + + private void notifyEnterOrExitForAutoFillIfNeeded(boolean enter) { if (isAutofillable() && isAttachedToWindow() && getResolvedAutofillMode() == AUTOFILL_MODE_AUTO) { AutofillManager afm = getAutofillManager(); if (afm != null) { - if (gainFocus) { - afm.startAutofillRequest(this); - } else { - afm.stopAutofillRequest(this); + if (enter && hasWindowFocus() && isFocused()) { + afm.notifyViewEntered(this); + } else if (!hasWindowFocus() || !isFocused()) { + afm.notifyViewExited(this); } } } @@ -7250,9 +7268,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * Called when assist structure is being retrieved from a view as part of an autofill request. * * <p>This method already provides most of what's needed for autofill, but should be overridden + * when: * <ol> * <li>The view contents does not include PII (Personally Identifiable Information), so it - * can call {@link ViewStructure#setSanitized(boolean)} passing {@code true}. + * can call {@link ViewStructure#setDataIsSensitive(boolean)} passing {@code false}. * <li>It must set fields such {@link ViewStructure#setText(CharSequence)}, * {@link ViewStructure#setAutofillOptions(String[])}, or {@link ViewStructure#setUrl(String)}. * </ol> @@ -7368,13 +7387,17 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * <li>Also implement {@link #autofillVirtual(int, AutofillValue)} to autofill the virtual * children. * <li>Call - * {@link android.view.autofill.AutofillManager#startAutofillRequestOnVirtualView} and - * {@link android.view.autofill.AutofillManager#stopAutofillRequestOnVirtualView(View, int)} + * {@link android.view.autofill.AutofillManager#notifyVirtualViewEntered} and + * {@link android.view.autofill.AutofillManager#notifyVirtualViewExited(View, int)} * when the focus inside the view changed. - * <li>Call {@link android.view.autofill.AutofillManager#virtualValueChanged(View, int, + * <li>Call {@link android.view.autofill.AutofillManager#notifyVirtualValueChanged(View, int, * AutofillValue)} when the value of a child changed. - * <li>Call {@link android.view.autofill.AutofillManager#reset()} when the autofill context - * of the view structure changed. + * <li>Call {@link AutofillManager#commit()} when the autofill context + * of the view structure changed and you want the current autofill interaction if such + * to be commited. + * <li>Call {@link AutofillManager#cancel()} ()} when the autofill context + * of the view structure changed and you want the current autofill interaction if such + * to be cancelled. * </ol> * * @param structure Fill in with structured view data. @@ -7453,12 +7476,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback, /** * Describes the content of a view so that a autofill service can fill in the appropriate data. * - * @return The hint set via the attribute + * @return The hint set via the attribute or {@code null} if no hint it set. * * @attr ref android.R.styleable#View_autofillHint */ @ViewDebug.ExportedProperty() - @AutofillHint public int getAutofillHint() { + @Nullable public String[] getAutofillHint() { return mAutofillHint; } @@ -9088,11 +9111,15 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * Sets the a hint that helps the autofill service to select the appropriate data to fill the * view. * - * @param autofillHint The autofill hint to set + * @param autofillHint The autofill hint to set. If the array is emtpy, {@code null} is set. * @attr ref android.R.styleable#View_autofillHint */ - public void setAutofillHint(@AutofillHint int autofillHint) { - mAutofillHint = autofillHint; + public void setAutofillHint(@Nullable String... autofillHint) { + if (autofillHint == null || autofillHint.length == 0) { + mAutofillHint = null; + } else { + mAutofillHint = autofillHint; + } } /** @@ -11085,6 +11112,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, @CallSuper public void dispatchStartTemporaryDetach() { mPrivateFlags3 |= PFLAG3_TEMPORARY_DETACH; + notifyEnterOrExitForAutoFillIfNeeded(false); onStartTemporaryDetach(); } @@ -11110,6 +11138,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, if (hasWindowFocus() && hasFocus()) { InputMethodManager.getInstance().focusIn(this); } + notifyEnterOrExitForAutoFillIfNeeded(true); } /** @@ -11512,6 +11541,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } else if (imm != null && (mPrivateFlags & PFLAG_FOCUSED) != 0) { imm.focusIn(this); } + + notifyEnterOrExitForAutoFillIfNeeded(hasWindowFocus); + refreshDrawableState(); } @@ -16879,12 +16911,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } needGlobalAttributesUpdate(false); - if (isAutofillable() && isFocused() && getResolvedAutofillMode() == AUTOFILL_MODE_AUTO) { - AutofillManager afm = getAutofillManager(); - if (afm != null) { - afm.startAutofillRequest(this); - } - } + notifyEnterOrExitForAutoFillIfNeeded(true); } void dispatchDetachedFromWindow() { @@ -16932,12 +16959,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, mOverlay.getOverlayView().dispatchDetachedFromWindow(); } - if (isAutofillable() && isFocused() && getResolvedAutofillMode() == AUTOFILL_MODE_AUTO) { - AutofillManager afm = getAutofillManager(); - if (afm != null) { - afm.stopAutofillRequest(this); - } - } + notifyEnterOrExitForAutoFillIfNeeded(false); } /** diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java index 9fa9985e02ec..de0ec40ac97b 100644 --- a/core/java/android/view/ViewGroup.java +++ b/core/java/android/view/ViewGroup.java @@ -46,6 +46,7 @@ import android.os.Parcelable; import android.os.SystemClock; import android.util.AttributeSet; import android.util.Log; +import android.util.Pools; import android.util.Pools.SynchronizedPool; import android.util.SparseArray; import android.util.SparseBooleanArray; @@ -3327,7 +3328,74 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager @Override public void dispatchProvideStructure(ViewStructure structure) { super.dispatchProvideStructure(structure); - dispatchProvideStructureForAssistOrAutoFill(structure, false); + if (isAssistBlocked() || structure.getChildCount() != 0) { + return; + } + final int childrenCount = mChildrenCount; + if (childrenCount <= 0) { + return; + } + structure.setChildCount(childrenCount); + ArrayList<View> preorderedList = buildOrderedChildList(); + boolean customOrder = preorderedList == null + && isChildrenDrawingOrderEnabled(); + for (int i = 0; i < childrenCount; i++) { + int childIndex; + try { + childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder); + } catch (IndexOutOfBoundsException e) { + childIndex = i; + if (mContext.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.M) { + Log.w(TAG, "Bad getChildDrawingOrder while collecting assist @ " + + i + " of " + childrenCount, e); + // At least one app is failing when we call getChildDrawingOrder + // at this point, so deal semi-gracefully with it by falling back + // on the basic order. + customOrder = false; + if (i > 0) { + // If we failed at the first index, there really isn't + // anything to do -- we will just proceed with the simple + // sequence order. + // Otherwise, we failed in the middle, so need to come up + // with an order for the remaining indices and use that. + // Failed at the first one, easy peasy. + int[] permutation = new int[childrenCount]; + SparseBooleanArray usedIndices = new SparseBooleanArray(); + // Go back and collected the indices we have done so far. + for (int j = 0; j < i; j++) { + permutation[j] = getChildDrawingOrder(childrenCount, j); + usedIndices.put(permutation[j], true); + } + // Fill in the remaining indices with indices that have not + // yet been used. + int nextIndex = 0; + for (int j = i; j < childrenCount; j++) { + while (usedIndices.get(nextIndex, false)) { + nextIndex++; + } + permutation[j] = nextIndex; + nextIndex++; + } + // Build the final view list. + preorderedList = new ArrayList<>(childrenCount); + for (int j = 0; j < childrenCount; j++) { + final int index = permutation[j]; + final View child = mChildren[index]; + preorderedList.add(child); + } + } + } else { + throw e; + } + } + final View child = getAndVerifyPreorderedView(preorderedList, mChildren, + childIndex); + final ViewStructure cstructure = structure.newChild(i); + child.dispatchProvideStructure(cstructure); + } + if (preorderedList != null) { + preorderedList.clear(); + } } /** @@ -3339,21 +3407,44 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager @Override public void dispatchProvideAutofillStructure(ViewStructure structure, int flags) { super.dispatchProvideAutofillStructure(structure, flags); - dispatchProvideStructureForAssistOrAutoFill(structure, true); + if (isAutofillBlocked() || structure.getChildCount() != 0) { + return; + } + final ChildListForAutoFill children = getChildrenForAutofill(); + final int childrenCount = children.size(); + structure.setChildCount(childrenCount); + for (int i = 0; i < childrenCount; i++) { + final View child = children.get(i); + final ViewStructure cstructure = structure.newChild(i); + child.dispatchProvideAutofillStructure(cstructure, flags); + } + children.recycle(); } - /** @hide */ - private ArrayList<View> getChildrenForAutofill() { - final ArrayList<View> list = new ArrayList<>(); - populateChildrenForAutofill(list); - return list; + /** + * Gets the children for autofill. Children for autofill are the first + * level descendants that are important for autofill. The returned + * child list object is pooled and the caller must recycle it once done. + * @hide */ + private @NonNull ChildListForAutoFill getChildrenForAutofill() { + final ChildListForAutoFill children = ChildListForAutoFill.obtain(); + populateChildrenForAutofill(children); + return children; } /** @hide */ private void populateChildrenForAutofill(ArrayList<View> list) { - final int count = mChildrenCount; - for (int i = 0; i < count; i++) { - final View child = mChildren[i]; + final int childrenCount = mChildrenCount; + if (childrenCount <= 0) { + return; + } + final ArrayList<View> preorderedList = buildOrderedChildList(); + final boolean customOrder = preorderedList == null + && isChildrenDrawingOrderEnabled(); + for (int i = 0; i < childrenCount; i++) { + final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder); + final View child = (preorderedList == null) + ? mChildren[childIndex] : preorderedList.get(childIndex); if (child.isImportantForAutofill()) { list.add(child); } else if (child instanceof ViewGroup) { @@ -3362,106 +3453,6 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager } } - private void dispatchProvideStructureForAssistOrAutoFill(ViewStructure structure, - boolean forAutofill) { - boolean blocked = forAutofill ? isAutofillBlocked() : isAssistBlocked(); - if (blocked || structure.getChildCount() != 0) { - return; - } - final View[] childrenArray; - final ArrayList<View> childrenList; - final int childrenCount; - - if (forAutofill) { - childrenArray = null; - // TODO(b/33197203): the current algorithm allocates a new list for each children that - // is a view group; ideally, we should use mAttachInfo.mTempArrayList instead, but that - // would complicated the algorithm a lot... - childrenList = getChildrenForAutofill(); - - childrenCount = childrenList.size(); - } else { - childrenArray = mChildren; - childrenList = null; - childrenCount = getChildCount(); - } - - if (childrenCount > 0) { - structure.setChildCount(childrenCount); - ArrayList<View> preorderedList = buildOrderedChildList(); - boolean customOrder = preorderedList == null - && isChildrenDrawingOrderEnabled(); - for (int i = 0; i < childrenCount; i++) { - int childIndex; - try { - childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder); - } catch (IndexOutOfBoundsException e) { - childIndex = i; - if (mContext.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.M) { - Log.w(TAG, "Bad getChildDrawingOrder while collecting assist @ " - + i + " of " + childrenCount, e); - // At least one app is failing when we call getChildDrawingOrder - // at this point, so deal semi-gracefully with it by falling back - // on the basic order. - customOrder = false; - if (i > 0) { - // If we failed at the first index, there really isn't - // anything to do -- we will just proceed with the simple - // sequence order. - // Otherwise, we failed in the middle, so need to come up - // with an order for the remaining indices and use that. - // Failed at the first one, easy peasy. - int[] permutation = new int[childrenCount]; - SparseBooleanArray usedIndices = new SparseBooleanArray(); - // Go back and collected the indices we have done so far. - for (int j = 0; j < i; j++) { - permutation[j] = getChildDrawingOrder(childrenCount, j); - usedIndices.put(permutation[j], true); - } - // Fill in the remaining indices with indices that have not - // yet been used. - int nextIndex = 0; - for (int j = i; j < childrenCount; j++) { - while (usedIndices.get(nextIndex, false)) { - nextIndex++; - } - permutation[j] = nextIndex; - nextIndex++; - } - // Build the final view list. - preorderedList = new ArrayList<>(childrenCount); - for (int j = 0; j < childrenCount; j++) { - final int index = permutation[j]; - final View child = forAutofill - ? childrenList.get(index) - : childrenArray[index]; - preorderedList.add(child); - } - } - } else { - throw e; - } - } - - final View child = forAutofill - ? getAndVerifyPreorderedView(preorderedList, childrenList, childIndex) - : getAndVerifyPreorderedView(preorderedList, childrenArray, childIndex); - final ViewStructure cstructure = structure.newChild(i); - - // Must explicitly check which recursive method to call. - if (forAutofill) { - // NOTE: flags are not currently supported, hence 0 - child.dispatchProvideAutofillStructure(cstructure, 0); - } else { - child.dispatchProvideStructure(cstructure); - } - } - if (preorderedList != null) { - preorderedList.clear(); - } - } - } - private static View getAndVerifyPreorderedView(ArrayList<View> preorderedList, View[] children, int childIndex) { final View child; @@ -3477,21 +3468,6 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager return child; } - private static View getAndVerifyPreorderedView(ArrayList<View> preorderedList, - ArrayList<View> children, int childIndex) { - final View child; - if (preorderedList != null) { - child = preorderedList.get(childIndex); - if (child == null) { - throw new RuntimeException("Invalid preorderedList contained null child at index " - + childIndex); - } - } else { - child = children.get(childIndex); - } - return child; - } - /** @hide */ @Override public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) { @@ -8192,6 +8168,29 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager } /** + * Pooled class that to hold the children for autifill. + */ + static class ChildListForAutoFill extends ArrayList<View> { + private static final int MAX_POOL_SIZE = 32; + + private static final Pools.SimplePool<ChildListForAutoFill> sPool = + new Pools.SimplePool<>(MAX_POOL_SIZE); + + public static ChildListForAutoFill obtain() { + ChildListForAutoFill list = sPool.acquire(); + if (list == null) { + list = new ChildListForAutoFill(); + } + return list; + } + + public void recycle() { + clear(); + sPool.release(this); + } + } + + /** * Pooled class that orderes the children of a ViewGroup from start * to end based on how they are laid out and the layout direction. */ @@ -8228,10 +8227,6 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager return mChildren.get(index); } - public int getChildIndex(View child) { - return mChildren.indexOf(child); - } - private void init(ViewGroup parent, boolean sort) { ArrayList<View> children = mChildren; final int childCount = parent.getChildCount(); diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 580888c4c3bc..ed4238501c5a 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -164,6 +164,17 @@ public final class ViewRootImpl implements ViewParent, 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 @@ public final class ViewRootImpl implements ViewParent, mFallbackEventHandler = new PhoneFallbackEventHandler(context); mChoreographer = Choreographer.getInstance(); mDisplayManager = (DisplayManager)context.getSystemService(Context.DISPLAY_SERVICE); + + if (!sCompatibilityDone) { + sAlwaysAssignFocus = mTargetSdkVersion < Build.VERSION_CODES.O; + + sCompatibilityDone = true; + } + loadSystemProperties(); } @@ -2180,7 +2198,7 @@ public final class ViewRootImpl implements ViewParent, } } - if (mFirst) { + if (mFirst && sAlwaysAssignFocus) { // handle first focus request if (DEBUG_INPUT_RESIZE) Log.v(mTag, "First: mView.hasFocus()=" + mView.hasFocus()); @@ -3290,7 +3308,9 @@ public final class ViewRootImpl implements ViewParent, checkThread(); if (mView != null) { if (!mView.hasFocus()) { - v.requestFocus(); + if (sAlwaysAssignFocus) { + v.requestFocus(); + } } else { // the one case where will transfer focus away from the current one // is if the current view is a view group that prefers to give focus @@ -4463,9 +4483,7 @@ public final class ViewRootImpl implements ViewParent, return true; } } else { - // find the best view to give focus to in this non-touch-mode with no-focus - View v = focusSearch(null, direction); - if (v != null && v.requestFocus(direction)) { + if (mView.restoreDefaultFocus()) { return true; } } @@ -4475,9 +4493,10 @@ public final class ViewRootImpl implements ViewParent, private boolean performKeyboardGroupNavigation(int direction) { final View focused = mView.findFocus(); - View cluster = focused != null - ? focused.keyboardNavigationClusterSearch(null, direction) - : keyboardNavigationClusterSearch(null, direction); + if (focused == null && mView.restoreDefaultFocus()) { + return true; + } + View cluster = focused.keyboardNavigationClusterSearch(null, direction); // Since requestFocus only takes "real" focus directions (and therefore also // restoreFocusInCluster), convert forward/backward focus into FOCUS_DOWN. diff --git a/core/java/android/view/ViewStructure.java b/core/java/android/view/ViewStructure.java index bccaca2c8baa..38c7738b4bac 100644 --- a/core/java/android/view/ViewStructure.java +++ b/core/java/android/view/ViewStructure.java @@ -16,6 +16,7 @@ package android.view; +import android.annotation.Nullable; import android.graphics.Matrix; import android.graphics.Rect; import android.os.Bundle; @@ -323,7 +324,7 @@ public abstract class ViewStructure { * Sets the a hint that helps the autofill service to select the appropriate data to fill the * view. */ - public abstract void setAutofillHint(@View.AutofillHint int hint); + public abstract void setAutofillHint(@Nullable String[] hint); /** * Sets the {@link AutofillValue} representing the current value of this node. @@ -346,19 +347,27 @@ public abstract class ViewStructure { public abstract void setInputType(int inputType); /** - * Marks this node as sanitized so its content are sent on {@link + * Sets whether the data on this node is sensitive; if it is, then its content (text, autofill + * value, etc..) is striped before calls to {@link * android.service.autofill.AutofillService#onFillRequest(android.app.assist.AssistStructure, - * Bundle, android.os.CancellationSignal, android.service.autofill.FillCallback)}. + * Bundle, int, android.os.CancellationSignal, android.service.autofill.FillCallback)}. * - * <p>Only nodes that does not have PII (Personally Identifiable Information - sensitive data - * such as email addresses, credit card numbers, passwords, etc...) should be marked - * as sanitized; a good rule of thumb is to mark as sanitized nodes whose value were statically - * set from resources. + * <p>By default, all nodes are assumed to be sensitive, and only nodes that does not have PII + * (Personally Identifiable Information - sensitive data such as email addresses, credit card + * numbers, passwords, etc...) should be marked as non-sensitive; a good rule of thumb is to + * mark as non-sensitive nodes whose value were statically set from resources. + * + * <p>Notice that the content of even sensitive nodes are sent to the service (through the + * {@link + * android.service.autofill.AutofillService#onSaveRequest(android.app.assist.AssistStructure, + * Bundle, android.service.autofill.SaveCallback)} call) when the user consented to save + * thedata, so it is important to set the content of sensitive nodes as well, but mark them as + * sensitive. * * <p>Should only be set when the node is used for autofill purposes - it will be ignored * when used for Assist. */ - public abstract void setSanitized(boolean sanitized); + public abstract void setDataIsSensitive(boolean sensitive); /** * Call when done populating a {@link ViewStructure} returned by diff --git a/core/java/android/view/WindowManagerInternal.java b/core/java/android/view/WindowManagerInternal.java index a541a4cd3b1e..6dbc09ceef97 100644 --- a/core/java/android/view/WindowManagerInternal.java +++ b/core/java/android/view/WindowManagerInternal.java @@ -168,6 +168,14 @@ public abstract class WindowManagerInternal { public abstract void setMagnificationSpec(MagnificationSpec spec); /** + * Set by the accessibility framework to indicate whether the magnifiable regions of the display + * should be shown. + * + * @param show {@code true} to show magnifiable region bounds, {@code false} to hide + */ + public abstract void setForceShowMagnifiableBounds(boolean show); + + /** * Obtains the magnification regions. * * @param magnificationRegion the current magnification region diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java index fe888ec56c09..c9f9f310e0b8 100644 --- a/core/java/android/view/accessibility/AccessibilityManager.java +++ b/core/java/android/view/accessibility/AccessibilityManager.java @@ -40,6 +40,8 @@ import android.util.Log; import android.view.IWindow; import android.view.View; +import com.android.internal.util.IntPair; + import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -109,6 +111,8 @@ public final class AccessibilityManager { boolean mIsEnabled; + int mRelevantEventTypes = AccessibilityEvent.TYPES_ALL_MASK; + boolean mIsTouchExplorationEnabled; boolean mIsHighTextContrastEnabled; @@ -203,6 +207,11 @@ public final class AccessibilityManager { public void notifyServicesStateChanged() { mHandler.obtainMessage(MyHandler.MSG_NOTIFY_SERVICES_STATE_CHANGED).sendToTarget(); } + + @Override + public void setRelevantEventTypes(int eventTypes) { + mRelevantEventTypes = eventTypes; + } }; /** @@ -362,6 +371,14 @@ public final class AccessibilityManager { return; } } + if ((event.getEventType() & mRelevantEventTypes) == 0) { + if (DEBUG) { + Log.i(LOG_TAG, "Not dispatching irrelevant event: " + event + + " that is not among " + + AccessibilityEvent.eventTypeToString(mRelevantEventTypes)); + } + return; + } userId = mUserId; } try { @@ -865,8 +882,9 @@ public final class AccessibilityManager { } try { - final int stateFlags = service.addClient(mClient, mUserId); - setStateLocked(stateFlags); + final long userStateAndRelevantEvents = service.addClient(mClient, mUserId); + setStateLocked(IntPair.first(userStateAndRelevantEvents)); + mRelevantEventTypes = IntPair.second(userStateAndRelevantEvents); mService = service; } catch (RemoteException re) { Log.e(LOG_TAG, "AccessibilityManagerService is dead", re); diff --git a/core/java/android/view/accessibility/IAccessibilityManager.aidl b/core/java/android/view/accessibility/IAccessibilityManager.aidl index 157980f6cb83..06cb5dca8afa 100644 --- a/core/java/android/view/accessibility/IAccessibilityManager.aidl +++ b/core/java/android/view/accessibility/IAccessibilityManager.aidl @@ -38,7 +38,7 @@ interface IAccessibilityManager { oneway void sendAccessibilityEvent(in AccessibilityEvent uiEvent, int userId); - int addClient(IAccessibilityManagerClient client, int userId); + long addClient(IAccessibilityManagerClient client, int userId); List<AccessibilityServiceInfo> getInstalledAccessibilityServiceList(int userId); diff --git a/core/java/android/view/accessibility/IAccessibilityManagerClient.aidl b/core/java/android/view/accessibility/IAccessibilityManagerClient.aidl index 045ac917e4b8..9cc0315280c7 100644 --- a/core/java/android/view/accessibility/IAccessibilityManagerClient.aidl +++ b/core/java/android/view/accessibility/IAccessibilityManagerClient.aidl @@ -25,5 +25,8 @@ package android.view.accessibility; oneway interface IAccessibilityManagerClient { void setState(int stateFlags); + void notifyServicesStateChanged(); + + void setRelevantEventTypes(int eventTypes); } diff --git a/core/java/android/view/autofill/AutoFillValue.java b/core/java/android/view/autofill/AutoFillValue.java index 5dd17f1c2bee..4774d8f0a71a 100644 --- a/core/java/android/view/autofill/AutoFillValue.java +++ b/core/java/android/view/autofill/AutoFillValue.java @@ -89,11 +89,6 @@ public final class AutoFillValue implements Parcelable { return mRealValue.equals(other.mRealValue); } - /** @hide */ - public String coerceToString() { - return mRealValue.coerceToString(); - } - @Override public String toString() { if (!DEBUG) return super.toString(); diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java index 2a12e4b9c5ab..f036b9cddec4 100644 --- a/core/java/android/view/autofill/AutofillManager.java +++ b/core/java/android/view/autofill/AutofillManager.java @@ -71,10 +71,17 @@ public final class AutofillManager { public static final String EXTRA_AUTHENTICATION_RESULT = "android.view.autofill.extra.AUTHENTICATION_RESULT"; - /** @hide */ public static final int FLAG_START_SESSION = 0x1; - /** @hide */ public static final int FLAG_FOCUS_GAINED = 0x2; - /** @hide */ public static final int FLAG_FOCUS_LOST = 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,11 +128,71 @@ public final class AutofillManager { } /** - * Called when an autofill operation on a {@link View} should start. + * 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 triggered the autofill request. + * @param view {@link View} that was entered. */ - public void startAutofillRequest(@NonNull View view) { + public void notifyViewEntered(@NonNull View view) { ensureServiceClientAddedIfNeeded(); if (!mEnabled) { @@ -139,38 +206,37 @@ public final class AutofillManager { if (!mHasSession) { // Starts new session. - startSession(id, view.getWindowToken(), bounds, value); + startSession(id, view.getWindowToken(), bounds, value, 0); } else { // Update focus on existing session. - updateSession(id, bounds, value, FLAG_FOCUS_GAINED); + updateSession(id, bounds, value, FLAG_VIEW_ENTERED); } } /** - * Called when an autofill operation on a {@link View} should stop. + * Called when a {@link View} that supports autofill is exited. * - * @param view {@link View} that triggered the autofill request in - * {@link #startAutofillRequest(View)}. + * @param view {@link View} that was exited. */ - public void stopAutofillRequest(@NonNull View view) { + public void notifyViewExited(@NonNull View view) { ensureServiceClientAddedIfNeeded(); if (mEnabled && mHasSession) { final AutofillId id = getAutofillId(view); // Update focus on existing session. - updateSession(id, null, null, FLAG_FOCUS_LOST); + updateSession(id, null, null, FLAG_VIEW_EXITED); } } /** - * Called when an autofill operation on a virtual {@link View} should start. + * Called when a virtual view that supports autofill is entered. * - * @param parent parent of the {@link View} that triggered the autofill request. - * @param childId id identifying the virtual child inside the parent view. + * @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 startAutofillRequestOnVirtualView(@NonNull View parent, int childId, + public void notifyVirtualViewEntered(@NonNull View view, int childId, @NonNull Rect bounds) { ensureServiceClientAddedIfNeeded(); @@ -178,32 +244,31 @@ public final class AutofillManager { return; } - final AutofillId id = getAutofillId(parent, childId); + final AutofillId id = getAutofillId(view, childId); if (!mHasSession) { // Starts new session. - startSession(id, parent.getWindowToken(), bounds, null); + startSession(id, view.getWindowToken(), bounds, null, 0); } else { // Update focus on existing session. - updateSession(id, bounds, null, FLAG_FOCUS_GAINED); + updateSession(id, bounds, null, FLAG_VIEW_ENTERED); } } /** - * Called when an autofill operation on a virtual {@link View} should stop. + * Called when a virtual view that supports autofill is exited. * - * @param parent parent of the {@link View} that triggered the autofill request in - * {@link #startAutofillRequestOnVirtualView(View, int, Rect)}. - * @param childId id identifying the virtual child inside the parent view. + * @param view the {@link View} whose descendant is the virtual view. + * @param childId id identifying the virtual child inside the view. */ - public void stopAutofillRequestOnVirtualView(@NonNull View parent, int childId) { + public void notifyVirtualViewExited(@NonNull View view, int childId) { ensureServiceClientAddedIfNeeded(); if (mEnabled && mHasSession) { - final AutofillId id = getAutofillId(parent, childId); + final AutofillId id = getAutofillId(view, childId); // Update focus on existing session. - updateSession(id, null, null, FLAG_FOCUS_LOST); + updateSession(id, null, null, FLAG_VIEW_EXITED); } } @@ -212,7 +277,7 @@ public final class AutofillManager { * * @param view view whose value changed. */ - public void valueChanged(View view) { + public void notifyValueChanged(View view) { if (!mEnabled || !mHasSession) { return; } @@ -226,26 +291,26 @@ public final class AutofillManager { /** * Called to indicate the value of an autofillable virtual {@link View} changed. * - * @param parent parent view whose value changed. + * @param view the {@link View} whose descendant is the virtual view. * @param childId id identifying the virtual child inside the parent view. * @param value new value of the child. */ - public void virtualValueChanged(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); } /** - * Called to indicate the current autofill context should be reset. + * Called to indicate the current autofill context should be commited. * * <p>For example, when a virtual view is rendering an {@code HTML} page with a form, it should * call this method after the form is submitted and another page is rendered. */ - public void reset() { + public void commit() { if (!mEnabled && !mHasSession) { return; } @@ -253,6 +318,20 @@ public final class AutofillManager { finishSession(); } + /** + * Called to indicate the current autofill context should be cancelled. + * + * <p>For example, when a virtual view is rendering an {@code HTML} page with a form, it should + * call this method if the user does not post the form but moves to another form in this page. + */ + public void cancel() { + if (!mEnabled && !mHasSession) { + return; + } + + cancelSession(); + } + private AutofillClient getClient() { if (mContext instanceof AutofillClient) { return (AutofillClient) mContext; @@ -293,15 +372,16 @@ public final class AutofillManager { } 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(); @@ -324,9 +404,21 @@ public final class AutofillManager { } } + private void cancelSession() { + if (DEBUG) { + Log.d(TAG, "cancelSession()"); + } + mHasSession = false; + try { + mService.cancelSession(mContext.getActivityToken(), mContext.getUserId()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + private void updateSession(AutofillId id, Rect bounds, AutofillValue value, int flags) { if (DEBUG) { - if (VERBOSE || (flags & FLAG_FOCUS_LOST) != 0) { + if (VERBOSE || (flags & FLAG_VIEW_EXITED) != 0) { Log.d(TAG, "updateSession(): id=" + id + ", bounds=" + bounds + ", value=" + value + ", flags=" + flags); } diff --git a/core/java/android/view/autofill/AutofillValue.java b/core/java/android/view/autofill/AutofillValue.java index 0c7620e69c62..e2dd7fe9eeaa 100644 --- a/core/java/android/view/autofill/AutofillValue.java +++ b/core/java/android/view/autofill/AutofillValue.java @@ -16,13 +16,23 @@ package android.view.autofill; +import static android.view.View.AUTOFILL_TYPE_DATE; +import static android.view.View.AUTOFILL_TYPE_LIST; +import static android.view.View.AUTOFILL_TYPE_TEXT; +import static android.view.View.AUTOFILL_TYPE_TOGGLE; import static android.view.autofill.Helper.DEBUG; +import static android.view.autofill.Helper.VERBOSE; +import android.annotation.NonNull; import android.annotation.Nullable; import android.os.Parcel; import android.os.Parcelable; import android.view.View; +import com.android.internal.util.Preconditions; + +import java.util.Objects; + /** * Abstracts how a {@link View} can be autofilled by an * {@link android.service.autofill.AutofillService}. @@ -31,52 +41,107 @@ import android.view.View; * {@link View#getAutofillType()}. */ public final class AutofillValue implements Parcelable { - private final String mText; - private final int mListIndex; - private final boolean mToggle; - private final long mDate; + private final @View.AutofillType int mType; + private final @NonNull Object mValue; - private AutofillValue(CharSequence text, int listIndex, boolean toggle, long date) { - mText = (text == null) ? null : text.toString(); - mListIndex = listIndex; - mToggle = toggle; - mDate = date; + private AutofillValue(@View.AutofillType int type, @NonNull Object value) { + mType = type; + mValue = value; } /** * Gets the value to autofill a text field. * - * <p>See {@link View#AUTOFILL_TYPE_TEXT} for more info. + * <p>See {@link View#AUTOFILL_TYPE_TEXT} for more info.</p> + * + * @throws IllegalStateException if the value is not a text value + */ + @NonNull public CharSequence getTextValue() { + Preconditions.checkState(isText(), "value must be a text value, not type=" + mType); + return (CharSequence) mValue; + } + + /** + * Checks is this is a text value. + * + * <p>See {@link View#AUTOFILL_TYPE_TEXT} for more info.</p> */ - public CharSequence getTextValue() { - return mText; + public boolean isText() { + return mType == AUTOFILL_TYPE_TEXT; } /** * Gets the value to autofill a toggable field. * - * <p>See {@link View#AUTOFILL_TYPE_TOGGLE} for more info. + * <p>See {@link View#AUTOFILL_TYPE_TOGGLE} for more info.</p> + * + * @throws IllegalStateException if the value is not a toggle value */ public boolean getToggleValue() { - return mToggle; + Preconditions.checkState(isToggle(), "value must be a toggle value, not type=" + mType); + return (Boolean) mValue; + } + + /** + * Checks is this is a toggle value. + * + * <p>See {@link View#AUTOFILL_TYPE_TOGGLE} for more info.</p> + */ + public boolean isToggle() { + return mType == AUTOFILL_TYPE_TOGGLE; } /** * Gets the value to autofill a selection list field. * - * <p>See {@link View#AUTOFILL_TYPE_LIST} for more info. + * <p>See {@link View#AUTOFILL_TYPE_LIST} for more info.</p> + * + * @throws IllegalStateException if the value is not a list value */ public int getListValue() { - return mListIndex; + Preconditions.checkState(isList(), "value must be a list value, not type=" + mType); + return (Integer) mValue; + } + + /** + * Checks is this is a list value. + * + * <p>See {@link View#AUTOFILL_TYPE_LIST} for more info.</p> + */ + public boolean isList() { + return mType == AUTOFILL_TYPE_LIST; } /** * Gets the value to autofill a date field. * - * <p>See {@link View#AUTOFILL_TYPE_DATE} for more info. + * <p>See {@link View#AUTOFILL_TYPE_DATE} for more info.</p> + * + * @throws IllegalStateException if the value is not a date value */ public long getDateValue() { - return mDate; + Preconditions.checkState(isDate(), "value must be a date value, not type=" + mType); + return (Long) mValue; + } + + /** + * Checks is this is a date value. + * + * <p>See {@link View#AUTOFILL_TYPE_DATE} for more info.</p> + */ + public boolean isDate() { + return mType == AUTOFILL_TYPE_DATE; + } + + /** + * Used to define whether a field is empty so it's not sent to service on save. + * + * <p>Only applies to some types, like text. + * + * @hide + */ + public boolean isEmpty() { + return isText() && ((CharSequence) mValue).length() == 0; } ///////////////////////////////////// @@ -85,13 +150,7 @@ public final class AutofillValue implements Parcelable { @Override public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + ((mText == null) ? 0 : mText.hashCode()); - result = prime * result + mListIndex; - result = prime * result + (mToggle ? 1231 : 1237); - result = prime * result + (int) (mDate ^ (mDate >>> 32)); - return result; + return mType + mValue.hashCode(); } @Override @@ -100,32 +159,24 @@ public final class AutofillValue implements Parcelable { if (obj == null) return false; if (getClass() != obj.getClass()) return false; final AutofillValue other = (AutofillValue) obj; - if (mText == null) { - if (other.mText != null) return false; + + if (mType != other.mType) return false; + + if (isText()) { + return mValue.toString().equals(other.mValue.toString()); } else { - if (!mText.equals(other.mText)) return false; + return Objects.equals(mValue, other.mValue); } - if (mListIndex != other.mListIndex) return false; - if (mToggle != other.mToggle) return false; - if (mDate != other.mDate) return false; - return true; - } - - /** @hide */ - public String coerceToString() { - // TODO(b/33197203): How can we filter on toggles or list values? - return mText; } @Override public String toString() { if (!DEBUG) return super.toString(); - if (mText != null) { - return mText.length() + "_chars"; - } + final String sanitizedValue = isText() && !VERBOSE + ? ((CharSequence) mValue).length() + "_chars" : mValue.toString(); - return "[l=" + mListIndex + ", t=" + mToggle + ", d=" + mDate + "]"; + return "[type=" + mType + ", value=" + sanitizedValue + "]"; } ///////////////////////////////////// @@ -139,17 +190,44 @@ public final class AutofillValue implements Parcelable { @Override public void writeToParcel(Parcel parcel, int flags) { - parcel.writeString(mText); - parcel.writeInt(mListIndex); - parcel.writeInt(mToggle ? 1 : 0); - parcel.writeLong(mDate); + parcel.writeInt(mType); + + switch (mType) { + case AUTOFILL_TYPE_TEXT: + parcel.writeCharSequence((CharSequence) mValue); + break; + case AUTOFILL_TYPE_TOGGLE: + parcel.writeInt((Boolean) mValue ? 1 : 0); + break; + case AUTOFILL_TYPE_LIST: + parcel.writeInt((Integer) mValue); + break; + case AUTOFILL_TYPE_DATE: + parcel.writeLong((Long) mValue); + break; + } } - private AutofillValue(Parcel parcel) { - mText = parcel.readString(); - mListIndex = parcel.readInt(); - mToggle = parcel.readInt() == 1; - mDate = parcel.readLong(); + private AutofillValue(@NonNull Parcel parcel) { + mType = parcel.readInt(); + + switch (mType) { + case AUTOFILL_TYPE_TEXT: + mValue = parcel.readCharSequence(); + break; + case AUTOFILL_TYPE_TOGGLE: + int rawValue = parcel.readInt(); + mValue = rawValue != 0; + break; + case AUTOFILL_TYPE_LIST: + mValue = parcel.readInt(); + break; + case AUTOFILL_TYPE_DATE: + mValue = parcel.readLong(); + break; + default: + throw new IllegalArgumentException("type=" + mType + " not valid"); + } } public static final Parcelable.Creator<AutofillValue> CREATOR = @@ -175,9 +253,8 @@ public final class AutofillValue implements Parcelable { * <p>See {@link View#AUTOFILL_TYPE_TEXT} for more info. */ // TODO(b/33197203): use cache - @Nullable public static AutofillValue forText(@Nullable CharSequence value) { - return value == null ? null : new AutofillValue(value, 0, false, 0); + return value == null ? null : new AutofillValue(AUTOFILL_TYPE_TEXT, value); } /** @@ -187,7 +264,7 @@ public final class AutofillValue implements Parcelable { * <p>See {@link View#AUTOFILL_TYPE_TOGGLE} for more info. */ public static AutofillValue forToggle(boolean value) { - return new AutofillValue(null, 0, value, 0); + return new AutofillValue(AUTOFILL_TYPE_TOGGLE, value); } /** @@ -197,7 +274,7 @@ public final class AutofillValue implements Parcelable { * <p>See {@link View#AUTOFILL_TYPE_LIST} for more info. */ public static AutofillValue forList(int value) { - return new AutofillValue(null, value, false, 0); + return new AutofillValue(AUTOFILL_TYPE_LIST, value); } /** @@ -206,6 +283,6 @@ public final class AutofillValue implements Parcelable { * <p>See {@link View#AUTOFILL_TYPE_DATE} for more info. */ public static AutofillValue forDate(long value) { - return new AutofillValue(null, 0, false, value); + return new AutofillValue(AUTOFILL_TYPE_DATE, value); } } diff --git a/core/java/android/view/autofill/IAutoFillManager.aidl b/core/java/android/view/autofill/IAutoFillManager.aidl index 07d8caba9aa6..85b05e585318 100644 --- a/core/java/android/view/autofill/IAutoFillManager.aidl +++ b/core/java/android/view/autofill/IAutoFillManager.aidl @@ -32,10 +32,11 @@ interface IAutoFillManager { boolean addClient(in IAutoFillManagerClient client, int userId); oneway void startSession(in IBinder activityToken, IBinder windowToken, in IBinder appCallback, in AutofillId autoFillId, in Rect bounds, in AutofillValue value, int userId, - boolean hasCallback); + boolean hasCallback, int flags); 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); + oneway void cancelSession(in IBinder activityToken, int userId); oneway void setAuthenticationResult(in Bundle data, in IBinder activityToken, int userId); oneway void setHasCallback(in IBinder activityToken, int userId, boolean hasIt); diff --git a/core/java/android/view/textclassifier/LangId.java b/core/java/android/view/textclassifier/LangId.java index ada3c37c83e9..23c7842780a9 100644 --- a/core/java/android/view/textclassifier/LangId.java +++ b/core/java/android/view/textclassifier/LangId.java @@ -22,7 +22,7 @@ package android.view.textclassifier; final class LangId { static { - System.loadLibrary("smart-selection_jni"); + System.loadLibrary("textclassifier"); } private final long mModelPtr; diff --git a/core/java/android/view/textclassifier/SmartSelection.java b/core/java/android/view/textclassifier/SmartSelection.java index c48cd06fee0e..9397a4163e97 100644 --- a/core/java/android/view/textclassifier/SmartSelection.java +++ b/core/java/android/view/textclassifier/SmartSelection.java @@ -23,7 +23,7 @@ package android.view.textclassifier; final class SmartSelection { static { - System.loadLibrary("smart-selection_jni"); + System.loadLibrary("textclassifier"); } private final long mCtx; @@ -92,4 +92,3 @@ final class SmartSelection { } } } - diff --git a/core/java/android/view/textclassifier/TextClassificationManager.java b/core/java/android/view/textclassifier/TextClassificationManager.java index f032414b70ba..35c9a294d273 100644 --- a/core/java/android/view/textclassifier/TextClassificationManager.java +++ b/core/java/android/view/textclassifier/TextClassificationManager.java @@ -63,7 +63,7 @@ public final class TextClassificationManager { if (mDefault == null) { try { mSmartSelectionFd = ParcelFileDescriptor.open( - new File("/etc/assistant/smart-selection.model"), + new File("/etc/textclassifier/textclassifier.smartselection.en.model"), ParcelFileDescriptor.MODE_READ_ONLY); mDefault = new TextClassifierImpl(mContext, mSmartSelectionFd); } catch (FileNotFoundException e) { @@ -109,7 +109,7 @@ public final class TextClassificationManager { synchronized (mLangIdLock) { if (mLangId == null) { mLangIdFd = ParcelFileDescriptor.open( - new File("/etc/assistant/lang-id.model"), + new File("/etc/textclassifier/textclassifier.langid.model"), ParcelFileDescriptor.MODE_READ_ONLY); mLangId = new LangId(mLangIdFd.getFd()); } diff --git a/core/java/android/view/textclassifier/TextClassifierImpl.java b/core/java/android/view/textclassifier/TextClassifierImpl.java index c95a1fb966f5..06ac8699f864 100644 --- a/core/java/android/view/textclassifier/TextClassifierImpl.java +++ b/core/java/android/view/textclassifier/TextClassifierImpl.java @@ -121,8 +121,8 @@ final class TextClassifierImpl implements TextClassifier { .classifyText(text.toString(), startIndex, endIndex); if (results.length > 0) { // TODO: Added this log for debug only. Remove before release. - Log.d(LOG_TAG, - String.format("Classification type: %s", results[0].mCollection)); + Log.d(LOG_TAG, String.format( + "Classification type: %s", getHighestScoringType(results))); return createClassificationResult(results, classified); } } @@ -188,7 +188,7 @@ final class TextClassifierImpl implements TextClassifier { builder.setEntityType(classifications[i].mCollection, classifications[i].mScore); } - final String type = classifications[0].mCollection; + final String type = getHighestScoringType(classifications); final Intent intent = IntentFactory.create(mContext, type, text.toString()); final PackageManager pm; final ResolveInfo resolveInfo; @@ -226,6 +226,23 @@ final class TextClassifierImpl implements TextClassifier { return builder.build(); } + private static String getHighestScoringType(SmartSelection.ClassificationResult[] types) { + if (types.length < 1) { + return ""; + } + + String type = types[0].mCollection; + float highestScore = types[0].mScore; + final int size = types.length; + for (int i = 1; i < size; i++) { + if (types[i].mScore > highestScore) { + type = types[i].mCollection; + highestScore = types[i].mScore; + } + } + return type; + } + /** * @throws IllegalArgumentException if text is null; startIndex is negative; * endIndex is greater than text.length() or is not greater than startIndex @@ -265,7 +282,7 @@ final class TextClassifierImpl implements TextClassifier { final SmartSelection.ClassificationResult[] results = smartSelection.classifyText(text, selectionStart, selectionEnd); if (results.length > 0) { - final String type = results[0].mCollection; + final String type = getHighestScoringType(results); if (matches(type, linkMask)) { final Intent intent = IntentFactory.create( context, type, text.substring(selectionStart, selectionEnd)); diff --git a/core/java/android/widget/AbsSpinner.java b/core/java/android/widget/AbsSpinner.java index 053574f8a12a..020e80a29134 100644 --- a/core/java/android/widget/AbsSpinner.java +++ b/core/java/android/widget/AbsSpinner.java @@ -23,6 +23,7 @@ import android.graphics.Rect; import android.os.Parcel; import android.os.Parcelable; import android.util.AttributeSet; +import android.util.Log; import android.util.SparseArray; import android.view.View; import android.view.ViewGroup; @@ -38,6 +39,8 @@ import com.android.internal.R; * @attr ref android.R.styleable#AbsSpinner_entries */ public abstract class AbsSpinner extends AdapterView<SpinnerAdapter> { + private static final String LOG_TAG = AbsSpinner.class.getSimpleName(); + SpinnerAdapter mAdapter; int mHeightMeasureSpec; @@ -514,8 +517,11 @@ public abstract class AbsSpinner extends AdapterView<SpinnerAdapter> { 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/AdapterView.java b/core/java/android/widget/AdapterView.java index e5505a671dbe..5725b496f150 100644 --- a/core/java/android/widget/AdapterView.java +++ b/core/java/android/widget/AdapterView.java @@ -918,7 +918,7 @@ public abstract class AdapterView<T extends Adapter> extends ViewGroup { // Always notify AutoFillManager - it will return right away if autofill is disabled. final AutofillManager afm = mContext.getSystemService(AutofillManager.class); if (afm != null) { - afm.valueChanged(this); + afm.notifyValueChanged(this); } } diff --git a/core/java/android/widget/CompoundButton.java b/core/java/android/widget/CompoundButton.java index 81aec9ceee35..899a824489e3 100644 --- a/core/java/android/widget/CompoundButton.java +++ b/core/java/android/widget/CompoundButton.java @@ -28,6 +28,7 @@ import android.graphics.drawable.Drawable; import android.os.Parcel; import android.os.Parcelable; import android.util.AttributeSet; +import android.util.Log; import android.view.Gravity; import android.view.SoundEffectConstants; import android.view.ViewDebug; @@ -55,6 +56,7 @@ import com.android.internal.R; * </p> */ public abstract class CompoundButton extends Button implements Checkable { + private static final String LOG_TAG = CompoundButton.class.getSimpleName(); private boolean mChecked; private boolean mBroadcasting; @@ -173,7 +175,7 @@ public abstract class CompoundButton extends Button implements Checkable { } final AutofillManager afm = mContext.getSystemService(AutofillManager.class); if (afm != null) { - afm.valueChanged(this); + afm.notifyValueChanged(this); } mBroadcasting = false; @@ -578,14 +580,18 @@ public abstract class CompoundButton extends Button implements Checkable { public void onProvideAutofillStructure(ViewStructure structure, int flags) { super.onProvideAutofillStructure(structure, flags); - structure.setSanitized(mCheckedFromResource); + structure.setDataIsSensitive(!mCheckedFromResource); } @Override public void autofill(AutofillValue value) { if (!isEnabled()) return; - 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 fa8316c7699f..f63573f4f3fe 100644 --- a/core/java/android/widget/DatePicker.java +++ b/core/java/android/widget/DatePicker.java @@ -29,6 +29,7 @@ import android.os.Parcel; import android.os.Parcelable; import android.text.format.DateUtils; import android.util.AttributeSet; +import android.util.Log; import android.util.SparseArray; import android.view.View; import android.view.ViewStructure; @@ -83,6 +84,8 @@ import java.util.Locale; */ @Widget public class DatePicker extends FrameLayout { + private static final String LOG_TAG = DatePicker.class.getSimpleName(); + /** * Presentation mode for the Holo-style date picker that uses a set of * {@link android.widget.NumberPicker}s. @@ -182,7 +185,7 @@ public class DatePicker extends FrameLayout { mDelegate.setAutoFillChangeListener((v, y, m, d) -> { final AutofillManager afm = context.getSystemService(AutofillManager.class); if (afm != null) { - afm.valueChanged(this); + afm.notifyValueChanged(this); } }); } @@ -775,7 +778,11 @@ public class DatePicker extends FrameLayout { 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 ade03e1b7fb4..faa23106e920 100644 --- a/core/java/android/widget/Editor.java +++ b/core/java/android/widget/Editor.java @@ -157,6 +157,7 @@ public class Editor { private static final int MENU_ITEM_ORDER_SELECT_ALL = 9; private static final int MENU_ITEM_ORDER_REPLACE = 10; private static final int MENU_ITEM_ORDER_PROCESS_TEXT_INTENT_ACTIONS_START = 11; + private static final int MENU_ITEM_ORDER_AUTOFILL = 12; // Each Editor manages its own undo stack. private final UndoManager mUndoManager = new UndoManager(); @@ -2644,6 +2645,10 @@ public class Editor { .setAlphabeticShortcut('a') .setEnabled(mTextView.canSelectAllText()) .setOnMenuItemClickListener(mOnContextMenuItemClickListener); + menu.add(Menu.NONE, TextView.ID_AUTOFILL, MENU_ITEM_ORDER_AUTOFILL, + com.android.internal.R.string.autofill) + .setEnabled(mTextView.canRequestAutofill()) + .setOnMenuItemClickListener(mOnContextMenuItemClickListener); mPreserveSelection = true; } @@ -3828,6 +3833,12 @@ public class Editor { .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM); } + if (mTextView.canRequestAutofill()) { + menu.add(Menu.NONE, TextView.ID_AUTOFILL, MENU_ITEM_ORDER_AUTOFILL, + com.android.internal.R.string.autofill) + .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM); + } + updateSelectAllItem(menu); updateReplaceItem(menu); } diff --git a/core/java/android/widget/RadioGroup.java b/core/java/android/widget/RadioGroup.java index bd62d6ceac3f..5e8279a3e7c9 100644 --- a/core/java/android/widget/RadioGroup.java +++ b/core/java/android/widget/RadioGroup.java @@ -55,6 +55,7 @@ import com.android.internal.R; * */ public class RadioGroup extends LinearLayout { + private static final String LOG_TAG = RadioGroup.class.getSimpleName(); // holds the checked id; the selection is empty by default private int mCheckedId = -1; @@ -188,7 +189,7 @@ public class RadioGroup extends LinearLayout { } final AutofillManager afm = mContext.getSystemService(AutofillManager.class); if (afm != null) { - afm.valueChanged(this); + afm.notifyValueChanged(this); } } @@ -421,14 +422,21 @@ public class RadioGroup extends LinearLayout { @Override public void onProvideAutofillStructure(ViewStructure structure, int flags) { super.onProvideAutofillStructure(structure, flags); - structure.setSanitized(mCheckedId == mInitialCheckedId); + structure.setDataIsSensitive(mCheckedId != mInitialCheckedId); } @Override public void autofill(AutofillValue value) { if (!isEnabled()) return; - 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 a6a9db44c420..59881b5e6b6b 100644 --- a/core/java/android/widget/TextClock.java +++ b/core/java/android/widget/TextClock.java @@ -132,7 +132,7 @@ public class TextClock extends TextView { private CharSequence mDescFormat; - private boolean mRegistered; + private boolean mAttached; private Calendar mTime; private String mTimeZone; @@ -252,7 +252,7 @@ public class TextClock extends TextView { } createTime(mTimeZone); - // Wait until registering for events to handle the ticker + // Wait until onAttachedToWindow() to handle the ticker chooseFormat(false); } @@ -503,9 +503,12 @@ public class TextClock extends TextView { boolean hadSeconds = mHasSeconds; mHasSeconds = DateFormat.hasSeconds(mFormat); - if (handleTicker && mRegistered && hadSeconds != mHasSeconds) { - if (hadSeconds) getHandler().removeCallbacks(mTicker); - else mTicker.run(); + if (handleTicker && mAttached && hadSeconds != mHasSeconds) { + if (hadSeconds) { + getHandler().removeCallbacks(mTicker); + } else if (getVisibility() == VISIBLE) { + mTicker.run(); + } } } @@ -517,27 +520,50 @@ public class TextClock extends TextView { } @Override - public void onVisibilityAggregated(boolean isVisible) { - if (!mRegistered && isVisible) { - mRegistered = true; + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + + if (!mAttached) { + mAttached = true; registerReceiver(); registerObserver(); createTime(mTimeZone); - if (mHasSeconds) { - mTicker.run(); - } else { - onTimeChanged(); + if (getVisibility() == VISIBLE) { + if (mHasSeconds) { + mTicker.run(); + } else { + onTimeChanged(); + } } - } else if (mRegistered && !isVisible) { + } + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + + if (mAttached) { unregisterReceiver(); unregisterObserver(); getHandler().removeCallbacks(mTicker); - mRegistered = false; + mAttached = false; + } + } + + @Override + public void onVisibilityAggregated(boolean isVisible) { + if (mAttached) { + if (isVisible && mHasSeconds) { + mTicker.run(); + } else { + getHandler().removeCallbacks(mTicker); + } + onTimeChanged(); } } @@ -560,7 +586,7 @@ public class TextClock extends TextView { } private void registerObserver() { - if (mRegistered) { + if (mAttached) { if (mFormatChangeObserver == null) { mFormatChangeObserver = new FormatChangeObserver(getHandler()); } @@ -587,9 +613,11 @@ public class TextClock extends TextView { } private void onTimeChanged() { - mTime.setTimeInMillis(System.currentTimeMillis()); - setText(DateFormat.format(mFormat, mTime)); - setContentDescription(DateFormat.format(mDescFormat, mTime)); + if (getVisibility() == VISIBLE) { + mTime.setTimeInMillis(System.currentTimeMillis()); + setText(DateFormat.format(mFormat, mTime)); + setContentDescription(DateFormat.format(mDescFormat, mTime)); + } } /** @hide */ diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index ee70acca62dd..c5c317d671ef 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -9136,7 +9136,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener if (DEBUG_AUTOFILL) { Log.v(LOG_TAG, "sendAfterTextChanged(): notify AFM for text=" + mText); } - afm.valueChanged(TextView.this); + afm.notifyValueChanged(TextView.this); } } @@ -9900,7 +9900,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener final boolean isPassword = hasPasswordTransformationMethod() || isPasswordInputType(getInputType()); if (forAutofill) { - structure.setSanitized(mTextFromResource); + structure.setDataIsSensitive(!mTextFromResource); } if (!isPassword || forAutofill) { @@ -10012,12 +10012,29 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener // 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 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener static final int ID_PASTE_AS_PLAIN_TEXT = android.R.id.pasteAsPlainText; static final int ID_REPLACE = android.R.id.replaceText; static final int ID_ASSIST = android.R.id.textAssist; + static final int ID_AUTOFILL = android.R.id.autofill; /** * Called when a context menu option for the text view is selected. Currently @@ -10543,6 +10561,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener case ID_SHARE: shareSelectedText(); return true; + + case ID_AUTOFILL: + requestAutofill(); + stopTextActionMode(); + return true; } return false; } diff --git a/core/java/android/widget/TimePicker.java b/core/java/android/widget/TimePicker.java index 1435983ea2e5..cfa78b53dd91 100644 --- a/core/java/android/widget/TimePicker.java +++ b/core/java/android/widget/TimePicker.java @@ -27,6 +27,7 @@ import android.icu.util.Calendar; import android.os.Parcel; import android.os.Parcelable; import android.util.AttributeSet; +import android.util.Log; import android.util.MathUtils; import android.view.View; import android.view.ViewStructure; @@ -53,6 +54,8 @@ import java.util.Locale; */ @Widget public class TimePicker extends FrameLayout { + private static final String LOG_TAG = TimePicker.class.getSimpleName(); + /** * Presentation mode for the Holo-style time picker that uses a set of * {@link android.widget.NumberPicker}s. @@ -144,7 +147,7 @@ public class TimePicker extends FrameLayout { mDelegate.setAutoFillChangeListener((v, h, m) -> { final AutofillManager afm = context.getSystemService(AutofillManager.class); if (afm != null) { - afm.valueChanged(this); + afm.notifyValueChanged(this); } }); } @@ -530,7 +533,11 @@ public class TimePicker extends FrameLayout { 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 3ac5a72e087b..83cc9f09e8ed 100644 --- a/core/java/com/android/internal/content/FileSystemProvider.java +++ b/core/java/com/android/internal/content/FileSystemProvider.java @@ -18,6 +18,7 @@ package com.android.internal.content; import android.annotation.CallSuper; import android.content.ContentResolver; +import android.content.ContentValues; import android.content.Intent; import android.content.res.AssetFileDescriptor; import android.database.Cursor; @@ -157,10 +158,9 @@ public abstract class FileSystemProvider extends DocumentsProvider { if (!before.renameTo(after)) { throw new IllegalStateException("Failed to rename to " + after); } - removeFromMediaStore(visibleFileBefore); final String afterDocId = getDocIdForFile(after); - scanFile(getFileForDocId(afterDocId, true)); + moveInMediaStore(visibleFileBefore, getFileForDocId(afterDocId, true)); if (!TextUtils.equals(docId, afterDocId)) { return afterDocId; @@ -170,22 +170,6 @@ public abstract class FileSystemProvider extends DocumentsProvider { } @Override - public void deleteDocument(String docId) throws FileNotFoundException { - final File file = getFileForDocId(docId); - final File visibleFile = getFileForDocId(docId, true); - - final boolean isDirectory = file.isDirectory(); - if (isDirectory) { - FileUtils.deleteContents(file); - } - if (!file.delete()) { - throw new IllegalStateException("Failed to delete " + file); - } - - removeFromMediaStore(visibleFile); - } - - @Override public String moveDocument(String sourceDocumentId, String sourceParentDocumentId, String targetParentDocumentId) throws FileNotFoundException { @@ -200,22 +184,56 @@ public abstract class FileSystemProvider extends DocumentsProvider { throw new IllegalStateException("Failed to move to " + after); } - // Notify media store to update its content - removeFromMediaStore(visibleFileBefore); final String docId = getDocIdForFile(after); - scanFile(getFileForDocId(docId, true)); + moveInMediaStore(visibleFileBefore, getFileForDocId(docId, true)); return docId; } - private void removeFromMediaStore(File visibleFile) throws FileNotFoundException { + private void moveInMediaStore(File oldVisibleFile, File newVisibleFile) { + if (newVisibleFile != null) { + final ContentResolver resolver = getContext().getContentResolver(); + final Uri externalUri = MediaStore.Files.getContentUri("external"); + + ContentValues values = new ContentValues(); + values.put(MediaStore.Files.FileColumns.DATA, newVisibleFile.getAbsolutePath()); + + // Logic borrowed from MtpDatabase. + // note - we are relying on a special case in MediaProvider.update() to update + // the paths for all children in the case where this is a directory. + final String path = oldVisibleFile.getAbsolutePath(); + resolver.update(externalUri, + values, + "_data LIKE ? AND lower(_data)=lower(?)", + new String[] { path, path }); + } + } + + @Override + public void deleteDocument(String docId) throws FileNotFoundException { + final File file = getFileForDocId(docId); + final File visibleFile = getFileForDocId(docId, true); + + final boolean isDirectory = file.isDirectory(); + if (isDirectory) { + FileUtils.deleteContents(file); + } + if (!file.delete()) { + throw new IllegalStateException("Failed to delete " + file); + } + + removeFromMediaStore(visibleFile, isDirectory); + } + + private void removeFromMediaStore(File visibleFile, boolean isFolder) + throws FileNotFoundException { if (visibleFile != null) { final ContentResolver resolver = getContext().getContentResolver(); final Uri externalUri = MediaStore.Files.getContentUri("external"); // Remove media store entries for any files inside this directory, using // path prefix match. Logic borrowed from MtpDatabase. - if (visibleFile.isDirectory()) { + if (isFolder) { final String path = visibleFile.getAbsolutePath() + "/"; resolver.delete(externalUri, "_data LIKE ?1 AND lower(substr(_data,1,?2))=lower(?3)", diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java index 2aeddb306c4d..6aa77665db51 100644 --- a/core/java/com/android/internal/os/BatteryStatsImpl.java +++ b/core/java/com/android/internal/os/BatteryStatsImpl.java @@ -6027,7 +6027,8 @@ public class BatteryStatsImpl extends BatteryStats { * Clear all stats for this uid. Returns true if the uid is completely * inactive so can be dropped. */ - boolean reset() { + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + public boolean reset() { boolean active = false; if (mWifiRunningTimer != null) { @@ -6968,7 +6969,10 @@ public class BatteryStatsImpl extends BatteryStats { boolean reset() { if (mBgCounter != null) { - mBgCounter.reset(true); + mBgCounter.reset(true /*detachIfReset*/); + // If we detach, we must null the mBgCounter reference so that it + // can be recreated and attached. + mBgCounter = null; } if (mTimer.reset(true)) { mTimer = null; diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java index a7e900a189c7..76d8af1a509d 100644 --- a/core/java/com/android/internal/os/ZygoteInit.java +++ b/core/java/com/android/internal/os/ZygoteInit.java @@ -132,9 +132,6 @@ public class ZygoteInit { Trace.traceBegin(Trace.TRACE_TAG_DALVIK, "PreloadOpenGL"); preloadOpenGL(); Trace.traceEnd(Trace.TRACE_TAG_DALVIK); - Trace.traceBegin(Trace.TRACE_TAG_DALVIK, "PreloadOpenGL"); - preloadOpenGL(); - Trace.traceEnd(Trace.TRACE_TAG_DALVIK); preloadSharedLibraries(); preloadTextResources(); // Ask the WebViewFactory to do any initialization that must run in the zygote process, @@ -585,7 +582,6 @@ public class ZygoteInit { OsConstants.CAP_SYS_MODULE, OsConstants.CAP_SYS_NICE, OsConstants.CAP_SYS_PTRACE, - OsConstants.CAP_SYS_RESOURCE, OsConstants.CAP_SYS_TIME, OsConstants.CAP_SYS_TTY_CONFIG, OsConstants.CAP_WAKE_ALARM diff --git a/core/java/com/android/internal/policy/PhoneWindow.java b/core/java/com/android/internal/policy/PhoneWindow.java index 804bd29da796..6c9280a1ea25 100644 --- a/core/java/com/android/internal/policy/PhoneWindow.java +++ b/core/java/com/android/internal/policy/PhoneWindow.java @@ -3585,7 +3585,8 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { synchronized (mWindows) { if (!mIsWatching) { try { - WindowManagerHolder.sWindowManager.watchRotation(this); + WindowManagerHolder.sWindowManager.watchRotation(this, + phoneWindow.getContext().getDisplay().getDisplayId()); mHandler = new Handler(); mIsWatching = true; } catch (RemoteException ex) { diff --git a/core/java/com/android/internal/policy/PipSnapAlgorithm.java b/core/java/com/android/internal/policy/PipSnapAlgorithm.java index ae31873492e9..95d714f1c3c7 100644 --- a/core/java/com/android/internal/policy/PipSnapAlgorithm.java +++ b/core/java/com/android/internal/policy/PipSnapAlgorithm.java @@ -321,7 +321,7 @@ public class PipSnapAlgorithm { stackBounds.top)); boundsOut.set(stackBounds); if (mIsMinimized) { - boundsOut.offsetTo(boundedLeft, boundsOut.top); + boundsOut.offsetTo(boundedLeft, boundedTop); return; } diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/activity/LaunchTvTaskStartedEvent.java b/core/java/com/android/internal/util/IntPair.java index 75d3efacc4a3..79925077dd8a 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/events/activity/LaunchTvTaskStartedEvent.java +++ b/core/java/com/android/internal/util/IntPair.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. @@ -14,21 +14,25 @@ * limitations under the License. */ -package com.android.systemui.recents.events.activity; - -import com.android.systemui.recents.events.EventBus; -import com.android.systemui.recents.tv.views.TaskCardView; +package com.android.internal.util; /** - * This event is sent following {@link LaunchTvTaskEvent} after the call to the system is made to - * start the task, only used on TV. + * Utilities for treating a {@code long} as a pair of {@code int}s + * + * @hide */ -public class LaunchTvTaskStartedEvent extends EventBus.AnimatedEvent { +public class IntPair { + private IntPair() {} - public final TaskCardView taskView; + public static long of(int first, int second) { + return (((long)first) << 32) | ((long)second & 0xffffffffL); + } - public LaunchTvTaskStartedEvent(TaskCardView taskView) { - this.taskView = taskView; + public static int first(long intPair) { + return (int)(intPair >> 32); } + public static int second(long intPair) { + return (int)intPair; + } } diff --git a/core/java/com/android/internal/widget/FloatingToolbar.java b/core/java/com/android/internal/widget/FloatingToolbar.java index 79b0cd1aced8..818cc2c2421d 100644 --- a/core/java/com/android/internal/widget/FloatingToolbar.java +++ b/core/java/com/android/internal/widget/FloatingToolbar.java @@ -1163,21 +1163,21 @@ public final class FloatingToolbar { isLastItem && menuItemButtonWidth <= availableWidth - extraPadding; if (canFitWithOverflow || canFitNoOverflow) { if (isNewGroup) { - final View border = createBorder(mContext); - final int borderWidth = border.getLayoutParams().width; + final View divider = createDivider(mContext); + final int dividerWidth = divider.getLayoutParams().width; // Add extra padding to the end of the previous button. // Half of the extra padding (less borderWidth) goes to the previous button. View previousButton = mMainPanel.getChildAt(mMainPanel.getChildCount() - 1); final int prevPaddingEnd = previousButton.getPaddingEnd() - + extraPadding / 2 - borderWidth; + + extraPadding / 2 - dividerWidth; previousButton.setPaddingRelative( previousButton.getPaddingStart(), previousButton.getPaddingTop(), prevPaddingEnd, previousButton.getPaddingBottom()); final ViewGroup.LayoutParams prevParams = previousButton.getLayoutParams(); - prevParams.width += extraPadding / 2 - borderWidth; + prevParams.width += extraPadding / 2 - dividerWidth; previousButton.setLayoutParams(prevParams); // Add extra padding to the start of this button. @@ -1190,8 +1190,8 @@ public final class FloatingToolbar { menuItemButton.getPaddingEnd(), menuItemButton.getPaddingBottom()); - // Include a border. - mMainPanel.addView(border); + // Include a divider. + mMainPanel.addView(divider); } setButtonTagAndClickListener(menuItemButton, menuItem); @@ -1670,21 +1670,28 @@ public final class FloatingToolbar { return popupWindow; } - private static View createBorder(Context context) { + private static View createDivider(Context context) { // TODO: Inflate this instead. - View border = new View(context); + View divider = new View(context); + int _1dp = (int) TypedValue.applyDimension( TypedValue.COMPLEX_UNIT_DIP, 1, context.getResources().getDisplayMetrics()); LinearLayout.LayoutParams params = new LinearLayout.LayoutParams( _1dp, ViewGroup.LayoutParams.MATCH_PARENT); params.setMarginsRelative(0, _1dp * 10, 0, _1dp * 10); - border.setLayoutParams(params); - border.setBackgroundColor(Color.parseColor("#9E9E9E")); - border.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO); - border.setEnabled(false); - border.setFocusable(false); - border.setContentDescription(null); - return border; + divider.setLayoutParams(params); + + TypedArray a = context.obtainStyledAttributes( + new TypedValue().data, new int[] { R.attr.floatingToolbarDividerColor }); + divider.setBackgroundColor(a.getColor(0, 0)); + a.recycle(); + + divider.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO); + divider.setEnabled(false); + divider.setFocusable(false); + divider.setContentDescription(null); + + return divider; } /** diff --git a/core/jni/Android.mk b/core/jni/Android.mk index 95b2593201e6..af5fca2c0e42 100644 --- a/core/jni/Android.mk +++ b/core/jni/Android.mk @@ -286,6 +286,7 @@ LOCAL_SHARED_LIBRARIES := \ libhwbinder \ libvintf \ libnativewindow \ + libtextclassifier \ LOCAL_SHARED_LIBRARIES += \ libhwui \ diff --git a/core/jni/android/graphics/Bitmap.cpp b/core/jni/android/graphics/Bitmap.cpp index f85219453212..a8d683028c13 100755 --- a/core/jni/android/graphics/Bitmap.cpp +++ b/core/jni/android/graphics/Bitmap.cpp @@ -453,7 +453,8 @@ bool GraphicsJNI::SetPixels(JNIEnv* env, jintArray srcColors, int srcOffset, int dst = dstBitmap.getAddr(x, y); SkColorSpace* colorSpace = dstBitmap.colorSpace(); - if (GraphicsJNI::isColorSpaceSRGB(colorSpace)) { + if (dstBitmap.colorType() == kRGBA_F16_SkColorType || + GraphicsJNI::isColorSpaceSRGB(colorSpace)) { // now copy/convert each scanline for (int y = 0; y < height; y++) { proc(dst, src, width, x, y); @@ -1267,7 +1268,8 @@ static jint Bitmap_getPixel(JNIEnv* env, jobject, jlong bitmapHandle, proc(dst, src, 1, bitmap.getColorTable()); SkColorSpace* colorSpace = bitmap.colorSpace(); - if (!GraphicsJNI::isColorSpaceSRGB(colorSpace)) { + if (bitmap.colorType() != kRGBA_F16_SkColorType && + !GraphicsJNI::isColorSpaceSRGB(colorSpace)) { auto sRGB = SkColorSpace::MakeSRGB(); auto xform = SkColorSpaceXform::New(colorSpace, sRGB.get()); xform->apply(SkColorSpaceXform::kBGRA_8888_ColorFormat, &dst[0], @@ -1299,7 +1301,8 @@ static void Bitmap_getPixels(JNIEnv* env, jobject, jlong bitmapHandle, SkColor* d = (SkColor*)dst + offset; SkColorSpace* colorSpace = bitmap.colorSpace(); - if (GraphicsJNI::isColorSpaceSRGB(colorSpace)) { + if (bitmap.colorType() == kRGBA_F16_SkColorType || + GraphicsJNI::isColorSpaceSRGB(colorSpace)) { while (--height >= 0) { proc(d, src, width, ctable); d += stride; @@ -1342,7 +1345,8 @@ static void Bitmap_setPixel(JNIEnv* env, jobject, jlong bitmapHandle, } SkColorSpace* colorSpace = bitmap.colorSpace(); - if (!GraphicsJNI::isColorSpaceSRGB(colorSpace)) { + if (bitmap.colorType() != kRGBA_F16_SkColorType && + !GraphicsJNI::isColorSpaceSRGB(colorSpace)) { auto sRGB = SkColorSpace::MakeSRGB(); auto xform = SkColorSpaceXform::New(sRGB.get(), colorSpace); xform->apply(SkColorSpaceXform::kBGRA_8888_ColorFormat, &color, diff --git a/core/jni/android/graphics/BitmapFactory.cpp b/core/jni/android/graphics/BitmapFactory.cpp index c1bb69da3179..e64a57447e6b 100644 --- a/core/jni/android/graphics/BitmapFactory.cpp +++ b/core/jni/android/graphics/BitmapFactory.cpp @@ -417,11 +417,7 @@ static jobject doDecode(JNIEnv* env, SkStreamRewindable* stream, jobject padding // For wide gamut images, we will leave the color space on the SkBitmap. Otherwise, // use the default. SkImageInfo bitmapInfo = decodeInfo; - sk_sp<SkColorSpace> srgb = - SkColorSpace::MakeRGB(SkColorSpace::kSRGB_RenderTargetGamma, - SkColorSpace::kSRGB_Gamut, - SkColorSpace::kNonLinearBlending_ColorSpaceFlag); - if (decodeInfo.colorSpace() == srgb.get()) { + if (decodeInfo.colorSpace() && decodeInfo.colorSpace()->isSRGB()) { bitmapInfo = bitmapInfo.makeColorSpace(GraphicsJNI::colorSpaceForType(decodeColorType)); } diff --git a/core/jni/android/graphics/FontFamily.cpp b/core/jni/android/graphics/FontFamily.cpp index 49024b6a0b5f..fb7c5c46843a 100644 --- a/core/jni/android/graphics/FontFamily.cpp +++ b/core/jni/android/graphics/FontFamily.cpp @@ -44,6 +44,7 @@ struct NativeFamilyBuilder { uint32_t langId; int variant; std::vector<minikin::Font> fonts; + std::vector<minikin::FontVariation> axes; }; static jlong FontFamily_initBuilder(JNIEnv* env, jobject clazz, jstring lang, jint variant) { @@ -155,32 +156,16 @@ static jboolean FontFamily_addFont(JNIEnv* env, jobject clazz, jlong builderPtr, } static jboolean FontFamily_addFontWeightStyle(JNIEnv* env, jobject clazz, jlong builderPtr, - jobject font, jint ttcIndex, jobject listOfAxis, jint weight, jboolean isItalic) { + jobject font, jint ttcIndex, jint weight, jboolean isItalic) { NPE_CHECK_RETURN_ZERO(env, font); + NativeFamilyBuilder* builder = reinterpret_cast<NativeFamilyBuilder*>(builderPtr); + // Declare axis native type. - std::unique_ptr<SkFontMgr::FontParameters::Axis[]> skiaAxes; - int skiaAxesLength = 0; - if (listOfAxis) { - ListHelper list(env, listOfAxis); - jint listSize = list.size(); - - skiaAxes.reset(new SkFontMgr::FontParameters::Axis[listSize]); - skiaAxesLength = listSize; - for (jint i = 0; i < listSize; ++i) { - jobject axisObject = list.get(i); - if (!axisObject) { - skiaAxes[i].fTag = 0; - skiaAxes[i].fStyleValue = 0; - continue; - } - AxisHelper axis(env, axisObject); - - jint tag = axis.getTag(); - jfloat stylevalue = axis.getStyleValue(); - skiaAxes[i].fTag = tag; - skiaAxes[i].fStyleValue = SkFloatToScalar(stylevalue); - } + std::vector<SkFontMgr::FontParameters::Axis> skiaAxes; + skiaAxes.reserve(builder->axes.size()); + for (const minikin::FontVariation& minikinAxis : builder->axes) { + skiaAxes.push_back({minikinAxis.axisTag, minikinAxis.value}); } const void* fontPtr = env->GetDirectBufferAddress(font); @@ -200,7 +185,7 @@ static jboolean FontFamily_addFontWeightStyle(JNIEnv* env, jobject clazz, jlong SkFontMgr::FontParameters params; params.setCollectionIndex(ttcIndex); - params.setAxes(skiaAxes.get(), skiaAxesLength); + params.setAxes(skiaAxes.data(), skiaAxes.size()); sk_sp<SkFontMgr> fm(SkFontMgr::RefDefault()); sk_sp<SkTypeface> face(fm->createFromStream(fontData.release(), params)); @@ -211,7 +196,6 @@ static jboolean FontFamily_addFontWeightStyle(JNIEnv* env, jobject clazz, jlong std::shared_ptr<minikin::MinikinFont> minikinFont = std::make_shared<MinikinFontSkia>(std::move(face), fontPtr, fontSize, ttcIndex, std::vector<minikin::FontVariation>()); - NativeFamilyBuilder* builder = reinterpret_cast<NativeFamilyBuilder*>(builderPtr); builder->fonts.push_back(minikin::Font(std::move(minikinFont), minikin::FontStyle(weight / 100, isItalic))); return true; @@ -270,6 +254,11 @@ static jboolean FontFamily_addFontFromAssetManager(JNIEnv* env, jobject, jlong b return true; } +static void FontFamily_addAxisValue(jlong builderPtr, jint tag, jfloat value) { + NativeFamilyBuilder* builder = reinterpret_cast<NativeFamilyBuilder*>(builderPtr); + builder->axes.push_back({static_cast<minikin::AxisTag>(tag), value}); +} + /////////////////////////////////////////////////////////////////////////////// static const JNINativeMethod gFontFamilyMethods[] = { @@ -278,10 +267,11 @@ static const JNINativeMethod gFontFamilyMethods[] = { { "nAbort", "(J)V", (void*)FontFamily_abort }, { "nUnrefFamily", "(J)V", (void*)FontFamily_unref }, { "nAddFont", "(JLjava/nio/ByteBuffer;I)Z", (void*)FontFamily_addFont }, - { "nAddFontWeightStyle", "(JLjava/nio/ByteBuffer;ILjava/util/List;IZ)Z", + { "nAddFontWeightStyle", "(JLjava/nio/ByteBuffer;IIZ)Z", (void*)FontFamily_addFontWeightStyle }, { "nAddFontFromAssetManager", "(JLandroid/content/res/AssetManager;Ljava/lang/String;IZIZ)Z", (void*)FontFamily_addFontFromAssetManager }, + { "nAddAxisValue", "(JIF)V", (void*)FontFamily_addAxisValue }, }; int register_android_graphics_FontFamily(JNIEnv* env) diff --git a/core/jni/android/graphics/Graphics.cpp b/core/jni/android/graphics/Graphics.cpp index 7c56c7bf4158..e66587a9d8fe 100644 --- a/core/jni/android/graphics/Graphics.cpp +++ b/core/jni/android/graphics/Graphics.cpp @@ -461,12 +461,7 @@ sk_sp<SkColorSpace> GraphicsJNI::colorSpaceForType(SkColorType type) { } bool GraphicsJNI::isColorSpaceSRGB(SkColorSpace* colorSpace) { - return colorSpace == nullptr - || colorSpace == SkColorSpace::MakeSRGB().get() - || colorSpace == SkColorSpace::MakeRGB( - SkColorSpace::kSRGB_RenderTargetGamma, - SkColorSpace::kSRGB_Gamut, - SkColorSpace::kNonLinearBlending_ColorSpaceFlag).get(); + return colorSpace == nullptr || colorSpace->isSRGB(); } /////////////////////////////////////////////////////////////////////////////// diff --git a/core/jni/android/graphics/Paint.cpp b/core/jni/android/graphics/Paint.cpp index 18462376cd20..fa25a8f0c596 100644 --- a/core/jni/android/graphics/Paint.cpp +++ b/core/jni/android/graphics/Paint.cpp @@ -239,30 +239,38 @@ namespace PaintGlue { return result; } - static jint doTextRunCursor(JNIEnv *env, Paint* paint, const jchar *text, jint start, - jint count, jint flags, jint offset, jint opt) { + static jint doTextRunCursor(JNIEnv *env, Paint* paint, Typeface* typeface, const jchar *text, + jint start, jint count, jint dir, jint offset, jint opt) { minikin::GraphemeBreak::MoveOpt moveOpt = minikin::GraphemeBreak::MoveOpt(opt); - size_t result = minikin::GraphemeBreak::getTextRunCursor(text, start, count, offset, - moveOpt); + int bidiFlags = dir == 1 ? minikin::kBidi_Force_RTL : minikin::kBidi_Force_LTR; + std::unique_ptr<float[]> advancesArray(new float[count]); + MinikinUtils::measureText(paint, bidiFlags, typeface, text, start, count, start + count, + advancesArray.get()); + size_t result = minikin::GraphemeBreak::getTextRunCursor(advancesArray.get(), text, + start, count, offset, moveOpt); return static_cast<jint>(result); } - static jint getTextRunCursor___C(JNIEnv* env, jobject clazz, jlong paintHandle, jcharArray text, - jint contextStart, jint contextCount, jint dir, jint offset, jint cursorOpt) { + static jint getTextRunCursor___C(JNIEnv* env, jobject clazz, jlong paintHandle, + jlong typefaceHandle, jcharArray text, jint contextStart, jint contextCount, jint dir, + jint offset, jint cursorOpt) { Paint* paint = reinterpret_cast<Paint*>(paintHandle); + Typeface* typeface = reinterpret_cast<Typeface*>(typefaceHandle); jchar* textArray = env->GetCharArrayElements(text, nullptr); - jint result = doTextRunCursor(env, paint, textArray, contextStart, contextCount, dir, - offset, cursorOpt); + jint result = doTextRunCursor(env, paint, typeface, textArray, + contextStart, contextCount, dir, offset, cursorOpt); env->ReleaseCharArrayElements(text, textArray, JNI_ABORT); return result; } - static jint getTextRunCursor__String(JNIEnv* env, jobject clazz, jlong paintHandle, jstring text, - jint contextStart, jint contextEnd, jint dir, jint offset, jint cursorOpt) { + static jint getTextRunCursor__String(JNIEnv* env, jobject clazz, jlong paintHandle, + jlong typefaceHandle, jstring text, jint contextStart, jint contextEnd, jint dir, + jint offset, jint cursorOpt) { Paint* paint = reinterpret_cast<Paint*>(paintHandle); + Typeface* typeface = reinterpret_cast<Typeface*>(typefaceHandle); const jchar* textArray = env->GetStringChars(text, nullptr); - jint result = doTextRunCursor(env, paint, textArray, contextStart, - contextEnd - contextStart, dir, offset, cursorOpt); + jint result = doTextRunCursor(env, paint, typeface, textArray, + contextStart, contextEnd - contextStart, dir, offset, cursorOpt); env->ReleaseStringChars(text, textArray); return result; } @@ -983,8 +991,8 @@ static const JNINativeMethod methods[] = { {"nGetTextAdvances","(JJLjava/lang/String;IIIII[FI)F", (void*) PaintGlue::getTextAdvances__StringIIIII_FI}, - {"nGetTextRunCursor", "(J[CIIIII)I", (void*) PaintGlue::getTextRunCursor___C}, - {"nGetTextRunCursor", "(JLjava/lang/String;IIIII)I", + {"nGetTextRunCursor", "(JJ[CIIIII)I", (void*) PaintGlue::getTextRunCursor___C}, + {"nGetTextRunCursor", "(JJLjava/lang/String;IIIII)I", (void*) PaintGlue::getTextRunCursor__String}, {"nGetTextPath", "(JJI[CIIFFJ)V", (void*) PaintGlue::getTextPath___C}, {"nGetTextPath", "(JJILjava/lang/String;IIFFJ)V", (void*) PaintGlue::getTextPath__String}, diff --git a/core/jni/android_graphics_Canvas.cpp b/core/jni/android_graphics_Canvas.cpp index c261e414203c..253daaa51d44 100644 --- a/core/jni/android_graphics_Canvas.cpp +++ b/core/jni/android_graphics_Canvas.cpp @@ -178,10 +178,10 @@ static jboolean quickRejectPath(jlong canvasHandle, jlong pathHandle) { // from one to the other (though SkClipOp is destined to become a strict subset) static_assert(SkRegion::kDifference_Op == static_cast<SkRegion::Op>(SkClipOp::kDifference), ""); static_assert(SkRegion::kIntersect_Op == static_cast<SkRegion::Op>(SkClipOp::kIntersect), ""); -static_assert(SkRegion::kUnion_Op == static_cast<SkRegion::Op>(SkClipOp::kUnion), ""); -static_assert(SkRegion::kXOR_Op == static_cast<SkRegion::Op>(SkClipOp::kXOR), ""); -static_assert(SkRegion::kReverseDifference_Op == static_cast<SkRegion::Op>(SkClipOp::kReverseDifference), ""); -static_assert(SkRegion::kReplace_Op == static_cast<SkRegion::Op>(SkClipOp::kReplace), ""); +static_assert(SkRegion::kUnion_Op == static_cast<SkRegion::Op>(SkClipOp::kUnion_deprecated), ""); +static_assert(SkRegion::kXOR_Op == static_cast<SkRegion::Op>(SkClipOp::kXOR_deprecated), ""); +static_assert(SkRegion::kReverseDifference_Op == static_cast<SkRegion::Op>(SkClipOp::kReverseDifference_deprecated), ""); +static_assert(SkRegion::kReplace_Op == static_cast<SkRegion::Op>(SkClipOp::kReplace_deprecated), ""); static SkClipOp opHandleToClipOp(jint opHandle) { // The opHandle is defined in Canvas.java to be Region::Op diff --git a/core/proto/android/providers/settings.proto b/core/proto/android/providers/settings.proto index 7eb0582a026f..98c9e781efee 100644 --- a/core/proto/android/providers/settings.proto +++ b/core/proto/android/providers/settings.proto @@ -97,7 +97,7 @@ message GlobalSettingsProto { SettingProto download_max_bytes_over_mobile = 52; SettingProto download_recommended_max_bytes_over_mobile = 53; SettingProto hdmi_control_enabled = 54; - SettingProto hdmi_system_audio_enabled = 55; + SettingProto hdmi_system_audio_control_enabled = 55; SettingProto hdmi_control_auto_wakeup_enabled = 56; SettingProto hdmi_control_auto_device_off_enabled = 57; SettingProto mhl_input_switching_enabled = 58; diff --git a/core/res/res/layout/autofill_dataset_picker.xml b/core/res/res/layout/autofill_dataset_picker.xml index 9b90de600397..133265b60e1f 100644 --- a/core/res/res/layout/autofill_dataset_picker.xml +++ b/core/res/res/layout/autofill_dataset_picker.xml @@ -18,6 +18,7 @@ android:id="@+id/autofill_dataset_picker" android:layout_width="wrap_content" android:layout_height="fill_parent" - android:divider="?android:attr/listDivider" - android:background="#ffffffff"> + android:divider="@null" + android:background="#ffffffff" + android:elevation="@dimen/floating_window_z"> </ListView> diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index 21c8780096f9..4432e3c90f1a 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -576,6 +576,7 @@ <attr name="floatingToolbarItemBackgroundDrawable" format="reference" /> <attr name="floatingToolbarOpenDrawable" format="reference" /> <attr name="floatingToolbarPopupBackgroundDrawable" format="reference" /> + <attr name="floatingToolbarDividerColor" format="reference" /> <!-- ============ --> <!-- Alert Dialog styles --> @@ -2307,37 +2308,9 @@ </attr> <!-- Describes the content of a view so that a autofill service can fill in the appropriate - data. Multiple flags can be combined to mean e.g. emailAddress or postalAddress. --> - <attr name="autofillHint"> - <!-- No hint. --> - <flag name="none" value="0" /> - <!-- The view contains an email address. --> - <flag name="emailAddress" value="0x1" /> - <!-- The view contains a real name. --> - <flag name="name" value="0x2" /> - <!-- The view contains a user name. --> - <flag name="username" value="0x4" /> - <!-- The view contains a password. --> - <flag name="password" value="0x8" /> - <!-- The view contains a phone number. --> - <flag name="phone" value="0x10" /> - <!-- The view contains a postal address. --> - <flag name="postalAddress" value="0x20" /> - <!-- The view contains a postal code. --> - <flag name="postalCode" value="0x40" /> - <!-- The view contains a credit card number. --> - <flag name="creditCardNumber" value="0x80" /> - <!-- The view contains a credit card security code --> - <flag name="creditCardSecurityCode" value="0x100" /> - <!-- The view contains a credit card expiration date --> - <flag name="creditCardExpirationDate" value="0x200" /> - <!-- The view contains the month a credit card expires --> - <flag name="creditCardExpirationMonth" value="0x400" /> - <!-- The view contains the year a credit card expires --> - <flag name="creditCardExpirationYear" value="0x800" /> - <!-- The view contains the day a credit card expires --> - <flag name="creditCardExpirationDay" value="0x1000" /> - </attr> + data. Multiple hints can be combined in a comma separated list or an array of strings + to mean e.g. emailAddress or postalAddress. --> + <attr name="autofillHint" format="string|reference" /> <!-- Hints the Android System whether the view node associated with this View should be included in a view structure used for autofill purposes. --> @@ -7213,7 +7186,7 @@ <!-- The key of another Preference that this Preference will depend on. If the other Preference is not set or is off, this Preference will be disabled. --> <attr name="dependency" format="string" /> - <!-- Whether the Preference stores its value to the shared preferences. --> + <!-- Whether the Preference stores its value to the storage. --> <attr name="persistent" /> <!-- The default value for the preference, which will be set either if persistence is off or persistence is on and the preference is not found in the persistent @@ -7222,6 +7195,9 @@ <!-- Whether the view of this Preference should be disabled when this Preference is disabled. --> <attr name="shouldDisableView" format="boolean" /> + <!-- Whether the preference has enabled to have its view recycled when used in the list + view. This is true by default. --> + <attr name="recycleEnabled" format="boolean" /> </declare-styleable> <!-- Base attributes available to CheckBoxPreference. --> @@ -8404,7 +8380,9 @@ <!-- Component name of an activity that allows the user to set up this service. --> <attr name="setupActivity" format="string" /> <!-- Component name of an activity that allows the user to modify the settings for this - service. --> + service. + {@deprecated This value is deprecated and not used by the framework starting from API + level 26. Use setupActivity instead.} --> <attr name="settingsActivity" /> <!-- Attribute whether the TV input service can record programs. This value can be changed at runtime by calling diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml index bfe666eed6e9..67050f751f5e 100644 --- a/core/res/res/values/attrs_manifest.xml +++ b/core/res/res/values/attrs_manifest.xml @@ -2401,6 +2401,9 @@ <!-- Load order of overlay package. --> <attr name="priority" /> + <!-- Whether the given RRO is static or not. --> + <attr name="isStatic" format="boolean" /> + </declare-styleable> <!-- Declaration of an {@link android.content.Intent} object in XML. May diff --git a/core/res/res/values/colors.xml b/core/res/res/values/colors.xml index 6015ed5259d8..f9fd57cf5df9 100644 --- a/core/res/res/values/colors.xml +++ b/core/res/res/values/colors.xml @@ -194,4 +194,8 @@ <color name="tooltip_background_dark">#e6616161</color> <color name="tooltip_background_light">#e6FFFFFF</color> + + <!-- FloatingToolbar --> + <color name="floating_popup_divider_dark">#2F2F2F</color> + <color name="floating_popup_divider_light">#E9E9E9</color> </resources> diff --git a/core/res/res/values/ids.xml b/core/res/res/values/ids.xml index f8a071d04372..cd3624db9afa 100644 --- a/core/res/res/values/ids.xml +++ b/core/res/res/values/ids.xml @@ -138,4 +138,8 @@ <!-- Accessibility action identifier for {@link android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction#ACTION_MOVE_WINDOW}. --> <item type="id" name="accessibilityActionMoveWindow" /> + + <!-- Action used to manually trigger an autofill request --> + <item type="id" name="autofill" /> + </resources> diff --git a/core/res/res/values/locale_config.xml b/core/res/res/values/locale_config.xml index 483d05b6cbd2..ba148431cd19 100644 --- a/core/res/res/values/locale_config.xml +++ b/core/res/res/values/locale_config.xml @@ -75,7 +75,7 @@ <item>ce-RU</item> <!-- Chechen (Russia) --> <item>cgg-UG</item> <!-- Chiga (Uganda) --> <item>chr-US</item> <!-- Cherokee (United States) --> - <item>cs-CZ</item> <!-- Czech (Czech Republic) --> + <item>cs-CZ</item> <!-- Czech (Czechia) --> <item>cy-GB</item> <!-- Welsh (United Kingdom) --> <item>da-DK</item> <!-- Danish (Denmark) --> <item>da-GL</item> <!-- Danish (Greenland) --> diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index 359fbcb08266..2897c62a8aa5 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -2805,6 +2805,8 @@ <public name="autofillHint" /> <public name="fontProviderPackage" /> <public name="importantForAutofill" /> + <public name="recycleEnabled"/> + <public name="isStatic" /> </public-group> <public-group type="style" first-id="0x010302e0"> @@ -2813,6 +2815,7 @@ <public-group type="id" first-id="0x01020041"> <public name="textAssist" /> <public name="accessibilityActionMoveWindow" /> + <public name="autofill" /> </public-group> diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index d1d406d8fa3e..1ed069b2ea02 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 0c318cf5e355..07cecbc12467 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 d100c63d4ec1..a661b070872d 100644 --- a/core/res/res/values/themes.xml +++ b/core/res/res/values/themes.xml @@ -398,6 +398,7 @@ please see themes_device_defaults.xml. <item name="floatingToolbarItemBackgroundDrawable">@drawable/item_background_material_dark</item> <item name="floatingToolbarOpenDrawable">@drawable/ic_menu_moreoverflow_material_dark</item> <item name="floatingToolbarPopupBackgroundDrawable">@drawable/floating_popup_background_dark</item> + <item name="floatingToolbarDividerColor">@color/floating_popup_divider_dark</item> <!-- SearchView attributes --> <item name="searchViewStyle">@style/Widget.Holo.SearchView</item> @@ -559,6 +560,7 @@ please see themes_device_defaults.xml. <item name="floatingToolbarItemBackgroundDrawable">@drawable/item_background_material_light</item> <item name="floatingToolbarOpenDrawable">@drawable/ic_menu_moreoverflow_material_light</item> <item name="floatingToolbarPopupBackgroundDrawable">@drawable/floating_popup_background_light</item> + <item name="floatingToolbarDividerColor">@color/floating_popup_divider_light</item> <!-- Tooltip popup colors --> <item name="tooltipForegroundColor">@color/bright_foreground_dark</item> diff --git a/core/res/res/xml/sms_short_codes.xml b/core/res/res/xml/sms_short_codes.xml index 29c6b7965543..1ae922aba716 100644 --- a/core/res/res/xml/sms_short_codes.xml +++ b/core/res/res/xml/sms_short_codes.xml @@ -79,7 +79,7 @@ <!-- Cyprus: 4-6 digits (not confirmed), known premium codes listed, plus EU --> <shortcode country="cy" pattern="\\d{4,6}" premium="7510" free="116\\d{3}" /> - <!-- Czech Republic: 7-8 digits, starting with 9, plus EU: + <!-- Czechia: 7-8 digits, starting with 9, plus EU: http://www.o2.cz/osobni/en/services-by-alphabet/91670-premium_sms.html --> <shortcode country="cz" premium="9\\d{6,7}" free="116\\d{3}" /> diff --git a/core/tests/coretests/src/android/content/res/FontResourcesParserTest.java b/core/tests/coretests/src/android/content/res/FontResourcesParserTest.java index 23d3aa5a64ab..82f46909cfa8 100644 --- a/core/tests/coretests/src/android/content/res/FontResourcesParserTest.java +++ b/core/tests/coretests/src/android/content/res/FontResourcesParserTest.java @@ -20,6 +20,11 @@ import static junit.framework.Assert.assertNull; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; +import static android.content.res.FontResourcesParser.FamilyResourceEntry; +import static android.content.res.FontResourcesParser.ProviderResourceEntry; +import static android.content.res.FontResourcesParser.FontFileResourceEntry; +import static android.content.res.FontResourcesParser.FontFamilyFilesResourceEntry; + import android.app.Instrumentation; import android.support.test.InstrumentationRegistry; import android.support.test.filters.SmallTest; @@ -56,44 +61,40 @@ public class FontResourcesParserTest { public void testParse() throws XmlPullParserException, IOException { XmlResourceParser parser = mResources.getXml(R.font.samplexmlfont); - FontConfig result = FontResourcesParser.parse(parser, mResources); + FamilyResourceEntry result = FontResourcesParser.parse(parser, mResources); assertNotNull(result); - List<FontConfig.Family> families = result.getFamilies(); - assertEquals(1, families.size()); - List<FontConfig.Font> fonts = families.get(0).getFonts(); - assertEquals(4, fonts.size()); - FontConfig.Font font1 = fonts.get(0); + FontFamilyFilesResourceEntry filesEntry = (FontFamilyFilesResourceEntry) result; + FontFileResourceEntry[] fileEntries = filesEntry.getEntries(); + assertEquals(4, fileEntries.length); + FontFileResourceEntry font1 = fileEntries[0]; assertEquals(400, font1.getWeight()); assertEquals(false, font1.isItalic()); - assertEquals("res/font/samplefont.ttf", font1.getFontName()); - FontConfig.Font font2 = fonts.get(1); + assertEquals("res/font/samplefont.ttf", font1.getFileName()); + FontFileResourceEntry font2 = fileEntries[1]; assertEquals(400, font2.getWeight()); assertEquals(true, font2.isItalic()); - assertEquals("res/font/samplefont2.ttf", font2.getFontName()); - FontConfig.Font font3 = fonts.get(2); + assertEquals("res/font/samplefont2.ttf", font2.getFileName()); + FontFileResourceEntry font3 = fileEntries[2]; assertEquals(800, font3.getWeight()); assertEquals(false, font3.isItalic()); - assertEquals("res/font/samplefont3.ttf", font3.getFontName()); - FontConfig.Font font4 = fonts.get(3); + assertEquals("res/font/samplefont3.ttf", font3.getFileName()); + FontFileResourceEntry font4 = fileEntries[3]; assertEquals(800, font4.getWeight()); assertEquals(true, font4.isItalic()); - assertEquals("res/font/samplefont4.ttf", font4.getFontName()); + assertEquals("res/font/samplefont4.ttf", font4.getFileName()); } @Test public void testParseDownloadableFont() throws IOException, XmlPullParserException { XmlResourceParser parser = mResources.getXml(R.font.samplexmldownloadedfont); - FontConfig result = FontResourcesParser.parse(parser, mResources); + FamilyResourceEntry result = FontResourcesParser.parse(parser, mResources); assertNotNull(result); - List<FontConfig.Family> families = result.getFamilies(); - assertEquals(1, families.size()); - FontConfig.Family family = families.get(0); - assertEquals("com.example.test.fontprovider.authority", family.getProviderAuthority()); - assertEquals("com.example.test.fontprovider.package", family.getProviderPackage()); - assertEquals("MyRequestedFont", family.getQuery()); - assertNull(family.getFonts()); + ProviderResourceEntry providerEntry = (ProviderResourceEntry) result; + assertEquals("com.example.test.fontprovider.authority", providerEntry.getAuthority()); + assertEquals("com.example.test.fontprovider.package", providerEntry.getPackage()); + assertEquals("MyRequestedFont", providerEntry.getQuery()); } } diff --git a/core/tests/coretests/src/android/database/PageViewCursorTest.java b/core/tests/coretests/src/android/database/PageViewCursorTest.java new file mode 100644 index 000000000000..0be89d5361d8 --- /dev/null +++ b/core/tests/coretests/src/android/database/PageViewCursorTest.java @@ -0,0 +1,318 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.database; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import android.annotation.Nullable; +import android.content.ContentResolver; +import android.os.Bundle; +import android.support.test.runner.AndroidJUnit4; +import android.util.Log; +import android.util.MathUtils; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.ArrayUtils; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.Random; + +@RunWith(AndroidJUnit4.class) +public class PageViewCursorTest { + + private static final int ITEM_COUNT = 20; + + private static final String NAME_COLUMN = "name"; + private static final String NUM_COLUMN = "num"; + + private static final String[] COLUMNS = new String[]{ + NAME_COLUMN, + NUM_COLUMN + }; + + private static final String[] NAMES = new String[] { + "000", + "111", + "222", + "333", + "444", + "555", + "666", + "777", + "888", + "999", + "aaa", + "bbb", + "ccc", + "ddd", + "eee", + "fff", + "ggg", + "hhh", + "iii", + "jjj" + }; + + private MatrixCursor mDelegate; + private PageViewCursor mCursor; + + @Before + public void setUp() { + Random rand = new Random(); + + mDelegate = new MatrixCursor(COLUMNS); + for (int i = 0; i < ITEM_COUNT; i++) { + MatrixCursor.RowBuilder row = mDelegate.newRow(); + row.add(NAME_COLUMN, NAMES[i]); + row.add(NUM_COLUMN, rand.nextInt()); + } + + mCursor = new PageViewCursor(mDelegate, 10, 5); + } + + @Test + public void testPage_Size() { + assertEquals(5, mCursor.getCount()); + } + + @Test + public void testPage_TotalSize() { + assertEquals(ITEM_COUNT, mCursor.getExtras().getInt(ContentResolver.EXTRA_TOTAL_SIZE)); + } + + @Test + public void testPage_OffsetExceedsCursorCount_EffectivelyEmptyCursor() { + mCursor = new PageViewCursor(mDelegate, ITEM_COUNT * 2, 5); + assertEquals(0, mCursor.getCount()); + } + + @Test + public void testMoveToPosition() { + assertTrue(mCursor.moveToPosition(0)); + assertEquals(NAMES[10], mCursor.getString(0)); + assertTrue(mCursor.moveToPosition(1)); + assertEquals(NAMES[11], mCursor.getString(0)); + assertTrue(mCursor.moveToPosition(4)); + assertEquals(NAMES[14], mCursor.getString(0)); + + // and then back down again for good measure. + assertTrue(mCursor.moveToPosition(1)); + assertEquals(NAMES[11], mCursor.getString(0)); + assertTrue(mCursor.moveToPosition(0)); + assertEquals(NAMES[10], mCursor.getString(0)); + } + + @Test + public void testMoveToPosition_MoveToSamePosition_NoOp() { + assertTrue(mCursor.moveToPosition(1)); + assertEquals(NAMES[11], mCursor.getString(0)); + assertTrue(mCursor.moveToPosition(1)); + assertEquals(NAMES[11], mCursor.getString(0)); + } + + @Test + public void testMoveToPosition_PositionOutOfBounds_MovesToBeforeFirst() { + assertTrue(mCursor.moveToPosition(0)); + assertEquals(NAMES[10], mCursor.getString(0)); + + // move before + assertFalse(mCursor.moveToPosition(-12)); + assertTrue(mCursor.isBeforeFirst()); + } + + @Test + public void testMoveToPosition_PositionOutOfBounds_MovesToAfterLast() { + assertTrue(mCursor.moveToPosition(0)); + assertEquals(NAMES[10], mCursor.getString(0)); + + assertFalse(mCursor.moveToPosition(222)); + assertTrue(mCursor.isAfterLast()); + } + + @Test + public void testPosition() { + assertEquals(-1, mCursor.getPosition()); + } + + @Test + public void testIsBeforeFirst() { + assertTrue(mCursor.isBeforeFirst()); + mCursor.moveToFirst(); + assertFalse(mCursor.isBeforeFirst()); + } + + @Test + public void testCount_ZeroForEmptyCursor() { + mCursor = new PageViewCursor(mDelegate, 0, 0); + assertEquals(0, mCursor.getCount()); + } + + @Test + public void testIsBeforeFirst_TrueForEmptyCursor() { + mCursor = new PageViewCursor(mDelegate, 0, 0); + assertTrue(mCursor.isBeforeFirst()); + } + + @Test + public void testIsAfterLast() { + assertFalse(mCursor.isAfterLast()); + mCursor.moveToLast(); + mCursor.moveToNext(); + assertTrue(mCursor.isAfterLast()); + } + + @Test + public void testIsAfterLast_TrueForEmptyCursor() { + mCursor = new PageViewCursor(mDelegate, 0, 0); + assertTrue(mCursor.isAfterLast()); + } + + @Test + public void testIsFirst() { + assertFalse(mCursor.isFirst()); + mCursor.moveToFirst(); + assertTrue(mCursor.isFirst()); + } + + @Test + public void testIsLast() { + assertFalse(mCursor.isLast()); + mCursor.moveToLast(); + assertTrue(mCursor.isLast()); + } + + @Test + public void testMove() { + // note that initial position is -1, so moving + // 2 will only put as at 1. + mCursor.move(2); + assertEquals(NAMES[11], mCursor.getString(0)); + mCursor.move(-1); + assertEquals(NAMES[10], mCursor.getString(0)); + } + + @Test + public void testMoveToFist() { + mCursor.moveToPosition(3); + mCursor.moveToFirst(); + assertEquals(NAMES[10], mCursor.getString(0)); + } + + @Test + public void testMoveToLast() { + mCursor.moveToLast(); + assertEquals(NAMES[14], mCursor.getString(0)); + } + + @Test + public void testMoveToNext() { + // default position is -1, so next is 0. + mCursor.moveToNext(); + assertEquals(NAMES[10], mCursor.getString(0)); + } + + @Test + public void testMoveToNext_AfterLastReturnsFalse() { + mCursor.moveToLast(); + assertFalse(mCursor.moveToNext()); + } + + @Test + public void testMoveToPrevious() { + mCursor.moveToPosition(3); + mCursor.moveToPrevious(); + assertEquals(NAMES[12], mCursor.getString(0)); + } + + @Test + public void testMoveToPrevious_BeforeFirstReturnsFalse() { + assertFalse(mCursor.moveToPrevious()); + } + + @Test + public void testWindow_ReadPastEnd() { + assertFalse(mCursor.moveToPosition(10)); + } + + @Test + public void testOffset_LimitOutOfBounds() { + mCursor = new PageViewCursor(mDelegate, 5, 100); + assertEquals(15, mCursor.getCount()); + } + + @Test + public void testPagingMarker() { + mCursor = new PageViewCursor(mDelegate, 5, 100); + assertTrue(mCursor.getExtras().getBoolean(PageViewCursor.EXTRA_AUTO_PAGED)); + } + + @Test + public void testWrap() { + Bundle queryArgs = new Bundle(); + queryArgs.putInt(ContentResolver.QUERY_ARG_OFFSET, 5); + queryArgs.putInt(ContentResolver.QUERY_ARG_LIMIT, 5); + Cursor wrapped = PageViewCursor.wrap(mDelegate, queryArgs); + assertTrue(wrapped instanceof PageViewCursor); + assertEquals(5, wrapped.getCount()); + } + + @Test + public void testWrap_NoOpWithoutPagingArgs() { + Cursor wrapped = PageViewCursor.wrap(mDelegate, Bundle.EMPTY); + assertTrue(mDelegate == wrapped); + } + + @Test + public void testWrap_NoOpCursorsWithExistingPaging_ByTotalSize() { + Bundle extras = new Bundle(); + extras.putInt(ContentResolver.EXTRA_TOTAL_SIZE, 5); + mDelegate.setExtras(extras); + + Bundle queryArgs = new Bundle(); + queryArgs.putInt(ContentResolver.QUERY_ARG_OFFSET, 5); + queryArgs.putInt(ContentResolver.QUERY_ARG_LIMIT, 5); + Cursor wrapped = PageViewCursor.wrap(mDelegate, queryArgs); + assertTrue(mDelegate == wrapped); + } + + @Test + public void testWrap_NoOpCursorsWithExistingPaging_ByHonoredArgs() { + Bundle extras = new Bundle(); + extras.putStringArray( + ContentResolver.EXTRA_HONORED_ARGS, + new String[] { + ContentResolver.QUERY_ARG_OFFSET, + ContentResolver.QUERY_ARG_LIMIT + }); + mDelegate.setExtras(extras); + + Bundle queryArgs = new Bundle(); + queryArgs.putInt(ContentResolver.QUERY_ARG_OFFSET, 5); + queryArgs.putInt(ContentResolver.QUERY_ARG_LIMIT, 5); + Cursor wrapped = PageViewCursor.wrap(mDelegate, queryArgs); + assertTrue(mDelegate == wrapped); + } + + private void assertStringAt(int row, int column, String expected) { + mCursor.moveToPosition(row); + assertEquals(expected, mCursor.getString(column)); + } +} diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java index 903ef84df4e0..0cfdaf5d3cab 100644 --- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java +++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java @@ -198,7 +198,7 @@ public class SettingsBackupTest { Settings.Global.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED, Settings.Global.HDMI_CONTROL_AUTO_WAKEUP_ENABLED, Settings.Global.HDMI_CONTROL_ENABLED, - Settings.Global.HDMI_SYSTEM_AUDIO_ENABLED, + Settings.Global.HDMI_SYSTEM_AUDIO_CONTROL_ENABLED, Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED, Settings.Global.HTTP_PROXY, Settings.Global.INET_CONDITION_DEBOUNCE_DOWN_DELAY, diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsSensorTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsSensorTest.java index e15216384ec1..4ec78ff5be7f 100644 --- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsSensorTest.java +++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsSensorTest.java @@ -65,4 +65,49 @@ public class BatteryStatsSensorTest extends TestCase { assertEquals(1, sensorBgCounter.getCountLocked(BatteryStats.STATS_SINCE_CHARGED)); } + + @SmallTest + public void testNestedSensorReset() throws Exception { + final MockClocks clocks = new MockClocks(); + MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks); + bi.mForceOnBattery = true; + clocks.realtime = 100; + clocks.uptime = 100; + bi.getOnBatteryTimeBase().setRunning(true, 100, 100); + bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_RECEIVER); + + clocks.realtime += 100; + clocks.uptime += 100; + + bi.noteStartSensorLocked(UID, SENSOR_ID); + + clocks.realtime += 100; + clocks.uptime += 100; + + // The sensor is started and the background counter has been created. + final BatteryStats.Uid uid = bi.getUidStats().get(UID); + assertNotNull(uid); + + BatteryStats.Uid.Sensor sensor = uid.getSensorStats().get(SENSOR_ID); + assertNotNull(sensor); + assertNotNull(sensor.getSensorTime()); + assertNotNull(sensor.getSensorBgCount()); + + // Reset the stats. Since the sensor is still running, we should still see the sensor + // timer. Background counter should be gone though. + bi.getUidStatsLocked(UID).reset(); + + sensor = uid.getSensorStats().get(SENSOR_ID); + assertNotNull(sensor); + assertNotNull(sensor.getSensorTime()); + assertNull(sensor.getSensorBgCount()); + + bi.noteStopSensorLocked(UID, SENSOR_ID); + + // Now the sensor timer has stopped so this reset should also take out the sensor. + bi.getUidStatsLocked(UID).reset(); + + sensor = uid.getSensorStats().get(SENSOR_ID); + assertNull(sensor); + } } diff --git a/data/fonts/fonts.xml b/data/fonts/fonts.xml index 76598a06c165..38a1a466400d 100644 --- a/data/fonts/fonts.xml +++ b/data/fonts/fonts.xml @@ -248,6 +248,9 @@ <font weight="400" style="normal">NotoSansCham-Regular.ttf</font> <font weight="700" style="normal">NotoSansCham-Bold.ttf</font> </family> + <family lang="und-Avst"> + <font weight="400" style="normal">NotoSansAvestan-Regular.ttf</font> + </family> <family lang="und-Bali"> <font weight="400" style="normal">NotoSansBalinese-Regular.ttf</font> </family> @@ -257,6 +260,9 @@ <family lang="und-Batk"> <font weight="400" style="normal">NotoSansBatak-Regular.ttf</font> </family> + <family lang="und-Brah"> + <font weight="400" style="normal">NotoSansBrahmi-Regular.ttf</font> + </family> <family lang="und-Bugi"> <font weight="400" style="normal">NotoSansBuginese-Regular.ttf</font> </family> @@ -266,33 +272,75 @@ <family lang="und-Cans"> <font weight="400" style="normal">NotoSansCanadianAboriginal-Regular.ttf</font> </family> + <family lang="und-Cari"> + <font weight="400" style="normal">NotoSansCarian-Regular.ttf</font> + </family> <family lang="und-Cher"> <font weight="400" style="normal">NotoSansCherokee-Regular.ttf</font> </family> <family lang="und-Copt"> <font weight="400" style="normal">NotoSansCoptic-Regular.ttf</font> </family> + <family lang="und-Xsux"> + <font weight="400" style="normal">NotoSansCuneiform-Regular.ttf</font> + </family> + <family lang="und-Cprt"> + <font weight="400" style="normal">NotoSansCypriot-Regular.ttf</font> + </family> + <family lang="und-Dsrt"> + <font weight="400" style="normal">NotoSansDeseret-Regular.ttf</font> + </family> + <family lang="und-Egyp"> + <font weight="400" style="normal">NotoSansEgyptianHieroglyphs-Regular.ttf</font> + </family> <family lang="und-Glag"> <font weight="400" style="normal">NotoSansGlagolitic-Regular.ttf</font> </family> + <family lang="und-Goth"> + <font weight="400" style="normal">NotoSansGothic-Regular.ttf</font> + </family> <family lang="und-Hano"> <font weight="400" style="normal">NotoSansHanunoo-Regular.ttf</font> </family> + <family lang="und-Armi"> + <font weight="400" style="normal">NotoSansImperialAramaic-Regular.ttf</font> + </family> + <family lang="und-Phli"> + <font weight="400" style="normal">NotoSansInscriptionalPahlavi-Regular.ttf</font> + </family> + <family lang="und-Prti"> + <font weight="400" style="normal">NotoSansInscriptionalParthian-Regular.ttf</font> + </family> <family lang="und-Java"> <font weight="400" style="normal">NotoSansJavanese-Regular.ttf</font> </family> + <family lang="und-Kthi"> + <font weight="400" style="normal">NotoSansKaithi-Regular.ttf</font> + </family> <family lang="und-Kali"> <font weight="400" style="normal">NotoSansKayahLi-Regular.ttf</font> </family> + <family lang="und-Khar"> + <font weight="400" style="normal">NotoSansKharoshthi-Regular.ttf</font> + </family> <family lang="und-Lepc"> <font weight="400" style="normal">NotoSansLepcha-Regular.ttf</font> </family> <family lang="und-Limb"> <font weight="400" style="normal">NotoSansLimbu-Regular.ttf</font> </family> + <family lang="und-Linb"> + <font weight="400" style="normal">NotoSansLinearB-Regular.ttf</font> + </family> <family lang="und-Lisu"> <font weight="400" style="normal">NotoSansLisu-Regular.ttf</font> </family> + <family lang="und-Lyci"> + <font weight="400" style="normal">NotoSansLycian-Regular.ttf</font> + </family> + <family lang="und-Lydi"> + <font weight="400" style="normal">NotoSansLydian-Regular.ttf</font> + </family> <family lang="und-Mand"> <font weight="400" style="normal">NotoSansMandaic-Regular.ttf</font> </family> @@ -305,12 +353,33 @@ <family lang="und-Nkoo"> <font weight="400" style="normal">NotoSansNKo-Regular.ttf</font> </family> + <family lang="und-Ogam"> + <font weight="400" style="normal">NotoSansOgham-Regular.ttf</font> + </family> <family lang="und-Olck"> <font weight="400" style="normal">NotoSansOlChiki-Regular.ttf</font> </family> + <family lang="und-Ital"> + <font weight="400" style="normal">NotoSansOldItalic-Regular.ttf</font> + </family> + <family lang="und-Xpeo"> + <font weight="400" style="normal">NotoSansOldPersian-Regular.ttf</font> + </family> + <family lang="und-Sarb"> + <font weight="400" style="normal">NotoSansOldSouthArabian-Regular.ttf</font> + </family> + <family lang="und-Orkh"> + <font weight="400" style="normal">NotoSansOldTurkic-Regular.ttf</font> + </family> + <family lang="und-Osma"> + <font weight="400" style="normal">NotoSansOsmanya-Regular.ttf</font> + </family> <family lang="und-Phag"> <font weight="400" style="normal">NotoSansPhagsPa-Regular.ttf</font> </family> + <family lang="und-Phnx"> + <font weight="400" style="normal">NotoSansPhoenician-Regular.ttf</font> + </family> <family lang="und-Rjng"> <font weight="400" style="normal">NotoSansRejang-Regular.ttf</font> </family> @@ -323,6 +392,9 @@ <family lang="und-Saur"> <font weight="400" style="normal">NotoSansSaurashtra-Regular.ttf</font> </family> + <family lang="und-Shaw"> + <font weight="400" style="normal">NotoSansShavian-Regular.ttf</font> + </family> <family lang="und-Sund"> <font weight="400" style="normal">NotoSansSundanese-Regular.ttf</font> </family> @@ -358,6 +430,9 @@ <family lang="und-Tfng"> <font weight="400" style="normal">NotoSansTifinagh-Regular.ttf</font> </family> + <family lang="und-Ugar"> + <font weight="400" style="normal">NotoSansUgaritic-Regular.ttf</font> + </family> <family lang="und-Vaii"> <font weight="400" style="normal">NotoSansVai-Regular.ttf</font> </family> diff --git a/graphics/java/android/graphics/Bitmap.java b/graphics/java/android/graphics/Bitmap.java index 3d5ba7938b31..ed587bbaed24 100644 --- a/graphics/java/android/graphics/Bitmap.java +++ b/graphics/java/android/graphics/Bitmap.java @@ -791,12 +791,12 @@ public final class Bitmap implements Parcelable { int neww = width; int newh = height; - Canvas canvas = new Canvas(); Bitmap bitmap; Paint paint; Rect srcR = new Rect(x, y, x + width, y + height); RectF dstR = new RectF(0, 0, width, height); + RectF deviceR = new RectF(); Config newConfig = Config.ARGB_8888; final Config config = source.getConfig(); @@ -827,7 +827,6 @@ public final class Bitmap implements Parcelable { } else { final boolean transformed = !m.rectStaysRect(); - RectF deviceR = new RectF(); m.mapRect(deviceR, dstR); neww = Math.round(deviceR.width()); @@ -841,9 +840,6 @@ public final class Bitmap implements Parcelable { } bitmap = createBitmap(neww, newh, transformedConfig, transformed || source.hasAlpha()); - canvas.translate(-deviceR.left, -deviceR.top); - canvas.concat(m); - paint = new Paint(); paint.setFilterBitmap(filter); if (transformed) { @@ -857,7 +853,9 @@ public final class Bitmap implements Parcelable { bitmap.setHasAlpha(source.hasAlpha()); bitmap.setPremultiplied(source.mRequestPremultiplied); - canvas.setBitmap(bitmap); + Canvas canvas = new Canvas(bitmap); + canvas.translate(-deviceR.left, -deviceR.top); + canvas.concat(m); canvas.drawBitmap(source, srcR, dstR, paint); canvas.setBitmap(null); if (isHardware) { diff --git a/graphics/java/android/graphics/ColorSpace.java b/graphics/java/android/graphics/ColorSpace.java index 908ec5045b27..929ac22f2960 100644 --- a/graphics/java/android/graphics/ColorSpace.java +++ b/graphics/java/android/graphics/ColorSpace.java @@ -490,16 +490,16 @@ public abstract class ColorSpace { * <tr> * <td>Opto-electronic transfer function (OETF)</td> * <td colspan="4">\(\begin{equation} - * C_{sRGB} = \begin{cases} 12.92 \times C_{linear} & C_{linear} \lt 0.0031308 \\ - * 1.055 \times C_{linear}^{\frac{1}{2.4}} - 0.055 & C_{linear} \ge 0.0031308 \end{cases} + * C_{DisplayP3} = \begin{cases} 12.92 \times C_{linear} & C_{linear} \lt 0.0030186 \\ + * 1.055 \times C_{linear}^{\frac{1}{2.4}} - 0.055 & C_{linear} \ge 0.0030186 \end{cases} * \end{equation}\) * </td> * </tr> * <tr> * <td>Electro-optical transfer function (EOTF)</td> * <td colspan="4">\(\begin{equation} - * C_{linear} = \begin{cases}\frac{C_{sRGB}}{12.92} & C_{sRGB} \lt 0.04045 \\ - * \left( \frac{C_{sRGB} + 0.055}{1.055} \right) ^{2.4} & C_{sRGB} \ge 0.04045 \end{cases} + * C_{linear} = \begin{cases}\frac{C_{DisplayP3}}{12.92} & C_{sRGB} \lt 0.039 \\ + * \left( \frac{C_{DisplayP3} + 0.055}{1.055} \right) ^{2.4} & C_{sRGB} \ge 0.039 \end{cases} * \end{equation}\) * </td> * </tr> @@ -1482,7 +1482,7 @@ public abstract class ColorSpace { "Display P3", new float[] { 0.680f, 0.320f, 0.265f, 0.690f, 0.150f, 0.060f }, ILLUMINANT_D65, - new Rgb.TransferParameters(1 / 1.055, 0.055 / 1.055, 1 / 12.92, 0.04045, 2.4), + new Rgb.TransferParameters(1 / 1.055, 0.055 / 1.055, 1 / 12.92, 0.039, 2.4), Named.DISPLAY_P3.ordinal() ); sNamedColorSpaces[Named.NTSC_1953.ordinal()] = new ColorSpace.Rgb( diff --git a/graphics/java/android/graphics/FontFamily.java b/graphics/java/android/graphics/FontFamily.java index 317f232ef7ec..16fc2b143964 100644 --- a/graphics/java/android/graphics/FontFamily.java +++ b/graphics/java/android/graphics/FontFamily.java @@ -25,7 +25,6 @@ import java.io.FileInputStream; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; -import java.util.List; /** * A family of typefaces with different styles. @@ -48,14 +47,8 @@ public class FontFamily { mBuilderPtr = nInitBuilder(null, 0); } - public FontFamily(String lang, String variant) { - int varEnum = 0; - if ("compact".equals(variant)) { - varEnum = 1; - } else if ("elegant".equals(variant)) { - varEnum = 2; - } - mBuilderPtr = nInitBuilder(lang, varEnum); + public FontFamily(String lang, int variant) { + mBuilderPtr = nInitBuilder(lang, variant); } public void freeze() { @@ -103,12 +96,15 @@ public class FontFamily { } } - public boolean addFontWeightStyle(ByteBuffer font, int ttcIndex, List<FontConfig.Axis> axes, + public boolean addFontWeightStyle(ByteBuffer font, int ttcIndex, FontConfig.Axis[] axes, int weight, boolean style) { if (mBuilderPtr == 0) { throw new IllegalStateException("Unable to call addFontWeightStyle after freezing."); } - return nAddFontWeightStyle(mBuilderPtr, font, ttcIndex, axes, weight, style); + for (FontConfig.Axis axis : axes) { + nAddAxisValue(mBuilderPtr, axis.getTag(), axis.getStyleValue()); + } + return nAddFontWeightStyle(mBuilderPtr, font, ttcIndex, weight, style); } /** @@ -143,8 +139,11 @@ public class FontFamily { private static native void nUnrefFamily(long nativePtr); private static native boolean nAddFont(long builderPtr, ByteBuffer font, int ttcIndex); private static native boolean nAddFontWeightStyle(long builderPtr, ByteBuffer font, - int ttcIndex, List<FontConfig.Axis> listOfAxis, - int weight, boolean isItalic); + int ttcIndex, int weight, boolean isItalic); private static native boolean nAddFontFromAssetManager(long builderPtr, AssetManager mgr, String path, int cookie, boolean isAsset, int weight, boolean isItalic); + + // The added axis values are only valid for the next nAddFont* method call. + @CriticalNative + private static native void nAddAxisValue(long builderPtr, int tag, float value); } diff --git a/graphics/java/android/graphics/FontListParser.java b/graphics/java/android/graphics/FontListParser.java index b757842c4f3f..1b6969f96f82 100644 --- a/graphics/java/android/graphics/FontListParser.java +++ b/graphics/java/android/graphics/FontListParser.java @@ -116,20 +116,23 @@ public class FontListParser { private static FontConfig readFamilies(XmlPullParser parser) throws XmlPullParserException, IOException { - FontConfig config = new FontConfig(); + List<FontConfig.Family> families = new ArrayList<>(); + List<FontConfig.Alias> aliases = new ArrayList<>(); + parser.require(XmlPullParser.START_TAG, null, "familyset"); while (parser.next() != XmlPullParser.END_TAG) { if (parser.getEventType() != XmlPullParser.START_TAG) continue; String tag = parser.getName(); if (tag.equals("family")) { - config.getFamilies().add(readFamily(parser)); + families.add(readFamily(parser)); } else if (tag.equals("alias")) { - config.getAliases().add(readAlias(parser)); + aliases.add(readAlias(parser)); } else { skip(parser); } } - return config; + return new FontConfig(families.toArray(new FontConfig.Family[families.size()]), + aliases.toArray(new FontConfig.Alias[aliases.size()])); } private static FontConfig.Family readFamily(XmlPullParser parser) @@ -147,7 +150,16 @@ public class FontListParser { skip(parser); } } - return new FontConfig.Family(name, fonts, lang, variant); + int intVariant = FontConfig.Family.VARIANT_DEFAULT; + if (variant != null) { + if (variant.equals("compact")) { + intVariant = FontConfig.Family.VARIANT_COMPACT; + } else if (variant.equals("elegant")) { + intVariant = FontConfig.Family.VARIANT_ELEGANT; + } + } + return new FontConfig.Family(name, fonts.toArray(new FontConfig.Font[fonts.size()]), lang, + intVariant); } /** Matches leading and trailing XML whitespace. */ @@ -177,7 +189,8 @@ public class FontListParser { } String fullFilename = "/system/fonts/" + FILENAME_WHITESPACE_PATTERN.matcher(filename).replaceAll(""); - return new FontConfig.Font(fullFilename, index, axes, weight, isItalic); + return new FontConfig.Font(fullFilename, index, + axes.toArray(new FontConfig.Axis[axes.size()]), weight, isItalic); } /** The 'tag' attribute value is read as four character values between U+0020 and U+007E diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java index b1d51ec2d92b..7ca4615434f5 100644 --- a/graphics/java/android/graphics/Paint.java +++ b/graphics/java/android/graphics/Paint.java @@ -2294,7 +2294,7 @@ public class Paint { throw new IndexOutOfBoundsException(); } - return nGetTextRunCursor(mNativePaint, text, + return nGetTextRunCursor(mNativePaint, mNativeTypeface, text, contextStart, contextLength, dir, offset, cursorOpt); } @@ -2380,7 +2380,7 @@ public class Paint { throw new IndexOutOfBoundsException(); } - return nGetTextRunCursor(mNativePaint, text, + return nGetTextRunCursor(mNativePaint, mNativeTypeface, text, contextStart, contextEnd, dir, offset, cursorOpt); } @@ -2686,9 +2686,9 @@ public class Paint { private static native float nGetTextAdvances(long paintPtr, long typefacePtr, String text, int start, int end, int contextStart, int contextEnd, int bidiFlags, float[] advances, int advancesIndex); - private native int nGetTextRunCursor(long paintPtr, char[] text, + private native int nGetTextRunCursor(long paintPtr, long typefacePtr, char[] text, int contextStart, int contextLength, int dir, int offset, int cursorOpt); - private native int nGetTextRunCursor(long paintPtr, String text, + private native int nGetTextRunCursor(long paintPtr, long typefacePtr, String text, int contextStart, int contextEnd, int dir, int offset, int cursorOpt); private static native void nGetTextPath(long paintPtr, long typefacePtr, int bidiFlags, char[] text, int index, int count, float x, float y, long path); diff --git a/graphics/java/android/graphics/Typeface.java b/graphics/java/android/graphics/Typeface.java index 6de19cb0f3b2..95577caf65a8 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 @@ public class Typeface { * Used by Resources to load a font resource of type xml. */ @Nullable - public static Typeface createFromResources(FontConfig config, AssetManager mgr, String path) { + public static Typeface createFromResources( + FamilyResourceEntry entry, AssetManager mgr, String path) { if (sFallbackFonts != null) { Typeface typeface = findFromCache(mgr, path); if (typeface != null) return typeface; - List<FontConfig.Family> families = config.getFamilies(); - if (families == null || families.isEmpty()) { - throw new RuntimeException( - "Font resource " + path + " contained no font families."); - } - if (families.size() > 1) { - throw new RuntimeException( - "Font resource " + path + " contained more than one family."); - } - FontConfig.Family family = families.get(0); - if (family.getProviderAuthority() != null && family.getQuery() != null) { + if (entry instanceof ProviderResourceEntry) { + final ProviderResourceEntry providerEntry = (ProviderResourceEntry) entry; // Downloadable font - typeface = findFromCache( - family.getProviderAuthority(), family.getQuery()); + typeface = findFromCache(providerEntry.getAuthority(), providerEntry.getQuery()); if (typeface != null) { return typeface; } // Downloaded font and it wasn't cached, request it again and return a // default font instead (nothing we can do now). - create(new FontRequest(family.getProviderAuthority(), family.getProviderPackage(), - family.getQuery()), NO_OP_REQUEST_CALLBACK); + create(new FontRequest(providerEntry.getAuthority(), providerEntry.getPackage(), + providerEntry.getQuery()), NO_OP_REQUEST_CALLBACK); return DEFAULT; } + // family is FontFamilyFilesResourceEntry + final FontFamilyFilesResourceEntry filesEntry = + (FontFamilyFilesResourceEntry) entry; + FontFamily fontFamily = new FontFamily(); - List<FontConfig.Font> fonts = family.getFonts(); - if (fonts == null || fonts.isEmpty()) { - throw new RuntimeException("Font resource " + path + " contained no fonts."); - } - for (int i = 0; i < fonts.size(); i++) { - FontConfig.Font font = fonts.get(i); - // TODO: Use style and weight info - if (!fontFamily.addFontFromAssetManager(mgr, font.getFontName(), - 0 /* resourceCookie */, false /* isAsset */, font.getWeight(), - font.isItalic())) { + for (final FontFileResourceEntry fontFile : filesEntry.getEntries()) { + if (!fontFamily.addFontFromAssetManager(mgr, fontFile.getFileName(), + 0 /* resourceCookie */, false /* isAsset */, fontFile.getWeight(), + fontFile.isItalic())) { return null; } } @@ -677,8 +671,8 @@ public class Typeface { List<FontFamily> familyList = new ArrayList<FontFamily>(); // Note that the default typeface is always present in the fallback list; // this is an enhancement from pre-Minikin behavior. - for (int i = 0; i < fontConfig.getFamilies().size(); i++) { - FontConfig.Family f = fontConfig.getFamilies().get(i); + for (int i = 0; i < fontConfig.getFamilies().length; i++) { + FontConfig.Family f = fontConfig.getFamilies()[i]; if (i == 0 || f.getName() == null) { familyList.add(makeFamilyFromParsed(f, bufferForPath)); } @@ -687,9 +681,9 @@ public class Typeface { setDefault(Typeface.createFromFamilies(sFallbackFonts)); Map<String, Typeface> systemFonts = new HashMap<String, Typeface>(); - for (int i = 0; i < fontConfig.getFamilies().size(); i++) { + for (int i = 0; i < fontConfig.getFamilies().length; i++) { Typeface typeface; - FontConfig.Family f = fontConfig.getFamilies().get(i); + FontConfig.Family f = fontConfig.getFamilies()[i]; if (f.getName() != null) { if (i == 0) { // The first entry is the default typeface; no sense in diff --git a/graphics/java/android/graphics/drawable/ShapeDrawable.java b/graphics/java/android/graphics/drawable/ShapeDrawable.java index a24b9701c9c2..67586077cd0a 100644 --- a/graphics/java/android/graphics/drawable/ShapeDrawable.java +++ b/graphics/java/android/graphics/drawable/ShapeDrawable.java @@ -545,7 +545,7 @@ public class ShapeDrawable extends Drawable { mChangingConfigurations = orig.mChangingConfigurations; mPaint = new Paint(orig.mPaint); mThemeAttrs = orig.mThemeAttrs; - if (mShape != null) { + if (orig.mShape != null) { try { mShape = orig.mShape.clone(); } catch (CloneNotSupportedException e) { diff --git a/libs/common_time/Android.mk b/libs/common_time/Android.mk index 1fec504e7969..636f05752ee4 100644 --- a/libs/common_time/Android.mk +++ b/libs/common_time/Android.mk @@ -15,7 +15,8 @@ LOCAL_SRC_FILES := \ clock_recovery.cpp \ common_clock.cpp \ main.cpp \ - utils.cpp + utils.cpp \ + LinearTransform.cpp # Uncomment to enable vesbose logging and debug service. #TIME_SERVICE_DEBUG=true diff --git a/libs/common_time/LinearTransform.cpp b/libs/common_time/LinearTransform.cpp new file mode 100644 index 000000000000..67308550fa4b --- /dev/null +++ b/libs/common_time/LinearTransform.cpp @@ -0,0 +1,281 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define __STDC_LIMIT_MACROS + +#include "LinearTransform.h" +#include <assert.h> + + +// disable sanitize as these functions may intentionally overflow (see comments below). +// the ifdef can be removed when host builds use clang. +#if defined(__clang__) +#define ATTRIBUTE_NO_SANITIZE_INTEGER __attribute__((no_sanitize("integer"))) +#else +#define ATTRIBUTE_NO_SANITIZE_INTEGER +#endif + +namespace android { + +// sanitize failure with T = int32_t and x = 0x80000000 +template<class T> +ATTRIBUTE_NO_SANITIZE_INTEGER +static inline T ABS(T x) { return (x < 0) ? -x : x; } + +// Static math methods involving linear transformations +// remote sanitize failure on overflow case. +ATTRIBUTE_NO_SANITIZE_INTEGER +static bool scale_u64_to_u64( + uint64_t val, + uint32_t N, + uint32_t D, + uint64_t* res, + bool round_up_not_down) { + uint64_t tmp1, tmp2; + uint32_t r; + + assert(res); + assert(D); + + // Let U32(X) denote a uint32_t containing the upper 32 bits of a 64 bit + // integer X. + // Let L32(X) denote a uint32_t containing the lower 32 bits of a 64 bit + // integer X. + // Let X[A, B] with A <= B denote bits A through B of the integer X. + // Let (A | B) denote the concatination of two 32 bit ints, A and B. + // IOW X = (A | B) => U32(X) == A && L32(X) == B + // + // compute M = val * N (a 96 bit int) + // --------------------------------- + // tmp2 = U32(val) * N (a 64 bit int) + // tmp1 = L32(val) * N (a 64 bit int) + // which means + // M = val * N = (tmp2 << 32) + tmp1 + tmp2 = (val >> 32) * N; + tmp1 = (val & UINT32_MAX) * N; + + // compute M[32, 95] + // tmp2 = tmp2 + U32(tmp1) + // = (U32(val) * N) + U32(L32(val) * N) + // = M[32, 95] + tmp2 += tmp1 >> 32; + + // if M[64, 95] >= D, then M/D has bits > 63 set and we have + // an overflow. + if ((tmp2 >> 32) >= D) { + *res = UINT64_MAX; + return false; + } + + // Divide. Going in we know + // tmp2 = M[32, 95] + // U32(tmp2) < D + r = tmp2 % D; + tmp2 /= D; + + // At this point + // tmp1 = L32(val) * N + // tmp2 = M[32, 95] / D + // = (M / D)[32, 95] + // r = M[32, 95] % D + // U32(tmp2) = 0 + // + // compute tmp1 = (r | M[0, 31]) + tmp1 = (tmp1 & UINT32_MAX) | ((uint64_t)r << 32); + + // Divide again. Keep the remainder around in order to round properly. + r = tmp1 % D; + tmp1 /= D; + + // At this point + // tmp2 = (M / D)[32, 95] + // tmp1 = (M / D)[ 0, 31] + // r = M % D + // U32(tmp1) = 0 + // U32(tmp2) = 0 + + // Pack the result and deal with the round-up case (As well as the + // remote possiblility over overflow in such a case). + *res = (tmp2 << 32) | tmp1; + if (r && round_up_not_down) { + ++(*res); + if (!(*res)) { + *res = UINT64_MAX; + return false; + } + } + + return true; +} + +// at least one known sanitize failure (see comment below) +ATTRIBUTE_NO_SANITIZE_INTEGER +static bool linear_transform_s64_to_s64( + int64_t val, + int64_t basis1, + int32_t N, + uint32_t D, + bool invert_frac, + int64_t basis2, + int64_t* out) { + uint64_t scaled, res; + uint64_t abs_val; + bool is_neg; + + if (!out) + return false; + + // Compute abs(val - basis_64). Keep track of whether or not this delta + // will be negative after the scale opertaion. + if (val < basis1) { + is_neg = true; + abs_val = basis1 - val; + } else { + is_neg = false; + abs_val = val - basis1; + } + + if (N < 0) + is_neg = !is_neg; + + if (!scale_u64_to_u64(abs_val, + invert_frac ? D : ABS(N), + invert_frac ? ABS(N) : D, + &scaled, + is_neg)) + return false; // overflow/undeflow + + // if scaled is >= 0x8000<etc>, then we are going to overflow or + // underflow unless ABS(basis2) is large enough to pull us back into the + // non-overflow/underflow region. + if (scaled & INT64_MIN) { + if (is_neg && (basis2 < 0)) + return false; // certain underflow + + if (!is_neg && (basis2 >= 0)) + return false; // certain overflow + + if (ABS(basis2) <= static_cast<int64_t>(scaled & INT64_MAX)) + return false; // not enough + + // Looks like we are OK + *out = (is_neg ? (-scaled) : scaled) + basis2; + } else { + // Scaled fits within signed bounds, so we just need to check for + // over/underflow for two signed integers. Basically, if both scaled + // and basis2 have the same sign bit, and the result has a different + // sign bit, then we have under/overflow. An easy way to compute this + // is + // (scaled_signbit XNOR basis_signbit) && + // (scaled_signbit XOR res_signbit) + // == + // (scaled_signbit XOR basis_signbit XOR 1) && + // (scaled_signbit XOR res_signbit) + + if (is_neg) + scaled = -scaled; // known sanitize failure + res = scaled + basis2; + + if ((scaled ^ basis2 ^ INT64_MIN) & (scaled ^ res) & INT64_MIN) + return false; + + *out = res; + } + + return true; +} + +bool LinearTransform::doForwardTransform(int64_t a_in, int64_t* b_out) const { + if (0 == a_to_b_denom) + return false; + + return linear_transform_s64_to_s64(a_in, + a_zero, + a_to_b_numer, + a_to_b_denom, + false, + b_zero, + b_out); +} + +bool LinearTransform::doReverseTransform(int64_t b_in, int64_t* a_out) const { + if (0 == a_to_b_numer) + return false; + + return linear_transform_s64_to_s64(b_in, + b_zero, + a_to_b_numer, + a_to_b_denom, + true, + a_zero, + a_out); +} + +template <class T> void LinearTransform::reduce(T* N, T* D) { + T a, b; + if (!N || !D || !(*D)) { + assert(false); + return; + } + + a = *N; + b = *D; + + if (a == 0) { + *D = 1; + return; + } + + // This implements Euclid's method to find GCD. + if (a < b) { + T tmp = a; + a = b; + b = tmp; + } + + while (1) { + // a is now the greater of the two. + const T remainder = a % b; + if (remainder == 0) { + *N /= b; + *D /= b; + return; + } + // by swapping remainder and b, we are guaranteeing that a is + // still the greater of the two upon entrance to the loop. + a = b; + b = remainder; + } +}; + +template void LinearTransform::reduce<uint64_t>(uint64_t* N, uint64_t* D); +template void LinearTransform::reduce<uint32_t>(uint32_t* N, uint32_t* D); + +// sanitize failure if *N = 0x80000000 +ATTRIBUTE_NO_SANITIZE_INTEGER +void LinearTransform::reduce(int32_t* N, uint32_t* D) { + if (N && D && *D) { + if (*N < 0) { + *N = -(*N); + reduce(reinterpret_cast<uint32_t*>(N), D); + *N = -(*N); + } else { + reduce(reinterpret_cast<uint32_t*>(N), D); + } + } +} + +} // namespace android diff --git a/libs/common_time/LinearTransform.h b/libs/common_time/LinearTransform.h new file mode 100644 index 000000000000..bf6ab8e78eb8 --- /dev/null +++ b/libs/common_time/LinearTransform.h @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef _LINEAR_TRANSFORM_H +#define _LINEAR_TRANSFORM_H + +#include <stdint.h> + +namespace android { + +// LinearTransform defines a structure which hold the definition of a +// transformation from single dimensional coordinate system A into coordinate +// system B (and back again). Values in A and in B are 64 bit, the linear +// scale factor is expressed as a rational number using two 32 bit values. +// +// Specifically, let +// f(a) = b +// F(b) = f^-1(b) = a +// then +// +// f(a) = (((a - a_zero) * a_to_b_numer) / a_to_b_denom) + b_zero; +// +// and +// +// F(b) = (((b - b_zero) * a_to_b_denom) / a_to_b_numer) + a_zero; +// +struct LinearTransform { + int64_t a_zero; + int64_t b_zero; + int32_t a_to_b_numer; + uint32_t a_to_b_denom; + + // Transform from A->B + // Returns true on success, or false in the case of a singularity or an + // overflow. + bool doForwardTransform(int64_t a_in, int64_t* b_out) const; + + // Transform from B->A + // Returns true on success, or false in the case of a singularity or an + // overflow. + bool doReverseTransform(int64_t b_in, int64_t* a_out) const; + + // Helpers which will reduce the fraction N/D using Euclid's method. + template <class T> static void reduce(T* N, T* D); + static void reduce(int32_t* N, uint32_t* D); +}; + + +} + +#endif // _LINEAR_TRANSFORM_H diff --git a/libs/common_time/clock_recovery.h b/libs/common_time/clock_recovery.h index 278a75ef2b05..8066a3968b59 100644 --- a/libs/common_time/clock_recovery.h +++ b/libs/common_time/clock_recovery.h @@ -19,9 +19,10 @@ #include <stdint.h> #include <common_time/ICommonClock.h> -#include <utils/LinearTransform.h> #include <utils/threads.h> +#include "LinearTransform.h" + #ifdef TIME_SERVICE_DEBUG #include "diag_thread.h" #endif diff --git a/libs/common_time/common_clock.cpp b/libs/common_time/common_clock.cpp index ee326e14d12b..aed52f177c80 100644 --- a/libs/common_time/common_clock.cpp +++ b/libs/common_time/common_clock.cpp @@ -23,7 +23,6 @@ #include <stdint.h> #include <utils/Errors.h> -#include <utils/LinearTransform.h> #include "common_clock.h" diff --git a/libs/common_time/common_clock.h b/libs/common_time/common_clock.h index b786fdceba5f..5e4e5f51b446 100644 --- a/libs/common_time/common_clock.h +++ b/libs/common_time/common_clock.h @@ -20,9 +20,10 @@ #include <stdint.h> #include <utils/Errors.h> -#include <utils/LinearTransform.h> #include <utils/threads.h> +#include "LinearTransform.h" + namespace android { class CommonClock { diff --git a/libs/hwui/hwui/MinikinUtils.cpp b/libs/hwui/hwui/MinikinUtils.cpp index d1871ffaf92b..415eef77f44e 100644 --- a/libs/hwui/hwui/MinikinUtils.cpp +++ b/libs/hwui/hwui/MinikinUtils.cpp @@ -58,8 +58,9 @@ minikin::Layout MinikinUtils::doLayout(const Paint* paint, int bidiFlags, size_t bufSize) { minikin::MinikinPaint minikinPaint; minikin::FontStyle minikinStyle = prepareMinikinPaint(&minikinPaint, paint, typeface); - minikin::Layout layout(Typeface::resolveDefault(typeface)->fFontCollection); - layout.doLayout(buf, start, count, bufSize, bidiFlags, minikinStyle, minikinPaint); + minikin::Layout layout; + layout.doLayout(buf, start, count, bufSize, bidiFlags, minikinStyle, minikinPaint, + Typeface::resolveDefault(typeface)->fFontCollection); return layout; } diff --git a/libs/hwui/tests/unit/CanvasStateTests.cpp b/libs/hwui/tests/unit/CanvasStateTests.cpp index 43974f650084..c41313aebb5d 100644 --- a/libs/hwui/tests/unit/CanvasStateTests.cpp +++ b/libs/hwui/tests/unit/CanvasStateTests.cpp @@ -74,7 +74,7 @@ TEST(CanvasState, simpleClipping) { state.clipRect(10, 10, 200, 200, SkClipOp::kIntersect); ASSERT_EQ(state.getRenderTargetClipBounds(), Rect(10, 10, 100, 100)); - state.clipRect(50, 50, 150, 150, SkClipOp::kReplace); + state.clipRect(50, 50, 150, 150, SkClipOp::kReplace_deprecated); ASSERT_EQ(state.getRenderTargetClipBounds(), Rect(50, 50, 150, 150)); } diff --git a/libs/hwui/tests/unit/FrameBuilderTests.cpp b/libs/hwui/tests/unit/FrameBuilderTests.cpp index a329980b7609..bd798e8dc688 100644 --- a/libs/hwui/tests/unit/FrameBuilderTests.cpp +++ b/libs/hwui/tests/unit/FrameBuilderTests.cpp @@ -506,19 +506,19 @@ RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, clippedMerging) { sk_sp<Bitmap> bitmap(TestUtils::createBitmap(20, 20)); // left side clipped (to inset left half) - canvas.clipRect(10, 0, 50, 100, SkClipOp::kReplace); + canvas.clipRect(10, 0, 50, 100, SkClipOp::kReplace_deprecated); canvas.drawBitmap(*bitmap, 0, 40, nullptr); // top side clipped (to inset top half) - canvas.clipRect(0, 10, 100, 50, SkClipOp::kReplace); + canvas.clipRect(0, 10, 100, 50, SkClipOp::kReplace_deprecated); canvas.drawBitmap(*bitmap, 40, 0, nullptr); // right side clipped (to inset right half) - canvas.clipRect(50, 0, 90, 100, SkClipOp::kReplace); + canvas.clipRect(50, 0, 90, 100, SkClipOp::kReplace_deprecated); canvas.drawBitmap(*bitmap, 80, 40, nullptr); // bottom not clipped, just abutting (inset bottom half) - canvas.clipRect(0, 50, 100, 90, SkClipOp::kReplace); + canvas.clipRect(0, 50, 100, 90, SkClipOp::kReplace_deprecated); canvas.drawBitmap(*bitmap, 40, 70, nullptr); }); @@ -2308,7 +2308,7 @@ RENDERTHREAD_OPENGL_PIPELINE_TEST(FrameBuilder, clip_replace) { }; auto node = TestUtils::createNode<RecordingCanvas>(20, 20, 30, 30, [](RenderProperties& props, RecordingCanvas& canvas) { - canvas.clipRect(0, -20, 10, 30, SkClipOp::kReplace); + canvas.clipRect(0, -20, 10, 30, SkClipOp::kReplace_deprecated); canvas.drawColor(SK_ColorWHITE, SkBlendMode::kSrcOver); }); diff --git a/libs/hwui/tests/unit/RecordingCanvasTests.cpp b/libs/hwui/tests/unit/RecordingCanvasTests.cpp index b2ea9accf63a..d36bca031a87 100644 --- a/libs/hwui/tests/unit/RecordingCanvasTests.cpp +++ b/libs/hwui/tests/unit/RecordingCanvasTests.cpp @@ -476,7 +476,7 @@ OPENGL_PIPELINE_TEST(RecordingCanvas, saveLayer_addClipFlag) { OPENGL_PIPELINE_TEST(RecordingCanvas, saveLayer_viewportCrop) { auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) { // shouldn't matter, since saveLayer will clip to its bounds - canvas.clipRect(-1000, -1000, 1000, 1000, SkClipOp::kReplace); + canvas.clipRect(-1000, -1000, 1000, 1000, SkClipOp::kReplace_deprecated); canvas.saveLayerAlpha(100, 100, 300, 300, 128, SaveFlags::ClipToLayer); canvas.drawRect(0, 0, 400, 400, SkPaint()); @@ -654,7 +654,7 @@ OPENGL_PIPELINE_TEST(RecordingCanvas, firstClipWillReplace) { OPENGL_PIPELINE_TEST(RecordingCanvas, replaceClipIntersectWithRoot) { auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 100, [](RecordingCanvas& canvas) { canvas.save(SaveFlags::MatrixClip); - canvas.clipRect(-10, -10, 110, 110, SkClipOp::kReplace); + canvas.clipRect(-10, -10, 110, 110, SkClipOp::kReplace_deprecated); canvas.drawColor(SK_ColorWHITE, SkBlendMode::kSrcOver); canvas.restore(); }); diff --git a/libs/hwui/tests/unit/SkiaPipelineTests.cpp b/libs/hwui/tests/unit/SkiaPipelineTests.cpp index 5bb0b6db06b5..79429eca08ec 100644 --- a/libs/hwui/tests/unit/SkiaPipelineTests.cpp +++ b/libs/hwui/tests/unit/SkiaPipelineTests.cpp @@ -331,7 +331,7 @@ RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, clip_replace) { std::vector<sp<RenderNode>> nodes; nodes.push_back(TestUtils::createSkiaNode(20, 20, 30, 30, [](RenderProperties& props, SkiaRecordingCanvas& canvas) { - canvas.clipRect(0, -20, 10, 30, SkClipOp::kReplace); + canvas.clipRect(0, -20, 10, 30, SkClipOp::kReplace_deprecated); canvas.drawColor(SK_ColorWHITE, SkBlendMode::kSrcOver); })); diff --git a/libs/hwui/utils/TestWindowContext.cpp b/libs/hwui/utils/TestWindowContext.cpp index ecad7bed296b..492ca7fe62fa 100644 --- a/libs/hwui/utils/TestWindowContext.cpp +++ b/libs/hwui/utils/TestWindowContext.cpp @@ -93,7 +93,7 @@ public: SkCanvas* prepareToDraw() { //mCanvas->reset(mSize.width(), mSize.height()); - mCanvas->clipRect(0, 0, mSize.width(), mSize.height(), SkClipOp::kReplace); + mCanvas->clipRect(0, 0, mSize.width(), mSize.height(), SkClipOp::kReplace_deprecated); return mCanvas->asSkCanvas(); } diff --git a/media/java/android/media/AudioRecord.java b/media/java/android/media/AudioRecord.java index 39184f1af878..0906ba50f7df 100644 --- a/media/java/android/media/AudioRecord.java +++ b/media/java/android/media/AudioRecord.java @@ -677,7 +677,7 @@ public class AudioRecord implements AudioRouting ((audioSource > MediaRecorder.getAudioSourceMax()) && (audioSource != MediaRecorder.AudioSource.RADIO_TUNER) && (audioSource != MediaRecorder.AudioSource.HOTWORD)) ) { - throw new IllegalArgumentException("Invalid audio source."); + throw new IllegalArgumentException("Invalid audio source " + audioSource); } mRecordSource = audioSource; @@ -703,8 +703,8 @@ public class AudioRecord implements AudioRouting mAudioFormat = audioFormat; break; default: - throw new IllegalArgumentException("Unsupported sample encoding." - + " Should be ENCODING_PCM_8BIT, ENCODING_PCM_16BIT, or ENCODING_PCM_FLOAT."); + throw new IllegalArgumentException("Unsupported sample encoding " + audioFormat + + ". Should be ENCODING_PCM_8BIT, ENCODING_PCM_16BIT, or ENCODING_PCM_FLOAT."); } } @@ -722,7 +722,8 @@ public class AudioRecord implements AudioRouting int frameSizeInBytes = mChannelCount * (AudioFormat.getBytesPerSample(mAudioFormat)); if ((audioBufferSize % frameSizeInBytes != 0) || (audioBufferSize < 1)) { - throw new IllegalArgumentException("Invalid audio buffer size."); + throw new IllegalArgumentException("Invalid audio buffer size " + audioBufferSize + + " (frame size " + frameSizeInBytes + ")"); } mNativeBufferSizeInBytes = audioBufferSize; diff --git a/media/java/android/media/MediaCodec.java b/media/java/android/media/MediaCodec.java index d26412789f39..13a22b49ff87 100644 --- a/media/java/android/media/MediaCodec.java +++ b/media/java/android/media/MediaCodec.java @@ -23,6 +23,7 @@ import android.graphics.ImageFormat; import android.graphics.Rect; import android.graphics.SurfaceTexture; import android.media.MediaCodecInfo.CodecCapabilities; +import android.media.MediaMetricsSet; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; @@ -3186,59 +3187,22 @@ final public class MediaCodec { public native final String getName(); /** - * Returns Analytics/Metrics data about the current content being + * Return Metrics data about the current codec instance. * - * @return a Bundle containing the set of attributes and values available - * for the media being handled by this instance of MediaCodec + * @return a MediaMetricsSet containing the set of attributes and values + * available for the media being handled by this instance of MediaCodec + * The attributes are descibed in {@link MediaMetricsSet.MediaCodec}. * - * <table style="width: 0%"> - * <thead> - * <tr> - * <th>Key</th> - * <th>Type</th> - * <th>Description</th> - * </tr> - * </thead> - * <tbody> - * <tr> - * <td>{@code "codec"}</td> - * <td>String</td> - * <td>Identifies the particular codec in use</td> - * </tr><tr> - * <td>{@code "mime"}</td> - * <td>String</td> - * <td>Mime type of the media being encoded/decoded</td> - * </tr><tr> - * <td>{@code "mode"}</td> - * <td>String</td> - * <td>"Audio" or "Video"</td> - * </tr><tr> - * <td>{@code "secure"}</td> - * <td>Integer</td> - * <td>Indicates whether the code is operating on secure content and - * may also use capabilities in android.media.MediaCrypto</td> - * </tr><tr> - * <td>{@code "height"}</td> - * <td>Integer</td> - * <td>Height (pixels); valid only when mode=video</td> - * </tr><tr> - * <td>{@code "width"}</td> - * <td>Integer</td> - * <td>Width (pixels); valid only when mode=video</td> - * </tr><tr> - * <td>{@code "rotation"}</td> - * <td>Integer</td> - * <td>rotation (degrees) to orient the video onto the target surface; - * valid only when mode=video. Note there may be additional - * rotations applied when the surface is mapped to the screen.</td> - * </tr> - * </tbody> - * </table> - * - * Additional fields specific to individual codecs will also appear in + * Additional vendor-specific fields may also be present in * the return value. */ - public native Bundle getMetrics(); + public MediaMetricsSet getMetrics() { + Bundle bundle = native_getMetrics(); + MediaMetricsSet mSet = new MediaMetricsSet(bundle); + return mSet; + } + + private native Bundle native_getMetrics(); /** * Change a video encoder's target bitrate on the fly. The value is an diff --git a/media/java/android/media/MediaExtractor.java b/media/java/android/media/MediaExtractor.java index b9e409d17748..2ed6668112ce 100644 --- a/media/java/android/media/MediaExtractor.java +++ b/media/java/android/media/MediaExtractor.java @@ -25,6 +25,7 @@ import android.content.res.AssetFileDescriptor; import android.media.MediaCodec; import android.media.MediaFormat; import android.media.MediaHTTPService; +import android.media.MediaMetricsSet; import android.net.Uri; import android.os.Bundle; import android.os.IBinder; @@ -651,41 +652,24 @@ final public class MediaExtractor { public native boolean hasCacheReachedEndOfStream(); /** - * Returns Analytics/Metrics data about the current media container. + * Return Metrics data about the current media container. * - * @return the set of keys and values available for the media being - * handled by this instance of MediaExtractor + * @return a MediaMetricsSet containing the set of attributes and values + * available for the media container being handled by this instance + * of MediaExtractor. + * The attributes are descibed in {@link MediaMetricsSet.MediaExtractor}. * - * <table style="width: 0%"> - * <thead> - * <tr> - * <th>Key</th> - * <th>Type</th> - * <th>Description</th> - * </tr> - * </thead> - * <tbody> - * <tr> - * <td>{@code "fmt"}</td> - * <td>String</td> - * <td>The container format (which determines the handler)</td> - * </tr><tr> - * <td>{@code "mime"}</td> - * <td>String</td> - * <td>Mime type of the container.</td> - * </tr><tr> - * <td>{@code "ntrk"}</td> - * <td>Integer</td> - * <td>Number of tracks in the container</td> - * </tr> - * </tbody> - * </table> - * - * Additional fields specific to individual codecs will also appear in + * Additional vendor-specific fields may also be present in * the return value. */ - public native Bundle getMetrics(); + public MediaMetricsSet getMetrics() { + Bundle bundle = native_getMetrics(); + MediaMetricsSet mSet = new MediaMetricsSet(bundle); + return mSet; + } + + private native Bundle native_getMetrics(); private static native final void native_init(); private native final void native_setup(); diff --git a/media/java/android/media/MediaMetricsSet.java b/media/java/android/media/MediaMetricsSet.java new file mode 100644 index 000000000000..5ecbee2127fc --- /dev/null +++ b/media/java/android/media/MediaMetricsSet.java @@ -0,0 +1,491 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.media; + +import android.os.Bundle; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileDescriptor; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.lang.Runnable; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.ref.WeakReference; +import java.net.HttpCookie; +import java.net.HttpURLConnection; +import java.net.InetSocketAddress; +import java.net.URL; +import java.nio.ByteOrder; +import java.util.Arrays; +import java.util.BitSet; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Scanner; +import java.util.Set; +import java.util.UUID; +import java.util.Vector; + + +/** + * MediaMetricsSet contains the results returned by the getMetrics() + * methods defined in other Media classes such as + * {@link MediaCodec}, {@link MediaExtractor}, {@link MediaPlayer}, + * and {@link MediaRecorder}. + * + * MediaMetricsSet behaves similarly to a {@link Bundle}. It contains + * a set of keys and values. + * Methods such as {@link #getInt} and {@link #getString} are provided + * to extract values of the corresponding types. + * The {@link #keySet} method can be used to discover all of the keys + * that are present in the particular instance. + * + */ +public final class MediaMetricsSet +{ + + /** + * This MediaCodec class holds the constants defining keys related to + * the metrics for a MediaCodec. + */ + public final static class MediaCodec + { + private MediaCodec() {} + + /** + * Key to extract the codec being used + * from the {@link MediaCodec#getMetrics} return value. + * The value is a String. + */ + public static final String KEY_CODEC = "android.media.mediacodec.codec"; + + /** + * Key to extract the MIME type + * from the {@link MediaCodec#getMetrics} return value. + * The value is a String. + */ + public static final String KEY_MIME = "android.media.mediacodec.mime"; + + /** + * Key to extract what the codec mode + * from the {@link MediaCodec#getMetrics} return value. + * The value is a String. Values will be one of the constants + * MODE_AUDIO or MODE_VIDEO. + */ + public static final String KEY_MODE = "android.media.mediacodec.mode"; + + /** + * The value returned for the key {@link #KEY_MODE} when the + * codec is a audio codec. + */ + public static final String MODE_AUDIO = "audio"; + + /** + * The value returned for the key {@link #KEY_MODE} when the + * codec is a video codec. + */ + public static final String MODE_VIDEO = "video"; + + /** + * Key to extract the flag indicating whether the codec is running + * as an encoder or decoder from the {@link MediaCodec#getMetrics} return value. + * The value is an integer. + * A 0 indicates decoder; 1 indicates encoder. + */ + public static final String KEY_ENCODER = "android.media.mediacodec.encoder"; + + /** + * Key to extract the flag indicating whether the codec is running + * in secure (DRM) mode from the {@link MediaCodec#getMetrics} return value. + * The value is an integer. + */ + public static final String KEY_SECURE = "android.media.mediacodec.secure"; + + /** + * Key to extract the width (in pixels) of the video track + * from the {@link MediaCodec#getMetrics} return value. + * The value is an integer. + */ + public static final String KEY_WIDTH = "android.media.mediacodec.width"; + + /** + * Key to extract the height (in pixels) of the video track + * from the {@link MediaCodec#getMetrics} return value. + * The value is an integer. + */ + public static final String KEY_HEIGHT = "android.media.mediacodec.height"; + + /** + * Key to extract the rotation (in degrees) to properly orient the video + * from the {@link MediaCodec#getMetrics} return. + * The value is a integer. + */ + public static final String KEY_ROTATION = "android.media.mediacodec.rotation"; + + } + + /** + * This class holds the constants defining keys related to + * the metrics for a MediaExtractor. + */ + public final static class MediaExtractor + { + private MediaExtractor() {} + + /** + * Key to extract the container format + * from the {@link MediaExtractor#getMetrics} return value. + * The value is a String. + */ + public static final String KEY_FORMAT = "android.media.mediaextractor.fmt"; + + /** + * Key to extract the container MIME type + * from the {@link MediaExtractor#getMetrics} return value. + * The value is a String. + */ + public static final String KEY_MIME = "android.media.mediaextractor.mime"; + + /** + * Key to extract the number of tracks in the container + * from the {@link MediaExtractor#getMetrics} return value. + * The value is an integer. + */ + public static final String KEY_TRACKS = "android.media.mediaextractor.ntrk"; + + } + + /** + * This class holds the constants defining keys related to + * the metrics for a MediaPlayer. + */ + public final static class MediaPlayer + { + private MediaPlayer() {} + + /** + * Key to extract the MIME type of the video track + * from the {@link MediaPlayer#getMetrics} return value. + * The value is a String. + */ + public static final String KEY_MIME_VIDEO = "android.media.mediaplayer.video.mime"; + + /** + * Key to extract the codec being used to decode the video track + * from the {@link MediaPlayer#getMetrics} return value. + * The value is a String. + */ + public static final String KEY_CODEC_VIDEO = "android.media.mediaplayer.video.codec"; + + /** + * Key to extract the width (in pixels) of the video track + * from the {@link MediaPlayer#getMetrics} return value. + * The value is an integer. + */ + public static final String KEY_WIDTH = "android.media.mediaplayer.width"; + + /** + * Key to extract the height (in pixels) of the video track + * from the {@link MediaPlayer#getMetrics} return value. + * The value is an integer. + */ + public static final String KEY_HEIGHT = "android.media.mediaplayer.height"; + + /** + * Key to extract the count of video frames played + * from the {@link MediaPlayer#getMetrics} return value. + * The value is an integer. + */ + public static final String KEY_FRAMES = "android.media.mediaplayer.frames"; + + /** + * Key to extract the count of video frames dropped + * from the {@link MediaPlayer#getMetrics} return value. + * The value is an integer. + */ + public static final String KEY_FRAMES_DROPPED = "android.media.mediaplayer.dropped"; + + /** + * Key to extract the MIME type of the audio track + * from the {@link MediaPlayer#getMetrics} return value. + * The value is a String. + */ + public static final String KEY_MIME_AUDIO = "android.media.mediaplayer.audio.mime"; + + /** + * Key to extract the codec being used to decode the audio track + * from the {@link MediaPlayer#getMetrics} return value. + * The value is a String. + */ + public static final String KEY_CODEC_AUDIO = "android.media.mediaplayer.audio.codec"; + + /** + * Key to extract the duration (in milliseconds) of the + * media being played + * from the {@link MediaPlayer#getMetrics} return value. + * The value is a long. + */ + public static final String KEY_DURATION = "android.media.mediaplayer.durationMs"; + + /** + * Key to extract the playing time (in milliseconds) of the + * media being played + * from the {@link MediaPlayer#getMetrics} return value. + * The value is a long. + */ + public static final String KEY_PLAYING = "android.media.mediaplayer.playingMs"; + + /** + * Key to extract the count of errors encountered while + * playing the media + * from the {@link MediaPlayer#getMetrics} return value. + * The value is an integer. + */ + public static final String KEY_ERRORS = "android.media.mediaplayer.err"; + + /** + * Key to extract an (optional) error code detected while + * playing the media + * from the {@link MediaPlayer#getMetrics} return value. + * The value is an integer. + */ + public static final String KEY_ERROR_CODE = "android.media.mediaplayer.errcode"; + + } + + /** + * This class holds the constants defining keys related to + * the metrics for a MediaRecorder. + */ + public final static class MediaRecorder + { + private MediaRecorder() {} + + /** + * Key to extract the audio bitrate + * from the {@link MediaRecorder#getMetrics} return. + * The value is an integer. + */ + public static final String KEY_AUDIO_BITRATE = "android.media.mediarecorder.audio-bitrate"; + + /** + * Key to extract the number of audio channels + * from the {@link MediaRecorder#getMetrics} return. + * The value is an integer. + */ + public static final String KEY_AUDIO_CHANNELS = "android.media.mediarecorder.audio-channels"; + + /** + * Key to extract the audio samplerate + * from the {@link MediaRecorder#getMetrics} return. + * The value is an integer. + */ + public static final String KEY_AUDIO_SAMPLERATE = "android.media.mediarecorder.audio-samplerate"; + + /** + * Key to extract the audio timescale + * from the {@link MediaRecorder#getMetrics} return. + * The value is an integer. + */ + public static final String KEY_AUDIO_TIMESCALE = "android.media.mediarecorder.audio-timescale"; + + /** + * Key to extract the video capture frame rate + * from the {@link MediaRecorder#getMetrics} return. + * The value is a double. + */ + public static final String KEY_CAPTURE_FPS = "android.media.mediarecorder.capture-fps"; + + /** + * Key to extract the video capture framerate enable value + * from the {@link MediaRecorder#getMetrics} return. + * The value is an integer. + */ + public static final String KEY_CAPTURE_FPS_ENABLE = "android.media.mediarecorder.capture-fpsenable"; + + /** + * Key to extract the intended playback frame rate + * from the {@link MediaRecorder#getMetrics} return. + * The value is an integer. + */ + public static final String KEY_FRAMERATE = "android.media.mediarecorder.frame-rate"; + + /** + * Key to extract the height (in pixels) of the captured video + * from the {@link MediaRecorder#getMetrics} return. + * The value is an integer. + */ + public static final String KEY_HEIGHT = "android.media.mediarecorder.height"; + + /** + * Key to extract the recorded movies time units + * from the {@link MediaRecorder#getMetrics} return. + * The value is an integer. + * A value of 1000 indicates that the movie's timing is in milliseconds. + */ + public static final String KEY_MOVIE_TIMESCALE = "android.media.mediarecorder.movie-timescale"; + + /** + * Key to extract the rotation (in degrees) to properly orient the video + * from the {@link MediaRecorder#getMetrics} return. + * The value is an integer. + */ + public static final String KEY_ROTATION = "android.media.mediarecorder.rotation"; + + /** + * Key to extract the video bitrate from being used + * from the {@link MediaRecorder#getMetrics} return. + * The value is an integer. + */ + public static final String KEY_VIDEO_BITRATE = "android.media.mediarecorder.video-bitrate"; + + /** + * Key to extract the value for how often video iframes are generated + * from the {@link MediaRecorder#getMetrics} return. + * The value is an integer. + */ + public static final String KEY_VIDEO_IFRAME_INTERVAL = "android.media.mediarecorder.video-iframe-interval"; + + /** + * Key to extract the video encoding level + * from the {@link MediaRecorder#getMetrics} return. + * The value is an integer. + */ + public static final String KEY_VIDEO_LEVEL = "android.media.mediarecorder.video-encoder-level"; + + /** + * Key to extract the video encoding profile + * from the {@link MediaRecorder#getMetrics} return. + * The value is an integer. + */ + public static final String KEY_VIDEO_PROFILE = "android.media.mediarecorder.video-encoder-profile"; + + /** + * Key to extract the recorded video time units + * from the {@link MediaRecorder#getMetrics} return. + * The value is an integer. + * A value of 1000 indicates that the video's timing is in milliseconds. + */ + public static final String KEY_VIDEO_TIMESCALE = "android.media.mediarecorder.video-timescale"; + + /** + * Key to extract the width (in pixels) of the captured video + * from the {@link MediaRecorder#getMetrics} return. + * The value is an integer. + */ + public static final String KEY_WIDTH = "android.media.mediarecorder.width"; + + } + + /* + * Methods that we want + */ + + private Bundle mBundle; + + MediaMetricsSet(Bundle bundle) { + mBundle = bundle; + } + + /** + * Returns the number of mappings contained in this Bundle. + * + * @return the number of mappings as an int. + */ + public int size() { + return mBundle.size(); + } + + /** + * Returns true if the mapping of this MediaMetricsSet is empty, + * false otherwise. + */ + public boolean isEmpty() { + return mBundle.isEmpty(); + } + + /** + * Returns the value associated with the given key, or defaultValue if + * no mapping of the desired type exists for the given key. + * + * @param key a String + * @param defaultValue Value to return if key does not exist + * @return a double value + */ + public double getDouble(String key, double defaultValue) { + return mBundle.getDouble(key, defaultValue); + } + + /** + * Returns the value associated with the given key, or defaultValue if + * no mapping of the desired type exists for the given key. + * + * @param key a String + * @param defaultValue Value to return if key does not exist + * @return an int value + */ + public int getInt(String key, int defaultValue) { + return mBundle.getInt(key, defaultValue); + } + + /** + * Returns the value associated with the given key, or defaultValue if + * no mapping of the desired type exists for the given key. + * + * @param key a String + * @param defaultValue Value to return if key does not exist + * @return a long value + */ + public long getLong(String key, long defaultValue) { + return mBundle.getLong(key, defaultValue); + } + + /** + * Returns the value associated with the given key, or defaultValue if + * no mapping of the desired type exists for the given key or if a null + * value is explicitly associated with the given key. + * + * @param key a String + * @param defaultValue Value to return if key does not exist or if a null + * value is associated with the given key. + * @return the String value associated with the given key, or defaultValue + * if no valid String object is currently mapped to that key. + */ + public String getString(String key, String defaultValue) { + return mBundle.getString(key, defaultValue); + } + + /** + * Returns a Set containing the Strings used as keys in this Bundle. + * + * @return a Set of String keys + */ + public Set<String> keySet() { + return mBundle.keySet(); + } + + + + public String toString() { + return mBundle.toString(); + } + +} + diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java index b85c91119384..1ee05b8c3a9a 100644 --- a/media/java/android/media/MediaPlayer.java +++ b/media/java/android/media/MediaPlayer.java @@ -49,6 +49,7 @@ import android.media.AudioManager; import android.media.BufferingParams; import android.media.MediaDrm; import android.media.MediaFormat; +import android.media.MediaMetricsSet; import android.media.MediaTimeProvider; import android.media.PlaybackParams; import android.media.SubtitleController; @@ -1494,70 +1495,22 @@ public class MediaPlayer extends PlayerBase public native int getVideoHeight(); /** - * Returns Analytics/Metrics data about the current video in this player. - * - * @return the a map of attributes and values available for this video - * player or null if no metrics are available. - * - * <table style="width: 0%"> - * <thead> - * <tr> - * <th>Key</th> - * <th>Type</th> - * <th>Description</th> - * </tr> - * </thead> - * <tbody> - * <tr> - * <td>{@code "video/codec"}</td> - * <td>String</td> - * <td>Identifies the video codec in use</td> - * </tr><tr> - * <td>{@code "video/mime"}</td> - * <td>String</td> - * <td>Mime type of the video being encoded/decoded</td> - * </tr><tr> - * <td>{@code "audio/codec"}</td> - * <td>String</td> - * <td>Identifies the audio codec in use</td> - * </tr><tr> - * <td>{@code "audio/mime"}</td> - * <td>String</td> - * <td>Mime type of the audio being encoded/decoded</td> - * </tr><tr> - * <td>{@code "ht"}</td> - * <td>Integer</td> - * <td>Height (pixels); valid only when mode=video</td> - * </tr><tr> - * <td>{@code "wid"}</td> - * <td>Integer</td> - * <td>Width (pixels); valid only when mode=video</td> - * </tr><tr> - * <td>{@code "frame"}</td> - * <td>Integer</td> - * <td>Number of decoded video frames sent to the display</td> - * </tr><tr> - * <td>{@code "dropped"}</td> - * <td>Integer</td> - * <td>Number of decoded video frames that were not sent to display. - * These frames were dropped by the player.</td> - * </tr><tr> - * <td>{@code "durationMs"}</td> - * <td>Integer</td> - * <td>The length of the media being played (in ms), e.g. "This video lasts for 30000 milliseconds". </td> - * </tr><tr> - * <td>{@code "playingMs"}</td> - * <td>Integer</td> - * <td>The time the media has been played (in ms). If you watch a - * 30 second twice through, this will report 60000 ms.</td> - * </tr> - * </tbody> - * </table> - * - * Additional fields specific to individual codecs will also appear in + * Return Metrics data about the current player. + * + * @return a MediaMetricsSet containing the set of attributes and values + * available for the media being handled by this instance of MediaPlayer + * The attributes are descibed in {@link MediaMetricsSet.MediaPlayer}. + * + * Additional vendor-specific fields may also be present in * the return value. */ - public native Bundle getMetrics(); + public MediaMetricsSet getMetrics() { + Bundle bundle = native_getMetrics(); + MediaMetricsSet mSet = new MediaMetricsSet(bundle); + return mSet; + } + + private native Bundle native_getMetrics(); /** * Checks whether the MediaPlayer is playing. diff --git a/media/java/android/media/MediaRecorder.java b/media/java/android/media/MediaRecorder.java index 075a84ff00a7..cdc1d60fa789 100644 --- a/media/java/android/media/MediaRecorder.java +++ b/media/java/android/media/MediaRecorder.java @@ -20,6 +20,7 @@ import android.annotation.NonNull; import android.annotation.SystemApi; import android.app.ActivityThread; import android.hardware.Camera; +import android.media.MediaMetricsSet; import android.os.Bundle; import android.os.Handler; import android.os.Looper; @@ -1259,91 +1260,23 @@ public class MediaRecorder private native void setParameter(String nameValuePair); /** - * Returns Metrics data about the current media container. + * Return Metrics data about the current Mediarecorder instance. * - * @return the set of keys and values available for the media being - * handled by this instance of MediaExtractor. The keys, data types, - * and meaning are described in the following table. + * @return a MediaMetricsSet containing the set of attributes and values + * available for the media being generated by this instance of + * MediaRecorder. + * The attributes are descibed in {@link MediaMetricsSet.MediaRecorder}. * - * <table style="width: 0%"> - * <thead> - * <tr> - * <th>Key</th> - * <th>Type</th> - * <th>Description</th> - * </tr> - * </thead> - * <tbody> - * <tr> - * <td>{@code "ht"}</td> - * <td>Integer</td> - * <td>Height of the recorded video (pixels)</td> - * </tr><tr> - * <td>{@code "wid"}</td> - * <td>Integer</td> - * <td>Width of the recorded video (pixels)</td> - * </tr><tr> - * <td>{@code "frame-rate"}</td> - * <td>Integer</td> - * <td>Framerate of captured Video (frames per second)</td> - * </tr><tr> - * <td>{@code "video-bitrate"}</td> - * <td>Integer</td> - * <td>Bit rate of encoded video (bits per second)</td> - * </tr><tr> - * <td>{@code "video-iframe-interval"}</td> - * <td>Integer</td> - * <td>Interval between encoded IFrames (seconds)</td> - * </tr><tr> - * <td>{@code "video-timescale"}</td> - * <td>Integer</td> - * <td></td> - * </tr><tr> - * <td>{@code "video-encoder-profile"}</td> - * <td>Integer</td> - * <td>Video Encoder Profile, as defined in OpenMAX IL</td> - * </tr><tr> - * <td>{@code "video-encoder-level"}</td> - * <td>Integer</td> - * <td>Video Encoder Level, as defined in OpenMAX IL</td> - * </tr><tr> - * <td>{@code "audio-bitrate"}</td> - * <td>Integer</td> - * <td>Bitrate of encoded audio (bits per second)</td> - * </tr><tr> - * <td>{@code "audio-samplerate"}</td> - * <td>Integer</td> - * <td></td> - * </tr><tr> - * <td>{@code "audio-channels"}</td> - * <td>Integer</td> - * <td>Number of Audio Channels Captured</td> - * </tr><tr> - * <td>{@code "audio-timescale"}</td> - * <td>Integer</td> - * <td></td> - * </tr><tr> - * <td>{@code "movie-timescale"}</td> - * <td>Integer</td> - * <td></td> - * </tr><tr> - * <td>{@code "movie-timescale"}</td> - * <td>Integer</td> - * <td></td> - * </tr><tr> - * <td>{@code "capture-fps"}</td> - * <td>Integer</td> - * <td></td> - * </tr><tr> - * <td>{@code "rotation"}</td> - * <td>Integer</td> - * <td>Orientation of the Video (degrees)</td> - * </tr> - * </tbody> - * </table> - */ - - public native Bundle getMetrics(); + * Additional vendor-specific fields may also be present in + * the return value. + */ + public MediaMetricsSet getMetrics() { + Bundle bundle = native_getMetrics(); + MediaMetricsSet mSet = new MediaMetricsSet(bundle); + return mSet; + } + + private native Bundle native_getMetrics(); @Override protected void finalize() { native_finalize(); } diff --git a/media/java/android/media/tv/TvContract.java b/media/java/android/media/tv/TvContract.java index 6e15d8da0033..62fd39516b85 100644 --- a/media/java/android/media/tv/TvContract.java +++ b/media/java/android/media/tv/TvContract.java @@ -2679,6 +2679,9 @@ public final class TvContract { /** * The ID of the TV channel that provides this TV program. * + * <p>This value cannot be changed once it's set. Trying to modify it will make the update + * fail. + * * <p>This is a part of the channel URI and matches to {@link BaseColumns#_ID}. * * <p>This is a required field. diff --git a/media/java/android/media/tv/TvInputHardwareInfo.java b/media/java/android/media/tv/TvInputHardwareInfo.java index 51fa036a77c4..957c5820838e 100644 --- a/media/java/android/media/tv/TvInputHardwareInfo.java +++ b/media/java/android/media/tv/TvInputHardwareInfo.java @@ -16,11 +16,15 @@ package android.media.tv; +import static java.lang.annotation.RetentionPolicy.SOURCE; + +import android.annotation.IntDef; import android.annotation.SystemApi; import android.media.AudioManager; import android.os.Parcel; import android.os.Parcelable; import android.util.Log; +import java.lang.annotation.Retention; /** * Simple container for information about TV input hardware. @@ -44,6 +48,28 @@ public final class TvInputHardwareInfo implements Parcelable { public static final int TV_INPUT_TYPE_HDMI = 9; public static final int TV_INPUT_TYPE_DISPLAY_PORT = 10; + /** @hide */ + @Retention(SOURCE) + @IntDef({CABLE_CONNECTION_STATUS_UNKNOWN, CABLE_CONNECTION_STATUS_CONNECTED, + CABLE_CONNECTION_STATUS_DISCONNECTED}) + public @interface CableConnectionStatus {} + + // Match hardware/interfaces/tv/input/1.0/types.hal + /** + * The hardware is unsure about the connection status or does not support cable detection. + */ + public static final int CABLE_CONNECTION_STATUS_UNKNOWN = 0; + + /** + * Cable is connected to the hardware. + */ + public static final int CABLE_CONNECTION_STATUS_CONNECTED = 1; + + /** + * Cable is disconnected to the hardware. + */ + public static final int CABLE_CONNECTION_STATUS_DISCONNECTED = 2; + public static final Parcelable.Creator<TvInputHardwareInfo> CREATOR = new Parcelable.Creator<TvInputHardwareInfo>() { @Override @@ -69,6 +95,8 @@ public final class TvInputHardwareInfo implements Parcelable { private int mAudioType; private String mAudioAddress; private int mHdmiPortId; + @CableConnectionStatus + private int mCableConnectionStatus; private TvInputHardwareInfo() { } @@ -96,6 +124,19 @@ public final class TvInputHardwareInfo implements Parcelable { return mHdmiPortId; } + /** + * Gets the cable connection status of the hardware. + * + * @return {@code CABLE_CONNECTION_STATUS_CONNECTED} if cable is connected. + * {@code CABLE_CONNECTION_STATUS_DISCONNECTED} if cable is disconnected. + * {@code CABLE_CONNECTION_STATUS_UNKNOWN} if the hardware is unsure about the + * connection status or does not support cable detection. + */ + @CableConnectionStatus + public int getCableConnectionStatus() { + return mCableConnectionStatus; + } + @Override public String toString() { StringBuilder b = new StringBuilder(128); @@ -106,6 +147,7 @@ public final class TvInputHardwareInfo implements Parcelable { if (mType == TV_INPUT_TYPE_HDMI) { b.append(", hdmi_port=").append(mHdmiPortId); } + b.append(", cable_connection_status=").append(mCableConnectionStatus); b.append("}"); return b.toString(); } @@ -125,6 +167,7 @@ public final class TvInputHardwareInfo implements Parcelable { if (mType == TV_INPUT_TYPE_HDMI) { dest.writeInt(mHdmiPortId); } + dest.writeInt(mCableConnectionStatus); } public void readFromParcel(Parcel source) { @@ -135,6 +178,7 @@ public final class TvInputHardwareInfo implements Parcelable { if (mType == TV_INPUT_TYPE_HDMI) { mHdmiPortId = source.readInt(); } + mCableConnectionStatus = source.readInt(); } public static final class Builder { @@ -143,6 +187,7 @@ public final class TvInputHardwareInfo implements Parcelable { private int mAudioType = AudioManager.DEVICE_NONE; private String mAudioAddress = ""; private Integer mHdmiPortId = null; + private Integer mCableConnectionStatus = CABLE_CONNECTION_STATUS_UNKNOWN; public Builder() { } @@ -172,6 +217,14 @@ public final class TvInputHardwareInfo implements Parcelable { return this; } + /** + * Sets cable connection status. + */ + public Builder cableConnectionStatus(@CableConnectionStatus int cableConnectionStatus) { + mCableConnectionStatus = cableConnectionStatus; + return this; + } + public TvInputHardwareInfo build() { if (mDeviceId == null || mType == null) { throw new UnsupportedOperationException(); @@ -191,6 +244,7 @@ public final class TvInputHardwareInfo implements Parcelable { if (mHdmiPortId != null) { info.mHdmiPortId = mHdmiPortId; } + info.mCableConnectionStatus = mCableConnectionStatus; return info; } } diff --git a/media/java/android/media/tv/TvInputInfo.java b/media/java/android/media/tv/TvInputInfo.java index 35988d40fe5d..a292b8e4f81e 100644 --- a/media/java/android/media/tv/TvInputInfo.java +++ b/media/java/android/media/tv/TvInputInfo.java @@ -138,7 +138,6 @@ public final class TvInputInfo implements Parcelable { // Attributes from XML meta data. private final String mSetupActivity; - private final String mSettingsActivity; private final boolean mCanRecord; private final int mTunerCount; @@ -259,9 +258,8 @@ public final class TvInputInfo implements Parcelable { private TvInputInfo(ResolveInfo service, String id, int type, boolean isHardwareInput, CharSequence label, int labelResId, Icon icon, Icon iconStandby, Icon iconDisconnected, - String setupActivity, String settingsActivity, boolean canRecord, int tunerCount, - HdmiDeviceInfo hdmiDeviceInfo, boolean isConnectedToHdmiSwitch, String parentId, - Bundle extras) { + String setupActivity, boolean canRecord, int tunerCount, HdmiDeviceInfo hdmiDeviceInfo, + boolean isConnectedToHdmiSwitch, String parentId, Bundle extras) { mService = service; mId = id; mType = type; @@ -272,7 +270,6 @@ public final class TvInputInfo implements Parcelable { mIconStandby = iconStandby; mIconDisconnected = iconDisconnected; mSetupActivity = setupActivity; - mSettingsActivity = settingsActivity; mCanRecord = canRecord; mTunerCount = tunerCount; mHdmiDeviceInfo = hdmiDeviceInfo; @@ -340,14 +337,12 @@ public final class TvInputInfo implements Parcelable { /** * Returns an intent to start the settings activity for this TV input. + * + * @deprecated Use {@link #createSetupIntent()} instead. Settings activity is deprecated. + * Use setup activity instead to provide settings. */ + @Deprecated public Intent createSettingsIntent() { - if (!TextUtils.isEmpty(mSettingsActivity)) { - Intent intent = new Intent(Intent.ACTION_MAIN); - intent.setClassName(mService.serviceInfo.packageName, mSettingsActivity); - intent.putExtra(EXTRA_INPUT_ID, getId()); - return intent; - } return null; } @@ -554,7 +549,6 @@ public final class TvInputInfo implements Parcelable { && Objects.equals(mIconStandby, obj.mIconStandby) && Objects.equals(mIconDisconnected, obj.mIconDisconnected) && TextUtils.equals(mSetupActivity, obj.mSetupActivity) - && TextUtils.equals(mSettingsActivity, obj.mSettingsActivity) && mCanRecord == obj.mCanRecord && mTunerCount == obj.mTunerCount && Objects.equals(mHdmiDeviceInfo, obj.mHdmiDeviceInfo) @@ -589,7 +583,6 @@ public final class TvInputInfo implements Parcelable { dest.writeParcelable(mIconStandby, flags); dest.writeParcelable(mIconDisconnected, flags); dest.writeString(mSetupActivity); - dest.writeString(mSettingsActivity); dest.writeByte(mCanRecord ? (byte) 1 : 0); dest.writeInt(mTunerCount); dest.writeParcelable(mHdmiDeviceInfo, flags); @@ -631,7 +624,6 @@ public final class TvInputInfo implements Parcelable { mIconStandby = in.readParcelable(null); mIconDisconnected = in.readParcelable(null); mSetupActivity = in.readString(); - mSettingsActivity = in.readString(); mCanRecord = in.readByte() == 1; mTunerCount = in.readInt(); mHdmiDeviceInfo = in.readParcelable(null); @@ -678,7 +670,6 @@ public final class TvInputInfo implements Parcelable { private Icon mIconStandby; private Icon mIconDisconnected; private String mSetupActivity; - private String mSettingsActivity; private Boolean mCanRecord; private Integer mTunerCount; private TvInputHardwareInfo mTvInputHardwareInfo; @@ -906,7 +897,7 @@ public final class TvInputInfo implements Parcelable { } parseServiceMetadata(type); return new TvInputInfo(mResolveInfo, id, type, isHardwareInput, mLabel, mLabelResId, - mIcon, mIconStandby, mIconDisconnected, mSetupActivity, mSettingsActivity, + mIcon, mIconStandby, mIconDisconnected, mSetupActivity, mCanRecord == null ? false : mCanRecord, mTunerCount == null ? 0 : mTunerCount, mHdmiDeviceInfo, isConnectedToHdmiSwitch, mParentId, mExtras); } @@ -961,8 +952,6 @@ public final class TvInputInfo implements Parcelable { if (inputType == TYPE_TUNER && TextUtils.isEmpty(mSetupActivity)) { throw new IllegalStateException("Setup activity not found for " + si.name); } - mSettingsActivity = sa.getString( - com.android.internal.R.styleable.TvInputService_settingsActivity); if (mCanRecord == null) { mCanRecord = sa.getBoolean( com.android.internal.R.styleable.TvInputService_canRecord, false); diff --git a/media/java/android/media/tv/TvInputManager.java b/media/java/android/media/tv/TvInputManager.java index 1eae8db60833..09b2050b1b1a 100644 --- a/media/java/android/media/tv/TvInputManager.java +++ b/media/java/android/media/tv/TvInputManager.java @@ -224,9 +224,8 @@ public final class TvInputManager { * {@link TvInputCallback#onInputStateChanged(String, int)}: The input source is connected. * * <p>This state indicates that a source device is connected to the input port and is in the - * normal operation mode. It is mostly relevant to hardware inputs such as HDMI input. This is - * the default state for any hardware inputs where their states are unknown. Non-hardware inputs - * are considered connected all the time. + * normal operation mode. It is mostly relevant to hardware inputs such as HDMI input. + * Non-hardware inputs are considered connected all the time. */ public static final int INPUT_STATE_CONNECTED = 0; @@ -236,7 +235,8 @@ public final class TvInputManager { * in standby mode. * * <p>This state indicates that a source device is connected to the input port but is in standby - * mode. It is mostly relevant to hardware inputs such as HDMI input. + * or low power mode. It is mostly relevant to hardware inputs such as HDMI input and Component + * inputs. */ public static final int INPUT_STATE_CONNECTED_STANDBY = 1; diff --git a/media/java/android/media/tv/TvInputService.java b/media/java/android/media/tv/TvInputService.java index 6f44e6d6e58b..e51025f43ea5 100644 --- a/media/java/android/media/tv/TvInputService.java +++ b/media/java/android/media/tv/TvInputService.java @@ -994,7 +994,9 @@ public abstract class TvInputService extends Service { * * <p>The current position for time shifting is the same as the current position of * playback. It should be equal to or greater than the start position reported by - * {@link #onTimeShiftGetStartPosition()}. + * {@link #onTimeShiftGetStartPosition()}. When playback is completed, the current position + * should stay where the playback ends, in other words, the returned value of this mehtod + * should be equal to the start position plus the duration of the program. * * @see #onTimeShiftPlay(Uri) * @see #onTimeShiftResume() diff --git a/media/jni/android_media_MediaCodec.cpp b/media/jni/android_media_MediaCodec.cpp index 293e5dd746da..a8dd3133b275 100644 --- a/media/jni/android_media_MediaCodec.cpp +++ b/media/jni/android_media_MediaCodec.cpp @@ -624,7 +624,7 @@ status_t JMediaCodec::getName(JNIEnv *env, jstring *nameStr) const { return OK; } -status_t JMediaCodec::getMetrics(JNIEnv *, Parcel *reply) const { +status_t JMediaCodec::getMetrics(JNIEnv *, MediaAnalyticsItem * &reply) const { status_t status = mCodec->getMetrics(reply); return status; @@ -1666,9 +1666,9 @@ static jobject android_media_MediaCodec_getName( } static jobject -android_media_MediaCodec_getMetrics(JNIEnv *env, jobject thiz) +android_media_MediaCodec_native_getMetrics(JNIEnv *env, jobject thiz) { - ALOGV("android_media_MediaCodec_getMetrics"); + ALOGV("android_media_MediaCodec_native_getMetrics"); sp<JMediaCodec> codec = getMediaCodec(env, thiz); if (codec == NULL ) { @@ -1677,16 +1677,14 @@ android_media_MediaCodec_getMetrics(JNIEnv *env, jobject thiz) } // get what we have for the metrics from the codec - Parcel reply; - status_t err = codec->getMetrics(env, &reply); + MediaAnalyticsItem *item = NULL; + + status_t err = codec->getMetrics(env, item); if (err != OK) { ALOGE("getMetrics failed"); return (jobject) NULL; } - // build and return the Bundle - MediaAnalyticsItem *item = new MediaAnalyticsItem; - item->readFromParcel(reply); jobject mybundle = MediaMetricsJNI::writeMetricsToBundle(env, item, NULL); // housekeeping @@ -2004,8 +2002,8 @@ static const JNINativeMethod gMethods[] = { { "getName", "()Ljava/lang/String;", (void *)android_media_MediaCodec_getName }, - { "getMetrics", "()Landroid/os/Bundle;", - (void *)android_media_MediaCodec_getMetrics}, + { "native_getMetrics", "()Landroid/os/Bundle;", + (void *)android_media_MediaCodec_native_getMetrics}, { "setParameters", "([Ljava/lang/String;[Ljava/lang/Object;)V", (void *)android_media_MediaCodec_setParameters }, diff --git a/media/jni/android_media_MediaCodec.h b/media/jni/android_media_MediaCodec.h index a8c76c5e61f7..c9a1700215a4 100644 --- a/media/jni/android_media_MediaCodec.h +++ b/media/jni/android_media_MediaCodec.h @@ -19,6 +19,7 @@ #include "jni.h" +#include <media/MediaAnalyticsItem.h> #include <media/hardware/CryptoAPI.h> #include <media/stagefright/foundation/ABase.h> #include <media/stagefright/foundation/AHandler.h> @@ -116,7 +117,7 @@ struct JMediaCodec : public AHandler { status_t getName(JNIEnv *env, jstring *name) const; - status_t getMetrics(JNIEnv *env, Parcel *reply) const; + status_t getMetrics(JNIEnv *env, MediaAnalyticsItem * &reply) const; status_t setParameters(const sp<AMessage> ¶ms); diff --git a/media/jni/android_media_MediaExtractor.cpp b/media/jni/android_media_MediaExtractor.cpp index 3c3349359e82..c2cfed9bfa2f 100644 --- a/media/jni/android_media_MediaExtractor.cpp +++ b/media/jni/android_media_MediaExtractor.cpp @@ -811,9 +811,9 @@ static void android_media_MediaExtractor_native_finalize( } static jobject -android_media_MediaExtractor_getMetrics(JNIEnv * env, jobject thiz) +android_media_MediaExtractor_native_getMetrics(JNIEnv * env, jobject thiz) { - ALOGV("android_media_MediaExtractor_getMetrics"); + ALOGV("android_media_MediaExtractor_native_getMetrics"); sp<JMediaExtractor> extractor = getMediaExtractor(env, thiz); if (extractor == NULL ) { @@ -905,8 +905,8 @@ static const JNINativeMethod gMethods[] = { { "hasCacheReachedEndOfStream", "()Z", (void *)android_media_MediaExtractor_hasCacheReachedEOS }, - {"getMetrics", "()Landroid/os/Bundle;", - (void *)android_media_MediaExtractor_getMetrics}, + {"native_getMetrics", "()Landroid/os/Bundle;", + (void *)android_media_MediaExtractor_native_getMetrics}, }; int register_android_media_MediaExtractor(JNIEnv *env) { diff --git a/media/jni/android_media_MediaPlayer.cpp b/media/jni/android_media_MediaPlayer.cpp index 27724a185c36..1b52cf58cbff 100644 --- a/media/jni/android_media_MediaPlayer.cpp +++ b/media/jni/android_media_MediaPlayer.cpp @@ -708,7 +708,7 @@ android_media_MediaPlayer_getVideoHeight(JNIEnv *env, jobject thiz) } static jobject -android_media_MediaPlayer_getMetrics(JNIEnv *env, jobject thiz) +android_media_MediaPlayer_native_getMetrics(JNIEnv *env, jobject thiz) { sp<MediaPlayer> mp = getMediaPlayer(env, thiz); if (mp == NULL ) { @@ -1393,7 +1393,7 @@ static const JNINativeMethod gMethods[] = { {"_stop", "()V", (void *)android_media_MediaPlayer_stop}, {"getVideoWidth", "()I", (void *)android_media_MediaPlayer_getVideoWidth}, {"getVideoHeight", "()I", (void *)android_media_MediaPlayer_getVideoHeight}, - {"getMetrics", "()Landroid/os/Bundle;", (void *)android_media_MediaPlayer_getMetrics}, + {"native_getMetrics", "()Landroid/os/Bundle;", (void *)android_media_MediaPlayer_native_getMetrics}, {"setPlaybackParams", "(Landroid/media/PlaybackParams;)V", (void *)android_media_MediaPlayer_setPlaybackParams}, {"getPlaybackParams", "()Landroid/media/PlaybackParams;", (void *)android_media_MediaPlayer_getPlaybackParams}, {"setSyncParams", "(Landroid/media/SyncParams;)V", (void *)android_media_MediaPlayer_setSyncParams}, diff --git a/media/jni/android_media_MediaRecorder.cpp b/media/jni/android_media_MediaRecorder.cpp index 77544eb736b5..7a63e003875a 100644 --- a/media/jni/android_media_MediaRecorder.cpp +++ b/media/jni/android_media_MediaRecorder.cpp @@ -628,9 +628,9 @@ void android_media_MediaRecorder_setInputSurface( } static jobject -android_media_MediaRecorder_getMetrics(JNIEnv *env, jobject thiz) +android_media_MediaRecorder_native_getMetrics(JNIEnv *env, jobject thiz) { - ALOGV("android_media_MediaRecorder_getMetrics"); + ALOGV("android_media_MediaRecorder_native_getMetrics"); sp<MediaRecorder> mr = getMediaRecorder(env, thiz); if (mr == NULL) { @@ -688,7 +688,7 @@ static const JNINativeMethod gMethods[] = { {"native_finalize", "()V", (void *)android_media_MediaRecorder_native_finalize}, {"native_setInputSurface", "(Landroid/view/Surface;)V", (void *)android_media_MediaRecorder_setInputSurface }, - {"getMetrics", "()Landroid/os/Bundle;", (void *)android_media_MediaRecorder_getMetrics}, + {"native_getMetrics", "()Landroid/os/Bundle;", (void *)android_media_MediaRecorder_native_getMetrics}, }; // This function only registers the native methods, and is called from diff --git a/media/mca/filterfw/jni/jni_util.h b/media/mca/filterfw/jni/jni_util.h index 803ed29bdf33..9b57958c4473 100644 --- a/media/mca/filterfw/jni/jni_util.h +++ b/media/mca/filterfw/jni/jni_util.h @@ -198,7 +198,8 @@ class ObjectPool { CObjMap objects_; FlagMap owns_; - DISALLOW_COPY_AND_ASSIGN(ObjectPool); + ObjectPool(const ObjectPool&) = delete; + ObjectPool& operator=(const ObjectPool&) = delete; }; template<typename T> ObjectPool<T>* ObjectPool<T>::instance_ = NULL; diff --git a/media/mca/filterfw/native/Android.mk b/media/mca/filterfw/native/Android.mk index 2e900fe29e38..feaefcbad6c5 100644 --- a/media/mca/filterfw/native/Android.mk +++ b/media/mca/filterfw/native/Android.mk @@ -41,7 +41,11 @@ LOCAL_EXPORT_LDLIBS := -llog -lgcc LOCAL_CFLAGS += -Wall -Werror -Wunused -Wunreachable-code -LOCAL_STATIC_LIBRARIES := libarect +LOCAL_STATIC_LIBRARIES := \ + libarect \ + +LOCAL_SHARED_LIBRARIES += \ + libgui \ # TODO: Build a shared library as well? include $(BUILD_STATIC_LIBRARY) diff --git a/media/mca/filterfw/native/base/utilities.h b/media/mca/filterfw/native/base/utilities.h index 6bb3b7f7c9cf..2818f7222dd3 100644 --- a/media/mca/filterfw/native/base/utilities.h +++ b/media/mca/filterfw/native/base/utilities.h @@ -23,18 +23,6 @@ namespace android { namespace filterfw { -// Convenience Macro to make copy constructor and assignment operator private -// (thereby disallowing copying and assigning). -#define DISALLOW_COPY_AND_ASSIGN(TypeName) \ - TypeName(const TypeName&); \ - void operator=(const TypeName&) - -// A macro to disallow all the implicit constructors, namely the -// default constructor, copy constructor and operator= functions. -#define DISALLOW_IMPLICIT_CONSTRUCTORS(TypeName) \ - TypeName(); \ - DISALLOW_COPY_AND_ASSIGN(TypeName) - // STLDeleteContainerPointers() // For a range within a container of pointers, calls delete // (non-array version) on these pointers. diff --git a/media/mca/filterfw/native/core/gl_env.h b/media/mca/filterfw/native/core/gl_env.h index 04453016dc93..6af91af6f6c1 100644 --- a/media/mca/filterfw/native/core/gl_env.h +++ b/media/mca/filterfw/native/core/gl_env.h @@ -256,7 +256,8 @@ class GLEnv { std::map<int, ShaderProgram*> attached_shaders_; std::map<int, VertexFrame*> attached_vframes_; - DISALLOW_COPY_AND_ASSIGN(GLEnv); + GLEnv(const GLEnv&) = delete; + GLEnv& operator=(const GLEnv&) = delete; }; } // namespace filterfw diff --git a/media/mca/filterfw/native/core/native_frame.h b/media/mca/filterfw/native/core/native_frame.h index 2da557dec000..fd5216542910 100644 --- a/media/mca/filterfw/native/core/native_frame.h +++ b/media/mca/filterfw/native/core/native_frame.h @@ -76,7 +76,8 @@ class NativeFrame { // Capacity of data buffer in bytes. int capacity_; - DISALLOW_COPY_AND_ASSIGN(NativeFrame); + NativeFrame(const NativeFrame&) = delete; + NativeFrame& operator=(const NativeFrame&) = delete; }; } // namespace filterfw diff --git a/media/mca/filterfw/native/core/native_program.h b/media/mca/filterfw/native/core/native_program.h index ce704af67aa8..e39afc9931ae 100644 --- a/media/mca/filterfw/native/core/native_program.h +++ b/media/mca/filterfw/native/core/native_program.h @@ -75,7 +75,8 @@ class NativeProgram { // Pointer to user data void* user_data_; - DISALLOW_COPY_AND_ASSIGN(NativeProgram); + NativeProgram(const NativeProgram&) = delete; + NativeProgram& operator=(const NativeProgram&) = delete; }; } // namespace filterfw diff --git a/packages/CarrierDefaultApp/AndroidManifest.xml b/packages/CarrierDefaultApp/AndroidManifest.xml index 2e642ec63cac..8df194c11800 100644 --- a/packages/CarrierDefaultApp/AndroidManifest.xml +++ b/packages/CarrierDefaultApp/AndroidManifest.xml @@ -25,7 +25,6 @@ <uses-permission android:name="android.permission.MODIFY_PHONE_STATE" /> <uses-permission android:name="android.permission.READ_PRIVILEGED_PHONE_STATE" /> <uses-permission android:name="android.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS" /> - <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" /> <uses-permission android:name="android.permission.SUBSTITUTE_NOTIFICATION_APP_NAME" /> <application android:label="@string/app_name" > @@ -34,10 +33,16 @@ <action android:name="com.android.internal.telephony.CARRIER_SIGNAL_REDIRECTED" /> </intent-filter> </receiver> - <activity android:name="com.android.carrierdefaultapp.CaptivePortalLaunchActivity" - android:theme="@android:style/Theme.Translucent.NoTitleBar" - android:excludeFromRecents="true"/> <service android:name="com.android.carrierdefaultapp.ProvisionObserver" android:permission="android.permission.BIND_JOB_SERVICE"/> + <activity + android:name="com.android.carrierdefaultapp.CaptivePortalLoginActivity" + android:label="@string/action_bar_label" + android:theme="@style/AppTheme" + android:configChanges="keyboardHidden|orientation|screenSize" > + <intent-filter> + <category android:name="android.intent.category.DEFAULT"/> + </intent-filter> + </activity> </application> </manifest> diff --git a/packages/CarrierDefaultApp/assets/quantum_ic_warning_amber_96.png b/packages/CarrierDefaultApp/assets/quantum_ic_warning_amber_96.png Binary files differnew file mode 100644 index 000000000000..08294cee4587 --- /dev/null +++ b/packages/CarrierDefaultApp/assets/quantum_ic_warning_amber_96.png diff --git a/packages/CarrierDefaultApp/res/drawable/ic_sim_card.xml b/packages/CarrierDefaultApp/res/drawable/ic_sim_card.xml index dc54fe2a3fac..75aa40522a8e 100644 --- a/packages/CarrierDefaultApp/res/drawable/ic_sim_card.xml +++ b/packages/CarrierDefaultApp/res/drawable/ic_sim_card.xml @@ -22,4 +22,4 @@ <path android:fillColor="#757575" android:pathData="M18,2h-8L4.02,8 4,20c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2L20,4c0,-1.1 -0.9,-2 -2,-2zM13,17h-2v-2h2v2zM13,13h-2L11,8h2v5z"/> -</vector>
\ No newline at end of file +</vector> diff --git a/packages/CarrierDefaultApp/res/layout/activity_captive_portal_login.xml b/packages/CarrierDefaultApp/res/layout/activity_captive_portal_login.xml new file mode 100644 index 000000000000..528576b57e5a --- /dev/null +++ b/packages/CarrierDefaultApp/res/layout/activity_captive_portal_login.xml @@ -0,0 +1,34 @@ +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:id="@+id/container" + android:layout_width="match_parent" + android:layout_height="match_parent" + tools:context="com.android.carrierdefaultapp.CaptivePortalLoginActivity" + tools:ignore="MergeRootFrame"> + <LinearLayout + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical" > + + <TextView + android:id="@+id/url_bar" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:textSize="20sp" + android:singleLine="true" /> + + <ProgressBar + android:id="@+id/progress_bar" + android:layout_width="match_parent" + android:layout_height="wrap_content" + style="?android:attr/progressBarStyleHorizontal" /> + + <WebView + android:id="@+id/webview" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_alignParentBottom="false" + android:layout_alignParentRight="false" /> + +</LinearLayout> +</FrameLayout> diff --git a/packages/CarrierDefaultApp/res/values/dimens.xml b/packages/CarrierDefaultApp/res/values/dimens.xml index a3c5049bfd8b..1ea8c351293d 100644 --- a/packages/CarrierDefaultApp/res/values/dimens.xml +++ b/packages/CarrierDefaultApp/res/values/dimens.xml @@ -1,3 +1,6 @@ <resources> <dimen name="glif_icon_size">32dp</dimen> + <!-- Default screen margins, per the Android Design guidelines. --> + <dimen name="activity_horizontal_margin">16dp</dimen> + <dimen name="activity_vertical_margin">16dp</dimen> </resources> diff --git a/packages/CarrierDefaultApp/res/values/strings.xml b/packages/CarrierDefaultApp/res/values/strings.xml index fe5669d79077..f9342b7c5899 100644 --- a/packages/CarrierDefaultApp/res/values/strings.xml +++ b/packages/CarrierDefaultApp/res/values/strings.xml @@ -1,14 +1,13 @@ <?xml version="1.0" encoding="utf-8"?> <resources> <string name="app_name">CarrierDefaultApp</string> - <string name="android_system_label">Android System</string> + <string name="android_system_label">Mobile Carrier</string> <string name="portal_notification_id">Mobile data has run out</string> - <string name="no_data_notification_id">No Mobile data service</string> - <string name="portal_notification_detail">Tap to add funds to your %s SIM</string> + <string name="no_data_notification_id">Your mobile data has been deactivated</string> + <string name="portal_notification_detail">Tap to visit the %s website</string> <string name="no_data_notification_detail">Please contact your service provider %s</string> - <string name="progress_dialogue_network_connection">Connecting to captive portal...</string> - <string name="alert_dialogue_network_timeout">Network timeout, would you like to retry?</string> - <string name="alert_dialogue_network_timeout_title">Network unavailable</string> - <string name="quit">Quit</string> - <string name="wait">Wait</string> + <string name="action_bar_label">Sign in to mobile network</string> + <string name="ssl_error_warning">The network you’re trying to join has security issues.</string> + <string name="ssl_error_example">For example, the login page may not belong to the organization shown.</string> + <string name="ssl_error_continue">Continue anyway via browser</string> </resources> diff --git a/packages/CarrierDefaultApp/res/values/styles.xml b/packages/CarrierDefaultApp/res/values/styles.xml index 3d2691505f56..939c1aa4c5da 100644 --- a/packages/CarrierDefaultApp/res/values/styles.xml +++ b/packages/CarrierDefaultApp/res/values/styles.xml @@ -1,3 +1,16 @@ <resources> - <style name="AlertDialog" parent="android:Theme.Material.Light.Dialog.Alert"/> + <style name="AppBaseTheme" parent="@android:style/Theme.Material.Settings"> + <!-- + Theme customizations available in newer API levels can go in + res/values-vXX/styles.xml, while customizations related to + backward-compatibility can go here. + --> + </style> + + <!-- Application theme. --> + <style name="AppTheme" parent="AppBaseTheme"> + <!-- All customizations that are NOT specific to a particular API-level can go here. --> + <!-- Setting's theme's accent color makes ProgressBar useless, reset back. --> + <item name="android:colorAccent">@*android:color/material_deep_teal_500</item> + </style> </resources> diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CaptivePortalLaunchActivity.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CaptivePortalLaunchActivity.java deleted file mode 100644 index 28251cb394e7..000000000000 --- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CaptivePortalLaunchActivity.java +++ /dev/null @@ -1,233 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.carrierdefaultapp; - -import android.app.Activity; -import android.app.AlertDialog; -import android.app.ProgressDialog; -import android.content.DialogInterface; -import android.content.Intent; -import android.net.CaptivePortal; -import android.net.ConnectivityManager; -import android.net.Network; -import android.net.NetworkCapabilities; -import android.net.NetworkInfo; -import android.net.NetworkRequest; -import android.os.Bundle; -import android.telephony.CarrierConfigManager; -import android.telephony.Rlog; -import android.telephony.SubscriptionManager; -import android.text.TextUtils; -import android.net.ICaptivePortal; -import android.view.ContextThemeWrapper; -import android.view.WindowManager; -import com.android.carrierdefaultapp.R; -import com.android.internal.telephony.PhoneConstants; -import com.android.internal.telephony.TelephonyIntents; -import com.android.internal.util.ArrayUtils; - -import static android.net.CaptivePortal.APP_RETURN_DISMISSED; - -/** - * Activity that launches in response to the captive portal notification - * @see com.android.carrierdefaultapp.CarrierActionUtils#CARRIER_ACTION_SHOW_PORTAL_NOTIFICATION - * This activity requests network connection if there is no available one, launches the - * {@link com.android.captiveportallogin portalApp} and keeps track of the portal activation result. - */ -public class CaptivePortalLaunchActivity extends Activity { - private static final String TAG = CaptivePortalLaunchActivity.class.getSimpleName(); - private static final boolean DBG = true; - public static final int NETWORK_REQUEST_TIMEOUT_IN_MS = 5 * 1000; - - private ConnectivityManager mCm = null; - private ConnectivityManager.NetworkCallback mCb = null; - /* Progress dialogue when request network connection for captive portal */ - private AlertDialog mProgressDialog = null; - /* Alert dialogue when network request is timeout */ - private AlertDialog mAlertDialog = null; - - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - mCm = ConnectivityManager.from(this); - // Check network connection before loading portal - Network network = getNetworkForCaptivePortal(); - NetworkInfo nwInfo = mCm.getNetworkInfo(network); - if (nwInfo == null || !nwInfo.isConnected()) { - if (DBG) logd("Network unavailable, request restricted connection"); - requestNetwork(getIntent()); - } else { - launchCaptivePortal(getIntent(), network); - } - } - - // show progress dialog during network connecting - private void showConnectingProgressDialog() { - mProgressDialog = new ProgressDialog(getApplicationContext()); - mProgressDialog.setMessage(getString(R.string.progress_dialogue_network_connection)); - mProgressDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY); - mProgressDialog.show(); - } - - // if network request is timeout, show alert dialog with two option: cancel & wait - private void showConnectionTimeoutAlertDialog() { - mAlertDialog = new AlertDialog.Builder(new ContextThemeWrapper(this, R.style.AlertDialog)) - .setMessage(getString(R.string.alert_dialogue_network_timeout)) - .setTitle(getString(R.string.alert_dialogue_network_timeout_title)) - .setNegativeButton(getString(R.string.quit), - new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - // cancel - dismissDialog(mAlertDialog); - finish(); - } - }) - .setPositiveButton(getString(R.string.wait), - new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - // wait, request network again - dismissDialog(mAlertDialog); - requestNetwork(getIntent()); - } - }) - .create(); - mAlertDialog.show(); - } - - private void requestNetwork(final Intent intent) { - NetworkRequest request = new NetworkRequest.Builder() - .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR) - .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) - .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED) - .build(); - - mCb = new ConnectivityManager.NetworkCallback() { - @Override - public void onAvailable(Network network) { - if (DBG) logd("Network available: " + network); - dismissDialog(mProgressDialog); - mCm.bindProcessToNetwork(network); - launchCaptivePortal(intent, network); - } - - @Override - public void onUnavailable() { - if (DBG) logd("Network unavailable"); - dismissDialog(mProgressDialog); - showConnectionTimeoutAlertDialog(); - } - }; - showConnectingProgressDialog(); - mCm.requestNetwork(request, mCb, NETWORK_REQUEST_TIMEOUT_IN_MS); - } - - private void releaseNetworkRequest() { - logd("release Network Request"); - if (mCb != null) { - mCm.unregisterNetworkCallback(mCb); - mCb = null; - } - } - - private void dismissDialog(AlertDialog dialog) { - if (dialog != null) { - dialog.dismiss(); - } - } - - private Network getNetworkForCaptivePortal() { - Network[] info = mCm.getAllNetworks(); - if (!ArrayUtils.isEmpty(info)) { - for (Network nw : info) { - final NetworkCapabilities nc = mCm.getNetworkCapabilities(nw); - if (nc.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) - && nc.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)) { - return nw; - } - } - } - return null; - } - - private void launchCaptivePortal(final Intent intent, Network network) { - String redirectUrl = intent.getStringExtra(TelephonyIntents.EXTRA_REDIRECTION_URL_KEY); - int subId = intent.getIntExtra(PhoneConstants.SUBSCRIPTION_KEY, - SubscriptionManager.getDefaultVoiceSubscriptionId()); - if (TextUtils.isEmpty(redirectUrl) || !matchUrl(redirectUrl, subId)) { - loge("Launch portal fails due to incorrect redirection URL: " + - Rlog.pii(TAG, redirectUrl)); - return; - } - final Intent portalIntent = new Intent(ConnectivityManager.ACTION_CAPTIVE_PORTAL_SIGN_IN); - portalIntent.putExtra(ConnectivityManager.EXTRA_NETWORK, network); - portalIntent.putExtra(ConnectivityManager.EXTRA_CAPTIVE_PORTAL, - new CaptivePortal(new ICaptivePortal.Stub() { - @Override - public void appResponse(int response) { - logd("portal response code: " + response); - releaseNetworkRequest(); - if (response == APP_RETURN_DISMISSED) { - // Upon success http response code, trigger re-evaluation - CarrierActionUtils.applyCarrierAction( - CarrierActionUtils.CARRIER_ACTION_ENABLE_RADIO, intent, - getApplicationContext()); - CarrierActionUtils.applyCarrierAction( - CarrierActionUtils.CARRIER_ACTION_ENABLE_METERED_APNS, intent, - getApplicationContext()); - CarrierActionUtils.applyCarrierAction( - CarrierActionUtils.CARRIER_ACTION_CANCEL_ALL_NOTIFICATIONS, - intent, getApplicationContext()); - } - } - })); - portalIntent.putExtra(ConnectivityManager.EXTRA_CAPTIVE_PORTAL_URL, redirectUrl); - portalIntent.setFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT | Intent.FLAG_ACTIVITY_NEW_TASK - | Intent.FLAG_ACTIVITY_CLEAR_TASK); - if (DBG) logd("launching portal"); - startActivity(portalIntent); - finish(); - } - - // match configured redirection url - private boolean matchUrl(String url, int subId) { - CarrierConfigManager configManager = getApplicationContext() - .getSystemService(CarrierConfigManager.class); - String[] redirectURLs = configManager.getConfigForSubId(subId).getStringArray( - CarrierConfigManager.KEY_CARRIER_DEFAULT_REDIRECTION_URL_STRING_ARRAY); - if (ArrayUtils.isEmpty(redirectURLs)) { - if (DBG) logd("match is unnecessary without any configured redirection url"); - return true; - } - for (String redirectURL : redirectURLs) { - if (url.startsWith(redirectURL)) { - return true; - } - } - if (DBG) loge("no match found for configured redirection url"); - return false; - } - - private static void logd(String s) { - Rlog.d(TAG, s); - } - - private static void loge(String s) { - Rlog.d(TAG, s); - } -} diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CaptivePortalLoginActivity.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CaptivePortalLoginActivity.java new file mode 100644 index 000000000000..a5820f2da56c --- /dev/null +++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CaptivePortalLoginActivity.java @@ -0,0 +1,434 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.carrierdefaultapp; + +import android.app.Activity; +import android.app.LoadedApk; +import android.content.Context; +import android.content.Intent; +import android.graphics.Bitmap; +import android.net.ConnectivityManager; +import android.net.ConnectivityManager.NetworkCallback; +import android.net.Network; +import android.net.NetworkCapabilities; +import android.net.NetworkRequest; +import android.net.Proxy; +import android.net.TrafficStats; +import android.net.Uri; +import android.net.http.SslError; +import android.os.Bundle; +import android.telephony.CarrierConfigManager; +import android.telephony.Rlog; +import android.telephony.SubscriptionManager; +import android.util.ArrayMap; +import android.util.Log; +import android.util.TypedValue; +import android.webkit.SslErrorHandler; +import android.webkit.WebChromeClient; +import android.webkit.WebSettings; +import android.webkit.WebView; +import android.webkit.WebViewClient; +import android.widget.ProgressBar; +import android.widget.TextView; + +import com.android.internal.telephony.PhoneConstants; +import com.android.internal.telephony.TelephonyIntents; +import com.android.internal.util.ArrayUtils; + +import java.io.IOException; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.net.HttpURLConnection; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Random; + +/** + * Activity that launches in response to the captive portal notification + * @see com.android.carrierdefaultapp.CarrierActionUtils#CARRIER_ACTION_SHOW_PORTAL_NOTIFICATION + * This activity requests network connection if there is no available one before loading the real + * portal page and apply carrier actions on the portal activation result. + */ +public class CaptivePortalLoginActivity extends Activity { + private static final String TAG = CaptivePortalLoginActivity.class.getSimpleName(); + private static final boolean DBG = true; + + private static final int SOCKET_TIMEOUT_MS = 10 * 1000; + public static final int NETWORK_REQUEST_TIMEOUT_MS = 5 * 1000; + + private URL mUrl; + private Network mNetwork; + private NetworkCallback mNetworkCallback; + private ConnectivityManager mCm; + private WebView mWebView; + private MyWebViewClient mWebViewClient; + private boolean mLaunchBrowser = false; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + mCm = ConnectivityManager.from(this); + mUrl = getUrlForCaptivePortal(); + if (mUrl == null) { + done(false); + return; + } + if (DBG) logd(String.format("onCreate for %s", mUrl.toString())); + setContentView(R.layout.activity_captive_portal_login); + getActionBar().setDisplayShowHomeEnabled(false); + + mWebView = (WebView) findViewById(R.id.webview); + mWebView.clearCache(true); + WebSettings webSettings = mWebView.getSettings(); + webSettings.setJavaScriptEnabled(true); + webSettings.setMixedContentMode(WebSettings.MIXED_CONTENT_COMPATIBILITY_MODE); + mWebViewClient = new MyWebViewClient(); + mWebView.setWebViewClient(mWebViewClient); + mWebView.setWebChromeClient(new MyWebChromeClient()); + + mNetwork = getNetworkForCaptivePortal(); + if (mNetwork == null) { + requestNetworkForCaptivePortal(); + } else { + mCm.bindProcessToNetwork(mNetwork); + // Start initial page load so WebView finishes loading proxy settings. + // Actual load of mUrl is initiated by MyWebViewClient. + mWebView.loadData("", "text/html", null); + } + } + + @Override + public void onBackPressed() { + WebView myWebView = (WebView) findViewById(R.id.webview); + if (myWebView.canGoBack() && mWebViewClient.allowBack()) { + myWebView.goBack(); + } else { + super.onBackPressed(); + } + } + + @Override + public void onDestroy() { + super.onDestroy(); + releaseNetworkRequest(); + if (mLaunchBrowser) { + // Give time for this network to become default. After 500ms just proceed. + for (int i = 0; i < 5; i++) { + // TODO: This misses when mNetwork underlies a VPN. + if (mNetwork.equals(mCm.getActiveNetwork())) break; + try { + Thread.sleep(100); + } catch (InterruptedException e) { + } + } + final String url = mUrl.toString(); + if (DBG) logd("starting activity with intent ACTION_VIEW for " + url); + startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(url))); + } + } + + // Find WebView's proxy BroadcastReceiver and prompt it to read proxy system properties. + private void setWebViewProxy() { + LoadedApk loadedApk = getApplication().mLoadedApk; + try { + Field receiversField = LoadedApk.class.getDeclaredField("mReceivers"); + receiversField.setAccessible(true); + ArrayMap receivers = (ArrayMap) receiversField.get(loadedApk); + for (Object receiverMap : receivers.values()) { + for (Object rec : ((ArrayMap) receiverMap).keySet()) { + Class clazz = rec.getClass(); + if (clazz.getName().contains("ProxyChangeListener")) { + Method onReceiveMethod = clazz.getDeclaredMethod("onReceive", Context.class, + Intent.class); + Intent intent = new Intent(Proxy.PROXY_CHANGE_ACTION); + onReceiveMethod.invoke(rec, getApplicationContext(), intent); + Log.v(TAG, "Prompting WebView proxy reload."); + } + } + } + } catch (Exception e) { + loge("Exception while setting WebView proxy: " + e); + } + } + + private void done(boolean success) { + if (DBG) logd(String.format("Result success %b for %s", success, mUrl.toString())); + if (success) { + // Trigger re-evaluation upon success http response code + CarrierActionUtils.applyCarrierAction( + CarrierActionUtils.CARRIER_ACTION_ENABLE_RADIO, getIntent(), + getApplicationContext()); + CarrierActionUtils.applyCarrierAction( + CarrierActionUtils.CARRIER_ACTION_ENABLE_METERED_APNS, getIntent(), + getApplicationContext()); + CarrierActionUtils.applyCarrierAction( + CarrierActionUtils.CARRIER_ACTION_CANCEL_ALL_NOTIFICATIONS, getIntent(), + getApplicationContext()); + + } + finishAndRemoveTask(); + } + + private URL getUrlForCaptivePortal() { + String url = getIntent().getStringExtra(TelephonyIntents.EXTRA_REDIRECTION_URL_KEY); + if (url.isEmpty()) { + url = mCm.getCaptivePortalServerUrl(); + } + final CarrierConfigManager configManager = getApplicationContext() + .getSystemService(CarrierConfigManager.class); + final int subId = getIntent().getIntExtra(PhoneConstants.SUBSCRIPTION_KEY, + SubscriptionManager.getDefaultVoiceSubscriptionId()); + final String[] portalURLs = configManager.getConfigForSubId(subId).getStringArray( + CarrierConfigManager.KEY_CARRIER_DEFAULT_REDIRECTION_URL_STRING_ARRAY); + if (!ArrayUtils.isEmpty(portalURLs)) { + for (String portalUrl : portalURLs) { + if (url.startsWith(portalUrl)) { + break; + } + } + url = null; + } + try { + return new URL(url); + } catch (MalformedURLException e) { + loge("Invalid captive portal URL " + url); + } + return null; + } + + private void testForCaptivePortal() { + new Thread(new Runnable() { + public void run() { + // Give time for captive portal to open. + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + } + HttpURLConnection urlConnection = null; + int httpResponseCode = 500; + int oldTag = TrafficStats.getAndSetThreadStatsTag(TrafficStats.TAG_SYSTEM_PROBE); + try { + urlConnection = (HttpURLConnection) mNetwork.openConnection(mUrl); + urlConnection.setInstanceFollowRedirects(false); + urlConnection.setConnectTimeout(SOCKET_TIMEOUT_MS); + urlConnection.setReadTimeout(SOCKET_TIMEOUT_MS); + urlConnection.setUseCaches(false); + urlConnection.getInputStream(); + httpResponseCode = urlConnection.getResponseCode(); + } catch (IOException e) { + } finally { + if (urlConnection != null) urlConnection.disconnect(); + TrafficStats.setThreadStatsTag(oldTag); + } + if (httpResponseCode == 204) { + done(true); + } + } + }).start(); + } + + private Network getNetworkForCaptivePortal() { + Network[] info = mCm.getAllNetworks(); + if (!ArrayUtils.isEmpty(info)) { + for (Network nw : info) { + final NetworkCapabilities nc = mCm.getNetworkCapabilities(nw); + if (nc.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) + && nc.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)) { + return nw; + } + } + } + return null; + } + + private void requestNetworkForCaptivePortal() { + NetworkRequest request = new NetworkRequest.Builder() + .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR) + .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) + .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED) + .build(); + + mNetworkCallback = new ConnectivityManager.NetworkCallback() { + @Override + public void onAvailable(Network network) { + if (DBG) logd("Network available: " + network); + mCm.bindProcessToNetwork(network); + mNetwork = network; + runOnUiThreadIfNotFinishing(() -> { + // Start initial page load so WebView finishes loading proxy settings. + // Actual load of mUrl is initiated by MyWebViewClient. + mWebView.loadData("", "text/html", null); + }); + } + + @Override + public void onUnavailable() { + if (DBG) logd("Network unavailable"); + runOnUiThreadIfNotFinishing(() -> { + // Instead of not loading anything in webview, simply load the page and return + // HTTP error page in the absence of network connection. + mWebView.loadUrl(mUrl.toString()); + }); + } + }; + logd("request Network for captive portal"); + mCm.requestNetwork(request, mNetworkCallback, NETWORK_REQUEST_TIMEOUT_MS); + } + + private void releaseNetworkRequest() { + logd("release Network for captive portal"); + if (mNetworkCallback != null) { + mCm.unregisterNetworkCallback(mNetworkCallback); + mNetworkCallback = null; + mNetwork = null; + } + } + + private class MyWebViewClient extends WebViewClient { + private static final String INTERNAL_ASSETS = "file:///android_asset/"; + private final String mBrowserBailOutToken = Long.toString(new Random().nextLong()); + // How many Android device-independent-pixels per scaled-pixel + // dp/sp = (px/sp) / (px/dp) = (1/sp) / (1/dp) + private final float mDpPerSp = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 1, + getResources().getDisplayMetrics()) + / TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 1, + getResources().getDisplayMetrics()); + private int mPagesLoaded; + + // If we haven't finished cleaning up the history, don't allow going back. + public boolean allowBack() { + return mPagesLoaded > 1; + } + + @Override + public void onPageStarted(WebView view, String url, Bitmap favicon) { + if (url.contains(mBrowserBailOutToken)) { + mLaunchBrowser = true; + done(false); + return; + } + // The first page load is used only to cause the WebView to + // fetch the proxy settings. Don't update the URL bar, and + // don't check if the captive portal is still there. + if (mPagesLoaded == 0) return; + // For internally generated pages, leave URL bar listing prior URL as this is the URL + // the page refers to. + if (!url.startsWith(INTERNAL_ASSETS)) { + final TextView myUrlBar = (TextView) findViewById(R.id.url_bar); + myUrlBar.setText(url); + } + if (mNetwork != null) { + testForCaptivePortal(); + } + } + + @Override + public void onPageFinished(WebView view, String url) { + mPagesLoaded++; + if (mPagesLoaded == 1) { + // Now that WebView has loaded at least one page we know it has read in the proxy + // settings. Now prompt the WebView read the Network-specific proxy settings. + setWebViewProxy(); + // Load the real page. + view.loadUrl(mUrl.toString()); + return; + } else if (mPagesLoaded == 2) { + // Prevent going back to empty first page. + view.clearHistory(); + } + if (mNetwork != null) { + testForCaptivePortal(); + } + } + + // Convert Android device-independent-pixels (dp) to HTML size. + private String dp(int dp) { + // HTML px's are scaled just like dp's, so just add "px" suffix. + return Integer.toString(dp) + "px"; + } + + // Convert Android scaled-pixels (sp) to HTML size. + private String sp(int sp) { + // Convert sp to dp's. + float dp = sp * mDpPerSp; + // Apply a scale factor to make things look right. + dp *= 1.3; + // Convert dp's to HTML size. + return dp((int) dp); + } + + // A web page consisting of a large broken lock icon to indicate SSL failure. + private final String SSL_ERROR_HTML = "<html><head><style>" + + "body { margin-left:" + dp(48) + "; margin-right:" + dp(48) + "; " + + "margin-top:" + dp(96) + "; background-color:#fafafa; }" + + "img { width:" + dp(48) + "; height:" + dp(48) + "; }" + + "div.warn { font-size:" + sp(16) + "; margin-top:" + dp(16) + "; " + + " opacity:0.87; line-height:1.28; }" + + "div.example { font-size:" + sp(14) + "; margin-top:" + dp(16) + "; " + + " opacity:0.54; line-height:1.21905; }" + + "a { font-size:" + sp(14) + "; text-decoration:none; text-transform:uppercase; " + + " margin-top:" + dp(24) + "; display:inline-block; color:#4285F4; " + + " height:" + dp(48) + "; font-weight:bold; }" + + "</style></head><body><p><img src=quantum_ic_warning_amber_96.png><br>" + + "<div class=warn>%s</div>" + + "<div class=example>%s</div>" + "<a href=%s>%s</a></body></html>"; + + @Override + public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) { + Log.w(TAG, "SSL error (error: " + error.getPrimaryError() + " host: " + // Only show host to avoid leaking private info. + + Uri.parse(error.getUrl()).getHost() + " certificate: " + + error.getCertificate() + "); displaying SSL warning."); + final String html = String.format(SSL_ERROR_HTML, getString(R.string.ssl_error_warning), + getString(R.string.ssl_error_example), mBrowserBailOutToken, + getString(R.string.ssl_error_continue)); + view.loadDataWithBaseURL(INTERNAL_ASSETS, html, "text/HTML", "UTF-8", null); + } + + @Override + public boolean shouldOverrideUrlLoading(WebView view, String url) { + if (url.startsWith("tel:")) { + startActivity(new Intent(Intent.ACTION_DIAL, Uri.parse(url))); + return true; + } + return false; + } + } + + private class MyWebChromeClient extends WebChromeClient { + @Override + public void onProgressChanged(WebView view, int newProgress) { + final ProgressBar myProgressBar = (ProgressBar) findViewById(R.id.progress_bar); + myProgressBar.setProgress(newProgress); + } + } + + private void runOnUiThreadIfNotFinishing(Runnable r) { + if (!isFinishing()) { + runOnUiThread(r); + } + } + + private static void logd(String s) { + Rlog.d(TAG, s); + } + + private static void loge(String s) { + Rlog.d(TAG, s); + } + +} diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CarrierActionUtils.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CarrierActionUtils.java index d9bd2fcc0acb..73ff3a9b5d1e 100644 --- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CarrierActionUtils.java +++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CarrierActionUtils.java @@ -112,8 +112,10 @@ public class CarrierActionUtils { logd("onShowCaptivePortalNotification"); final NotificationManager notificationMgr = context.getSystemService( NotificationManager.class); - Intent portalIntent = new Intent(context, CaptivePortalLaunchActivity.class); + Intent portalIntent = new Intent(context, CaptivePortalLoginActivity.class); portalIntent.putExtras(intent); + portalIntent.setFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT + | Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, portalIntent, PendingIntent.FLAG_UPDATE_CURRENT); Notification notification = getNotification(context, R.string.portal_notification_id, diff --git a/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/LaunchCaptivePortalActivityTest.java b/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/LaunchCaptivePortalActivityTest.java deleted file mode 100644 index 8a18d7229435..000000000000 --- a/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/LaunchCaptivePortalActivityTest.java +++ /dev/null @@ -1,108 +0,0 @@ -package com.android.carrierdefaultapp; - -import android.annotation.TargetApi; -import android.content.Intent; -import android.net.ConnectivityManager; -import android.net.Network; -import android.net.NetworkCapabilities; -import android.net.NetworkInfo; -import android.net.NetworkRequest; - -import com.android.internal.telephony.TelephonyIntents; - -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.mockito.ArgumentCaptor; -import org.mockito.Captor; -import org.mockito.Mock; - -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.anyInt; -import static org.mockito.Matchers.eq; -import static org.mockito.Mockito.atLeast; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; - -public class LaunchCaptivePortalActivityTest extends - CarrierDefaultActivityTestCase<CaptivePortalLaunchActivity> { - - @Mock - private ConnectivityManager mCm; - @Mock - private NetworkInfo mNetworkInfo; - @Mock - private Network mNetwork; - - @Captor - private ArgumentCaptor<Integer> mInt; - @Captor - private ArgumentCaptor<NetworkRequest> mNetworkReq; - - private NetworkCapabilities mNetworkCapabilities; - - public LaunchCaptivePortalActivityTest() { - super(CaptivePortalLaunchActivity.class); - } - - @Before - public void setUp() throws Exception { - super.setUp(); - injectSystemService(ConnectivityManager.class, mCm); - } - - @After - public void tearDown() throws Exception { - super.tearDown(); - } - - @Override - protected Intent createActivityIntent() { - Intent intent = new Intent(getInstrumentation().getTargetContext(), - CaptivePortalLaunchActivity.class); - intent.putExtra(TelephonyIntents.EXTRA_REDIRECTION_URL_KEY, "url"); - return intent; - } - - @Test - public void testWithoutInternetConnection() throws Throwable { - startActivity(); - TestContext.waitForMs(100); - verify(mCm, atLeast(1)).requestNetwork(mNetworkReq.capture(), any(), mInt.capture()); - // verify network request - assert(mNetworkReq.getValue().networkCapabilities.hasCapability( - NetworkCapabilities.NET_CAPABILITY_INTERNET)); - assert(mNetworkReq.getValue().networkCapabilities.hasTransport( - NetworkCapabilities.TRANSPORT_CELLULAR)); - assertFalse(mNetworkReq.getValue().networkCapabilities.hasCapability( - NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)); - assertEquals(CaptivePortalLaunchActivity.NETWORK_REQUEST_TIMEOUT_IN_MS, - (int) mInt.getValue()); - // verify captive portal app is not launched due to unavailable network - assertNull(getStartedActivityIntent()); - stopActivity(); - } - - @Test - public void testWithInternetConnection() throws Throwable { - // Mock internet connection - mNetworkCapabilities = new NetworkCapabilities() - .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) - .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR); - doReturn(new Network[]{mNetwork}).when(mCm).getAllNetworks(); - doReturn(mNetworkCapabilities).when(mCm).getNetworkCapabilities(eq(mNetwork)); - doReturn(mNetworkInfo).when(mCm).getNetworkInfo(eq(mNetwork)); - doReturn(true).when(mNetworkInfo).isConnected(); - - startActivity(); - TestContext.waitForMs(100); - // verify there is no network request with internet connection - verify(mCm, times(0)).requestNetwork(any(), any(), anyInt()); - // verify captive portal app is launched - assertNotNull(getStartedActivityIntent()); - assertEquals(ConnectivityManager.ACTION_CAPTIVE_PORTAL_SIGN_IN, - getStartedActivityIntent().getAction()); - stopActivity(); - } -} diff --git a/packages/Osu/src/com/android/hotspot2/app/OSUService.java b/packages/Osu/src/com/android/hotspot2/app/OSUService.java index 62a203dbfde0..e9da113af747 100644 --- a/packages/Osu/src/com/android/hotspot2/app/OSUService.java +++ b/packages/Osu/src/com/android/hotspot2/app/OSUService.java @@ -46,8 +46,9 @@ public class OSUService extends IntentService { private static final String[] INTENTS = { WifiManager.SCAN_RESULTS_AVAILABLE_ACTION, - WifiManager.PASSPOINT_WNM_FRAME_RECEIVED_ACTION, - WifiManager.PASSPOINT_ICON_RECEIVED_ACTION, + // TODO(b/32883320): use updated intent definitions. + //WifiManager.PASSPOINT_WNM_FRAME_RECEIVED_ACTION, + //WifiManager.PASSPOINT_ICON_RECEIVED_ACTION, WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION, WifiManager.WIFI_STATE_CHANGED_ACTION, WifiManager.NETWORK_STATE_CHANGED_ACTION, @@ -120,6 +121,8 @@ public class OSUService extends IntentService { case WifiManager.SCAN_RESULTS_AVAILABLE_ACTION: mOsuManager.pushScanResults(wifiManager.getScanResults()); break; + // TODO(b/32883320): use updated intent definitions. + /* case WifiManager.PASSPOINT_WNM_FRAME_RECEIVED_ACTION: long bssid = bundle.getLong(WifiManager.EXTRA_PASSPOINT_WNM_BSSID); String url = bundle.getString(WifiManager.EXTRA_PASSPOINT_WNM_URL); @@ -157,6 +160,7 @@ public class OSUService extends IntentService { bundle.getString(WifiManager.EXTRA_PASSPOINT_ICON_FILE), bundle.getByteArray(WifiManager.EXTRA_PASSPOINT_ICON_DATA)); break; + */ case WifiManager.NETWORK_STATE_CHANGED_ACTION: mOsuManager.networkConnectChange( (WifiInfo) intent.getParcelableExtra(WifiManager.EXTRA_WIFI_INFO)); diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml index f77d466ef87e..c64574fde216 100644 --- a/packages/SettingsLib/res/values/strings.xml +++ b/packages/SettingsLib/res/values/strings.xml @@ -422,11 +422,11 @@ <!-- Setting Checkbox title whether to enable WiFi Verbose Logging. [CHAR LIMIT=40] --> <string name="wifi_verbose_logging">Enable Wi\u2011Fi Verbose Logging</string> <!-- Setting Checkbox title whether to enable WiFi Aggressive Handover. [CHAR LIMIT=40] --> - <string name="wifi_aggressive_handover">Aggressive Wi\u2011Fi to Cellular handover</string> + <string name="wifi_aggressive_handover">Aggressive Wi\u2011Fi to mobile handover</string> <!-- Setting Checkbox title whether to enable WiFi Scanning in the presence of traffic. [CHAR LIMIT=80] --> <string name="wifi_allow_scan_with_traffic">Always allow Wi\u2011Fi Roam Scans</string> - <!-- Setting Checkbox title whether to always keep cellular data active. [CHAR LIMIT=80] --> - <string name="mobile_data_always_on">Cellular data always active</string> + <!-- Setting Checkbox title whether to always keep mobile data active. [CHAR LIMIT=80] --> + <string name="mobile_data_always_on">Mobile data always active</string> <!-- Setting Checkbox title for disabling Bluetooth absolute volume --> <string name="bluetooth_disable_absolute_volume">Disable absolute volume</string> @@ -463,7 +463,7 @@ <!-- Setting Checkbox summary whether to enable Wifi verbose Logging [CHAR LIMIT=80] --> <string name="wifi_verbose_logging_summary">Increase Wi\u2011Fi logging level, show per SSID RSSI in Wi\u2011Fi Picker</string> <!-- Setting Checkbox summary whether to enable Wifi aggressive handover [CHAR LIMIT=130] --> - <string name="wifi_aggressive_handover_summary">When enabled, Wi\u2011Fi will be more aggressive in handing over the data connection to Cellular, when Wi\u2011Fi signal is low</string> + <string name="wifi_aggressive_handover_summary">When enabled, Wi\u2011Fi will be more aggressive in handing over the data connection to mobile, when Wi\u2011Fi signal is low</string> <!-- Setting Checkbox summary whether to always allow WiFi Roam Scans [CHAR LIMIT=130] --> <string name="wifi_allow_scan_with_traffic_summary">Allow/Disallow Wi\u2011Fi Roam Scans based on the amount of data traffic present at the interface</string> <!-- UI debug setting: limit size of Android logger buffers --> diff --git a/packages/SettingsLib/src/com/android/settingslib/drawer/CategoryManager.java b/packages/SettingsLib/src/com/android/settingslib/drawer/CategoryManager.java index fa5ba73b318f..ee7885d2a077 100644 --- a/packages/SettingsLib/src/com/android/settingslib/drawer/CategoryManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/drawer/CategoryManager.java @@ -132,7 +132,7 @@ public class CategoryManager { mCategoryByKeyMap.put(category.key, category); } backwardCompatCleanupForCategory(mTileByComponentCache, mCategoryByKeyMap); - normalizePriority(context, mCategoryByKeyMap); + sortCategories(context, mCategoryByKeyMap); filterDuplicateTiles(mCategoryByKeyMap); } } @@ -188,17 +188,17 @@ public class CategoryManager { } /** - * Normalize priority values on tiles across injected from all apps to make sure they don't set - * the same priority value. However internal tiles' priority remains unchanged. + * Sort the tiles injected from all apps such that if they have the same priority value, + * they wil lbe sorted by package name. * <p/> - * A list of tiles are considered normalized when their priority value increases in a linear + * A list of tiles are considered sorted when their priority value decreases in a linear * scan. */ @VisibleForTesting - synchronized void normalizePriority(Context context, + synchronized void sortCategories(Context context, Map<String, DashboardCategory> categoryByKeyMap) { for (Entry<String, DashboardCategory> categoryEntry : categoryByKeyMap.entrySet()) { - normalizePriorityForExternalTiles(context, categoryEntry.getValue()); + sortCategoriesForExternalTiles(context, categoryEntry.getValue()); } } @@ -228,39 +228,34 @@ public class CategoryManager { } /** - * Normalize priority value for tiles within a single {@code DashboardCategory}. + * Sort priority value for tiles within a single {@code DashboardCategory}. * - * @see #normalizePriority(Context, Map) + * @see #sortCategories(Context, Map) */ - private synchronized void normalizePriorityForExternalTiles(Context context, + private synchronized void sortCategoriesForExternalTiles(Context context, DashboardCategory dashboardCategory) { final String skipPackageName = context.getPackageName(); - // Sort tiles based on [package, priority within package] + // Sort tiles based on [priority, package within priority] Collections.sort(dashboardCategory.tiles, (tile1, tile2) -> { final String package1 = tile1.intent.getComponent().getPackageName(); final String package2 = tile2.intent.getComponent().getPackageName(); final int packageCompare = CASE_INSENSITIVE_ORDER.compare(package1, package2); - // First sort by package name + // First sort by priority + final int priorityCompare = tile2.priority - tile1.priority; + if (priorityCompare != 0) { + return priorityCompare; + } + // Then sort by package name, skip package take precedence if (packageCompare != 0) { - return packageCompare; - } else if (TextUtils.equals(package1, skipPackageName)) { - return 0; + if (TextUtils.equals(package1, skipPackageName)) { + return -1; + } + if (TextUtils.equals(package2, skipPackageName)) { + return 1; + } } - // Then sort by priority - return tile1.priority - tile2.priority; + return packageCompare; }); - // Update priority for all items so no package define the same priority value. - final int count = dashboardCategory.tiles.size(); - for (int i = 0; i < count; i++) { - final String packageName = - dashboardCategory.tiles.get(i).intent.getComponent().getPackageName(); - if (TextUtils.equals(packageName, skipPackageName)) { - // We skip this tile because it's a intent pointing to our own app. We trust the - // priority is set correctly, so don't normalize. - continue; - } - dashboardCategory.tiles.get(i).priority = i; - } } } diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java index 6fe581eab86d..48f3e2a855f3 100644 --- a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java +++ b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java @@ -47,6 +47,7 @@ import android.text.TextUtils; import android.text.style.TtsSpan; import android.util.Log; +import com.android.internal.annotations.VisibleForTesting; import com.android.settingslib.R; import java.util.ArrayList; @@ -112,9 +113,14 @@ public class AccessPoint implements Comparable<AccessPoint> { private static final int PSK_WPA2 = 2; private static final int PSK_WPA_WPA2 = 3; - public static final int SIGNAL_LEVELS = 4; + /** + * The number of distinct wifi levels. + * + * <p>Must keep in sync with {@link R.array.wifi_signal} and {@link WifiManager#RSSI_LEVELS}. + */ + public static final int SIGNAL_LEVELS = 5; - static final int UNREACHABLE_RSSI = Integer.MAX_VALUE; + public static final int UNREACHABLE_RSSI = Integer.MIN_VALUE; private final Context mContext; @@ -170,8 +176,8 @@ public class AccessPoint implements Comparable<AccessPoint> { } } update(mConfig, mInfo, mNetworkInfo); - mRssi = getRssi(); - mSeen = getSeen(); + updateRssi(); + updateSeen(); mId = sLastId.incrementAndGet(); } @@ -369,27 +375,57 @@ public class AccessPoint implements Comparable<AccessPoint> { return mInfo; } + /** + * Returns the number of levels to show for a Wifi icon, from 0 to {@link #SIGNAL_LEVELS}-1. + * + * <p>Use {@#isReachable()} to determine if an AccessPoint is in range, as this method will + * always return at least 0. + */ public int getLevel() { - if (!isReachable()) { - return -1; - } return WifiManager.calculateSignalLevel(mRssi, SIGNAL_LEVELS); } public int getRssi() { + return mRssi; + } + + /** + * Updates {@link #mRssi}. + * + * <p>If the given connection is active, the existing value of {@link #mRssi} will be returned. + * If the given AccessPoint is not active, a value will be calculated from previous scan + * results, returning the best RSSI for all matching AccessPoints. If the access point is not + * connected and there are no scan results, the rssi will be set to {@link #UNREACHABLE_RSSI}. + * + * <p>Old scan results will be evicted from the cache when this method is invoked. + */ + private void updateRssi() { evictOldScanResults(); - int rssi = Integer.MIN_VALUE; + + if (this.isActive()) { + return; + } + + int rssi = UNREACHABLE_RSSI; for (ScanResult result : mScanResultCache.values()) { if (result.level > rssi) { rssi = result.level; } } - return rssi; + mRssi = rssi; } - public long getSeen() { + /** + * Updates {@link #mSeen} based on the scan result cache. + * + * <p>Old scan results will be evicted from the cache when this method is invoked. + */ + private void updateSeen() { evictOldScanResults(); + + // TODO(sghuman): Set to now if connected + long seen = 0; for (ScanResult result : mScanResultCache.values()) { if (result.timestamp > seen) { @@ -397,7 +433,7 @@ public class AccessPoint implements Comparable<AccessPoint> { } } - return seen; + mSeen = seen; } public NetworkInfo getNetworkInfo() { @@ -822,13 +858,12 @@ public class AccessPoint implements Comparable<AccessPoint> { boolean update(ScanResult result) { if (matches(result)) { + int oldLevel = getLevel(); + /* Add or update the scan result for the BSSID */ mScanResultCache.put(result.BSSID, result); - - int oldLevel = getLevel(); - int oldRssi = getRssi(); - mSeen = getSeen(); - mRssi = (getRssi() + oldRssi)/2; + updateSeen(); + updateRssi(); int newLevel = getLevel(); if (newLevel > 0 && newLevel != oldLevel && mAccessPointListener != null) { @@ -848,7 +883,7 @@ public class AccessPoint implements Comparable<AccessPoint> { return false; } - boolean update(WifiConfiguration config, WifiInfo info, NetworkInfo networkInfo) { + public boolean update(WifiConfiguration config, WifiInfo info, NetworkInfo networkInfo) { boolean reorder = false; if (info != null && isInfoForThisAccessPoint(config, info)) { reorder = (mInfo == null); @@ -877,10 +912,16 @@ public class AccessPoint implements Comparable<AccessPoint> { } } + @VisibleForTesting void setRssi(int rssi) { mRssi = rssi; } + /** Sets the rssi to {@link #UNREACHABLE_RSSI}. */ + void setUnreachable() { + setRssi(AccessPoint.UNREACHABLE_RSSI); + } + int getRankingScore() { return mRankingScore; } @@ -890,7 +931,7 @@ public class AccessPoint implements Comparable<AccessPoint> { } /** Return true if the current RSSI is reachable, and false otherwise. */ - boolean isReachable() { + public boolean isReachable() { return mRssi != UNREACHABLE_RSSI; } diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java index 50972c709863..c9fa0170e7c4 100644 --- a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java +++ b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java @@ -223,8 +223,7 @@ public class AccessPointPreference extends Preference { } final Context context = getContext(); - int level = WifiManager.calculateSignalLevel( - mAccessPoint.getRssi(), WifiManager.RSSI_LEVELS); + int level = mAccessPoint.getLevel(); int wifiBadge = mAccessPoint.getBadge(); if (level != mLevel || wifiBadge != mWifiBadge) { mLevel = level; diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java index 8421c2c9dee3..55c886e6fb1c 100644 --- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java +++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java @@ -471,7 +471,8 @@ public class WifiTracker { accessPoint.update(connectionConfig, mLastInfo, mLastNetworkInfo); } if (mIncludeSaved) { - // If saved network not present in scan result then set its Rssi to MAX_VALUE + // If saved network not present in scan result then set its Rssi to + // UNREACHABLE_RSSI boolean apFound = false; for (ScanResult result : results) { if (result.SSID.equals(accessPoint.getSsidStr())) { @@ -480,7 +481,7 @@ public class WifiTracker { } } if (!apFound) { - accessPoint.setRssi(Integer.MAX_VALUE); + accessPoint.setUnreachable(); } accessPoints.add(accessPoint); apMap.put(accessPoint.getSsidStr(), accessPoint); diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java index ec0190cfa538..e8a58c13baf0 100644 --- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java +++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java @@ -89,21 +89,10 @@ public class AccessPointTest { @Test public void testThatCopyAccessPoint_scanCacheShouldMatch() { - Bundle bundle = new Bundle(); - ArrayList<ScanResult> scanResults = new ArrayList<>(); - for (int i = 0; i < 5; i++) { - ScanResult scanResult = new ScanResult(); - scanResult.level = i; - scanResult.BSSID = "bssid-" + i; - scanResult.timestamp = SystemClock.elapsedRealtime() * 1000; - scanResults.add(scanResult); - } - - bundle.putParcelableArrayList("key_scanresultcache", scanResults); - AccessPoint original = new AccessPoint(mContext, bundle); + AccessPoint original = createAccessPointWithScanResultCache(); assertThat(original.getRssi()).isEqualTo(4); AccessPoint copy = new AccessPoint(mContext, createWifiConfiguration()); - assertThat(copy.getRssi()).isEqualTo(Integer.MIN_VALUE); + assertThat(copy.getRssi()).isEqualTo(AccessPoint.UNREACHABLE_RSSI); copy.copyFrom(original); assertThat(original.getRssi()).isEqualTo(copy.getRssi()); } @@ -190,6 +179,37 @@ public class AccessPointTest { assertThat(points.indexOf(firstName)).isLessThan(points.indexOf(lastname)); } + @Test + public void testRssiIsSetFromScanResults() { + AccessPoint ap = createAccessPointWithScanResultCache(); + int originalRssi = ap.getRssi(); + assertThat(originalRssi).isNotEqualTo(AccessPoint.UNREACHABLE_RSSI); + } + + @Test + public void testGetRssiShouldReturnSetRssiValue() { + AccessPoint ap = createAccessPointWithScanResultCache(); + int originalRssi = ap.getRssi(); + int newRssi = originalRssi - 10; + ap.setRssi(newRssi); + assertThat(ap.getRssi()).isEqualTo(newRssi); + } + + private AccessPoint createAccessPointWithScanResultCache() { + Bundle bundle = new Bundle(); + ArrayList<ScanResult> scanResults = new ArrayList<>(); + for (int i = 0; i < 5; i++) { + ScanResult scanResult = new ScanResult(); + scanResult.level = i; + scanResult.BSSID = "bssid-" + i; + scanResult.timestamp = SystemClock.elapsedRealtime() * 1000; + scanResults.add(scanResult); + } + + bundle.putParcelableArrayList("key_scanresultcache", scanResults); + return new AccessPoint(mContext, bundle); + } + private WifiConfiguration createWifiConfiguration() { WifiConfiguration configuration = new WifiConfiguration(); configuration.BSSID = "bssid"; diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/CategoryManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/CategoryManagerTest.java index 434241d17f64..8d61338f86c3 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/CategoryManagerTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/CategoryManagerTest.java @@ -120,7 +120,7 @@ public class CategoryManagerTest { } @Test - public void normalizePriority_singlePackage_shouldReorderBasedOnPriority() { + public void sortCategories_singlePackage_shouldReorderBasedOnPriority() { // Create some fake tiles that are not sorted. final String testPackage = "com.android.test"; final DashboardCategory category = new DashboardCategory(); @@ -141,22 +141,18 @@ public class CategoryManagerTest { category.tiles.add(tile3); mCategoryByKeyMap.put(CategoryKey.CATEGORY_HOMEPAGE, category); - // Normalize their priorities - mCategoryManager.normalizePriority(ShadowApplication.getInstance().getApplicationContext(), + // Sort their priorities + mCategoryManager.sortCategories(ShadowApplication.getInstance().getApplicationContext(), mCategoryByKeyMap); // Verify they are now sorted. - assertThat(category.tiles.get(0)).isSameAs(tile2); + assertThat(category.tiles.get(0)).isSameAs(tile3); assertThat(category.tiles.get(1)).isSameAs(tile1); - assertThat(category.tiles.get(2)).isSameAs(tile3); - // Verify their priority is normalized - assertThat(category.tiles.get(0).priority).isEqualTo(0); - assertThat(category.tiles.get(1).priority).isEqualTo(1); - assertThat(category.tiles.get(2).priority).isEqualTo(2); + assertThat(category.tiles.get(2)).isSameAs(tile2); } @Test - public void normalizePriority_multiPackage_shouldReorderBasedOnPackageAndPriority() { + public void sortCategories_multiPackage_shouldReorderBasedOnPackageAndPriority() { // Create some fake tiles that are not sorted. final String testPackage1 = "com.android.test1"; final String testPackage2 = "com.android.test2"; @@ -178,22 +174,18 @@ public class CategoryManagerTest { category.tiles.add(tile3); mCategoryByKeyMap.put(CategoryKey.CATEGORY_HOMEPAGE, category); - // Normalize their priorities - mCategoryManager.normalizePriority(ShadowApplication.getInstance().getApplicationContext(), + // Sort their priorities + mCategoryManager.sortCategories(ShadowApplication.getInstance().getApplicationContext(), mCategoryByKeyMap); // Verify they are now sorted. - assertThat(category.tiles.get(0)).isSameAs(tile3); - assertThat(category.tiles.get(1)).isSameAs(tile2); - assertThat(category.tiles.get(2)).isSameAs(tile1); - // Verify their priority is normalized - assertThat(category.tiles.get(0).priority).isEqualTo(0); - assertThat(category.tiles.get(1).priority).isEqualTo(1); - assertThat(category.tiles.get(2).priority).isEqualTo(2); + assertThat(category.tiles.get(0)).isSameAs(tile2); + assertThat(category.tiles.get(1)).isSameAs(tile1); + assertThat(category.tiles.get(2)).isSameAs(tile3); } @Test - public void normalizePriority_internalPackageTiles_shouldSkipTileForInternalPackage() { + public void sortCategories_internalPackageTiles_shouldSkipTileForInternalPackage() { // Create some fake tiles that are not sorted. final String testPackage = ShadowApplication.getInstance().getApplicationContext().getPackageName(); @@ -215,18 +207,82 @@ public class CategoryManagerTest { category.tiles.add(tile3); mCategoryByKeyMap.put(CategoryKey.CATEGORY_HOMEPAGE, category); - // Normalize their priorities - mCategoryManager.normalizePriority(ShadowApplication.getInstance().getApplicationContext(), + // Sort their priorities + mCategoryManager.sortCategories(ShadowApplication.getInstance().getApplicationContext(), mCategoryByKeyMap); // Verify the sorting order is not changed assertThat(category.tiles.get(0)).isSameAs(tile1); assertThat(category.tiles.get(1)).isSameAs(tile2); assertThat(category.tiles.get(2)).isSameAs(tile3); - // Verify their priorities are not changed. - assertThat(category.tiles.get(0).priority).isEqualTo(100); - assertThat(category.tiles.get(1).priority).isEqualTo(100); - assertThat(category.tiles.get(2).priority).isEqualTo(50); + } + + @Test + public void sortCategories_internalAndExternalPackageTiles_shouldRetainPriorityOrdering() { + // Inject one external tile among internal tiles. + final String testPackage = + ShadowApplication.getInstance().getApplicationContext().getPackageName(); + final String testPackage2 = "com.google.test2"; + final DashboardCategory category = new DashboardCategory(); + final Tile tile1 = new Tile(); + tile1.intent = new Intent().setComponent(new ComponentName(testPackage, "class1")); + tile1.priority = 2; + final Tile tile2 = new Tile(); + tile2.intent = new Intent().setComponent(new ComponentName(testPackage, "class2")); + tile2.priority = 1; + final Tile tile3 = new Tile(); + tile3.intent = new Intent().setComponent(new ComponentName(testPackage2, "class0")); + tile3.priority = 0; + final Tile tile4 = new Tile(); + tile4.intent = new Intent().setComponent(new ComponentName(testPackage, "class3")); + tile4.priority = -1; + category.tiles.add(tile1); + category.tiles.add(tile2); + category.tiles.add(tile3); + category.tiles.add(tile4); + mCategoryByKeyMap.put(CategoryKey.CATEGORY_HOMEPAGE, category); + + // Sort their priorities + mCategoryManager.sortCategories(ShadowApplication.getInstance().getApplicationContext(), + mCategoryByKeyMap); + + // Verify the sorting order is not changed + assertThat(category.tiles.get(0)).isSameAs(tile1); + assertThat(category.tiles.get(1)).isSameAs(tile2); + assertThat(category.tiles.get(2)).isSameAs(tile3); + assertThat(category.tiles.get(3)).isSameAs(tile4); + } + + @Test + public void sortCategories_samePriority_internalPackageTileShouldTakePrecedence() { + // Inject one external tile among internal tiles with same priority. + final String testPackage = + ShadowApplication.getInstance().getApplicationContext().getPackageName(); + final String testPackage2 = "com.google.test2"; + final String testPackage3 = "com.abcde.test3"; + final DashboardCategory category = new DashboardCategory(); + final Tile tile1 = new Tile(); + tile1.intent = new Intent().setComponent(new ComponentName(testPackage2, "class1")); + tile1.priority = 1; + final Tile tile2 = new Tile(); + tile2.intent = new Intent().setComponent(new ComponentName(testPackage, "class2")); + tile2.priority = 1; + final Tile tile3 = new Tile(); + tile3.intent = new Intent().setComponent(new ComponentName(testPackage3, "class3")); + tile3.priority = 1; + category.tiles.add(tile1); + category.tiles.add(tile2); + category.tiles.add(tile3); + mCategoryByKeyMap.put(CategoryKey.CATEGORY_HOMEPAGE, category); + + // Sort their priorities + mCategoryManager.sortCategories(ShadowApplication.getInstance().getApplicationContext(), + mCategoryByKeyMap); + + // Verify the sorting order is internal first, follow by package name ordering + assertThat(category.tiles.get(0)).isSameAs(tile2); + assertThat(category.tiles.get(1)).isSameAs(tile3); + assertThat(category.tiles.get(2)).isSameAs(tile1); } @Test diff --git a/packages/SettingsProvider/AndroidManifest.xml b/packages/SettingsProvider/AndroidManifest.xml index 6c06d05e100b..9374d522707b 100644 --- a/packages/SettingsProvider/AndroidManifest.xml +++ b/packages/SettingsProvider/AndroidManifest.xml @@ -17,6 +17,7 @@ android:multiprocess="false" android:exported="true" android:singleUser="true" - android:initOrder="100" /> + android:initOrder="100" + android:visibleToInstantApps="true" /> </application> </manifest> diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java index 1fe3c4844087..4a54c0e909d2 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java @@ -240,8 +240,8 @@ class SettingsProtoDumpUtil { Settings.Global.HDMI_CONTROL_ENABLED, GlobalSettingsProto.HDMI_CONTROL_ENABLED); dumpSetting(s, p, - Settings.Global.HDMI_SYSTEM_AUDIO_ENABLED, - GlobalSettingsProto.HDMI_SYSTEM_AUDIO_ENABLED); + Settings.Global.HDMI_SYSTEM_AUDIO_CONTROL_ENABLED, + GlobalSettingsProto.HDMI_SYSTEM_AUDIO_CONTROL_ENABLED); dumpSetting(s, p, Settings.Global.HDMI_CONTROL_AUTO_WAKEUP_ENABLED, GlobalSettingsProto.HDMI_CONTROL_AUTO_WAKEUP_ENABLED); diff --git a/packages/Shell/res/drawable/ic_bug_report_black_24dp.xml b/packages/Shell/res/drawable/ic_bug_report_black_24dp.xml new file mode 100644 index 000000000000..a102ceef3e30 --- /dev/null +++ b/packages/Shell/res/drawable/ic_bug_report_black_24dp.xml @@ -0,0 +1,9 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + <path + android:pathData="M20,8h-2.81c-0.45,-0.78 -1.07,-1.45 -1.82,-1.96L17,4.41 15.59,3l-2.17,2.17C12.96,5.06 12.49,5 12,5c-0.49,0 -0.96,0.06 -1.41,0.17L8.41,3 7,4.41l1.62,1.63C7.88,6.55 7.26,7.22 6.81,8L4,8v2h2.09c-0.05,0.33 -0.09,0.66 -0.09,1v1L4,12v2h2v1c0,0.34 0.04,0.67 0.09,1L4,16v2h2.81c1.04,1.79 2.97,3 5.19,3s4.15,-1.21 5.19,-3L20,18v-2h-2.09c0.05,-0.33 0.09,-0.66 0.09,-1v-1h2v-2h-2v-1c0,-0.34 -0.04,-0.67 -0.09,-1L20,10L20,8zM14,16h-4v-2h4v2zM14,12h-4v-2h4v2z" + android:fillColor="#000000"/> +</vector> diff --git a/packages/Shell/res/values/strings.xml b/packages/Shell/res/values/strings.xml index 2a5703a37d9e..1c49a55d4f3b 100644 --- a/packages/Shell/res/values/strings.xml +++ b/packages/Shell/res/values/strings.xml @@ -4,9 +4,9 @@ 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. @@ -16,6 +16,9 @@ <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_label">Shell</string> + <!-- Title of notification channel for bug report related notifications. [CHAR LIMIT=50] --> + <string name="bugreport_notification_channel">Bug reports</string> + <!-- Title of notification indicating a bugreport is being generated. [CHAR LIMIT=50] --> <string name="bugreport_in_progress_title">Bug report <xliff:g id="id">#%d</xliff:g> is being generated</string> <!-- Title of notification indicating a bugreport has been successfully captured. [CHAR LIMIT=50] --> diff --git a/packages/Shell/src/com/android/shell/BugreportProgressService.java b/packages/Shell/src/com/android/shell/BugreportProgressService.java index 1df626ff9a2f..bf5e6f8590cc 100644 --- a/packages/Shell/src/com/android/shell/BugreportProgressService.java +++ b/packages/Shell/src/com/android/shell/BugreportProgressService.java @@ -57,6 +57,7 @@ import android.annotation.SuppressLint; import android.app.AlertDialog; import android.app.Notification; import android.app.Notification.Action; +import android.app.NotificationChannel; import android.app.NotificationManager; import android.app.PendingIntent; import android.app.Service; @@ -64,6 +65,7 @@ import android.content.ClipData; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; +import android.content.pm.PackageManager; import android.content.res.Configuration; import android.graphics.Bitmap; import android.net.Uri; @@ -196,6 +198,8 @@ public class BugreportProgressService extends Service { */ private static final String SCREENSHOT_DIR = "bugreports"; + private static final String NOTIFICATION_CHANNEL_ID = "bugreports"; + /** Managed dumpstate processes (keyed by id) */ private final SparseArray<DumpstateListener> mProcesses = new SparseArray<>(); @@ -240,6 +244,12 @@ public class BugreportProgressService extends Service { final Configuration conf = mContext.getResources().getConfiguration(); mIsWatch = (conf.uiMode & Configuration.UI_MODE_TYPE_MASK) == Configuration.UI_MODE_TYPE_WATCH; + NotificationManager nm = NotificationManager.from(mContext); + nm.createNotificationChannel( + new NotificationChannel(NOTIFICATION_CHANNEL_ID, + mContext.getString(R.string.bugreport_notification_channel), + isTv(this) ? NotificationManager.IMPORTANCE_DEFAULT + : NotificationManager.IMPORTANCE_LOW)); } @Override @@ -1008,13 +1018,16 @@ public class BugreportProgressService extends Service { sNotificationBundle.putString(Notification.EXTRA_SUBSTITUTE_APP_NAME, context.getString(com.android.internal.R.string.android_system_label)); } - return new Notification.Builder(context) + return new Notification.Builder(context, NOTIFICATION_CHANNEL_ID) .addExtras(sNotificationBundle) .setCategory(Notification.CATEGORY_SYSTEM) - .setSmallIcon(com.android.internal.R.drawable.stat_sys_adb) + .setSmallIcon( + isTv(context) ? R.drawable.ic_bug_report_black_24dp + : com.android.internal.R.drawable.stat_sys_adb) .setLocalOnly(true) .setColor(context.getColor( - com.android.internal.R.color.system_notification_accent_color)); + com.android.internal.R.color.system_notification_accent_color)) + .extend(new Notification.TvExtender()); } /** @@ -1333,6 +1346,10 @@ public class BugreportProgressService extends Service { return false; } + private static boolean isTv(Context context) { + return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK); + } + /** * Checks whether a character is valid on bugreport names. */ diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index dcc5501365b6..4b932a3361e6 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -180,6 +180,9 @@ <!-- accessibility --> <uses-permission android:name="android.permission.MODIFY_ACCESSIBILITY_DATA" /> + <!-- to control accessibility volume --> + <uses-permission android:name="android.permission.BIND_ACCESSIBILITY_SERVICE" /> + <application android:name=".SystemUIApplication" android:persistent="true" @@ -303,20 +306,6 @@ </intent-filter> </activity> - <activity android:name=".recents.tv.RecentsTvActivity" - android:label="@string/accessibility_desc_recent_apps" - android:exported="false" - android:launchMode="singleInstance" - android:excludeFromRecents="true" - android:stateNotNeeded="true" - android:resumeWhilePausing="true" - android:screenOrientation="behind" - android:theme="@style/RecentsTvTheme.Wallpaper"> - <intent-filter> - <action android:name="com.android.systemui.recents.TOGGLE_RECENTS" /> - </intent-filter> - </activity> - <activity android:name=".stackdivider.ForcedResizableInfoActivity" android:theme="@style/ForcedResizableTheme" diff --git a/packages/SystemUI/res/anim/tv_pip_controls_in_recents_focus_gain_animation.xml b/packages/SystemUI/res/anim/tv_pip_controls_in_recents_focus_gain_animation.xml deleted file mode 100644 index 52e3a041d717..000000000000 --- a/packages/SystemUI/res/anim/tv_pip_controls_in_recents_focus_gain_animation.xml +++ /dev/null @@ -1,34 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- 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. ---> - -<set xmlns:android="http://schemas.android.com/apk/res/android"> - - <objectAnimator - android:propertyName="translationY" - android:valueTo="0dp" - android:interpolator="@android:interpolator/fast_out_slow_in" - android:duration="@integer/recents_tv_pip_focus_anim_duration" /> - <objectAnimator - android:propertyName="scaleX" - android:valueTo="1.0" - android:interpolator="@android:interpolator/fast_out_slow_in" - android:duration="@integer/recents_tv_pip_focus_anim_duration" /> - <objectAnimator - android:propertyName="scaleY" - android:valueTo="1.0" - android:interpolator="@android:interpolator/fast_out_slow_in" - android:duration="@integer/recents_tv_pip_focus_anim_duration" /> -</set> diff --git a/packages/SystemUI/res/anim/tv_pip_controls_in_recents_focus_loss_animation.xml b/packages/SystemUI/res/anim/tv_pip_controls_in_recents_focus_loss_animation.xml deleted file mode 100644 index b571aa569f0d..000000000000 --- a/packages/SystemUI/res/anim/tv_pip_controls_in_recents_focus_loss_animation.xml +++ /dev/null @@ -1,34 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- 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. ---> - -<set xmlns:android="http://schemas.android.com/apk/res/android"> - - <objectAnimator - android:propertyName="translationY" - android:valueTo="-57dp" - android:interpolator="@android:interpolator/fast_out_slow_in" - android:duration="@integer/recents_tv_pip_focus_anim_duration" /> - <objectAnimator - android:propertyName="scaleX" - android:valueTo="0.7" - android:interpolator="@android:interpolator/fast_out_slow_in" - android:duration="@integer/recents_tv_pip_focus_anim_duration" /> - <objectAnimator - android:propertyName="scaleY" - android:valueTo="0.7" - android:interpolator="@android:interpolator/fast_out_slow_in" - android:duration="@integer/recents_tv_pip_focus_anim_duration" /> -</set> diff --git a/packages/SystemUI/res/anim/tv_pip_controls_in_recents_scrim_fade_in_animation.xml b/packages/SystemUI/res/anim/tv_pip_controls_in_recents_scrim_fade_in_animation.xml deleted file mode 100644 index 257bf35c8e76..000000000000 --- a/packages/SystemUI/res/anim/tv_pip_controls_in_recents_scrim_fade_in_animation.xml +++ /dev/null @@ -1,21 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- 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. ---> - -<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android" - android:propertyName="alpha" - android:valueTo="1" - android:interpolator="@android:interpolator/fast_out_slow_in" - android:duration="100" /> diff --git a/packages/SystemUI/res/drawable/recents_tv_background_gradient.xml b/packages/SystemUI/res/drawable/recents_tv_background_gradient.xml deleted file mode 100644 index 1e52a91b8944..000000000000 --- a/packages/SystemUI/res/drawable/recents_tv_background_gradient.xml +++ /dev/null @@ -1,22 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- 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. ---> -<shape xmlns:android="http://schemas.android.com/apk/res/android" - android:shape="rectangle"> - <gradient - android:startColor="#4C000000" - android:endColor="#72000000" - android:angle="90"/> -</shape>
\ No newline at end of file diff --git a/packages/SystemUI/res/drawable/recents_tv_card_thumbnail_background.xml b/packages/SystemUI/res/drawable/recents_tv_card_thumbnail_background.xml deleted file mode 100644 index dc8e62975b1b..000000000000 --- a/packages/SystemUI/res/drawable/recents_tv_card_thumbnail_background.xml +++ /dev/null @@ -1,25 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- 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. ---> - -<shape - xmlns:android="http://schemas.android.com/apk/res/android" - android:shape="rectangle"> - - <solid - android:color="@color/recents_tv_card_background_color"/> - <corners - android:radius="@dimen/recents_tv_card_corner_radius" /> -</shape>
\ No newline at end of file diff --git a/packages/SystemUI/res/drawable/recents_tv_dismiss_icon.xml b/packages/SystemUI/res/drawable/recents_tv_dismiss_icon.xml deleted file mode 100644 index 7fb67a2db2a0..000000000000 --- a/packages/SystemUI/res/drawable/recents_tv_dismiss_icon.xml +++ /dev/null @@ -1,19 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- 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. ---> -<transition xmlns:android="http://schemas.android.com/apk/res/android"> - <item android:drawable="@drawable/ic_dismiss_outline" /> - <item android:drawable="@drawable/ic_cancel_white_24dp" /> -</transition>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout-television/recents_on_tv.xml b/packages/SystemUI/res/layout-television/recents_on_tv.xml deleted file mode 100644 index 2b78beef3708..000000000000 --- a/packages/SystemUI/res/layout-television/recents_on_tv.xml +++ /dev/null @@ -1,56 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- 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. ---> -<com.android.systemui.recents.tv.views.RecentsTvView - xmlns:android="http://schemas.android.com/apk/res/android" - android:id="@+id/recents_view" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:clipChildren="false" - android:clipToPadding="false" - android:background="@drawable/recents_tv_background_gradient"> - - <com.android.systemui.recents.tv.views.TaskStackHorizontalGridView - android:id="@+id/task_list" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:clipChildren="false" - android:clipToPadding="false" - android:descendantFocusability="beforeDescendants" - android:layout_marginTop="@dimen/recents_tv_gird_row_top_margin" - android:focusable="true" - android:layoutDirection="rtl" /> - - <!-- Placeholder view to give focus to the PIP menus in talkback mode --> - <View - android:id="@+id/pip" - android:layout_width="1dp" - android:layout_height="1dp" - android:focusable="true" - android:visibility="gone" /> - - <!-- Placeholder to dismiss during talkback. --> - <ImageView - android:id="@+id/dismiss_placeholder" - android:layout_width="@dimen/recents_tv_dismiss_icon_size" - android:layout_height="@dimen/recents_tv_dismiss_icon_size" - android:layout_gravity="bottom|center_horizontal" - android:layout_marginBottom="50dp" - android:src="@drawable/ic_cancel_white_24dp" - android:contentDescription="@string/status_bar_accessibility_dismiss_recents" - android:focusable="true" - android:visibility="gone" /> - -</com.android.systemui.recents.tv.views.RecentsTvView> diff --git a/packages/SystemUI/res/layout-television/recents_tv_card_info_field.xml b/packages/SystemUI/res/layout-television/recents_tv_card_info_field.xml deleted file mode 100644 index 34cba07d7ac0..000000000000 --- a/packages/SystemUI/res/layout-television/recents_tv_card_info_field.xml +++ /dev/null @@ -1,46 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- 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. ---> -<LinearLayout - xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:tools="http://schemas.android.com/tools" - android:id="@+id/card_info_field" - android:layout_width="match_parent" - android:layout_height="wrap_content"> - <ImageView - android:id="@+id/card_extra_badge" - android:layout_width="@dimen/recents_tv_card_extra_badge_size" - android:layout_height="@dimen/recents_tv_card_extra_badge_size" - android:layout_marginBottom="@dimen/recents_tv_icon_padding_bottom" - android:scaleType="fitCenter" - android:layout_centerVertical="true" - android:layout_alignParentEnd="true"/> - <TextView - android:id="@+id/card_title_text" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:includeFontPadding="true" - android:singleLine="true" - android:shadowColor="@color/recents_tv_text_shadow_color" - android:shadowRadius="5" - android:shadowDx="0" - android:shadowDy="0" - android:textColor="@color/recents_tv_card_title_text_color" - android:fontFamily="@string/font_roboto_regular" - android:textSize="@dimen/recents_tv_title_text_size" - android:paddingStart="@dimen/recents_tv_text_padding_start" - android:layout_marginBottom="@dimen/recents_tv_text_padding_bottom" - android:ellipsize="end"/> -</LinearLayout> diff --git a/packages/SystemUI/res/layout-television/recents_tv_empty.xml b/packages/SystemUI/res/layout-television/recents_tv_empty.xml deleted file mode 100644 index e5d888f826a3..000000000000 --- a/packages/SystemUI/res/layout-television/recents_tv_empty.xml +++ /dev/null @@ -1,28 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- 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. ---> - -<TextView - xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center" - android:gravity="center" - android:drawablePadding="25dp" - android:textSize="16sp" - android:textColor="#ffffffff" - android:text="@string/recents_empty_message" - android:fontFamily="sans-serif" - android:visibility="gone" />
\ No newline at end of file diff --git a/packages/SystemUI/res/layout-television/recents_tv_task_card_view.xml b/packages/SystemUI/res/layout-television/recents_tv_task_card_view.xml deleted file mode 100644 index 201f47d4348c..000000000000 --- a/packages/SystemUI/res/layout-television/recents_tv_task_card_view.xml +++ /dev/null @@ -1,64 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- 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. ---> -<com.android.systemui.recents.tv.views.TaskCardView - xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:focusable="true" - android:focusableInTouchMode="true" - android:layout_gravity="center" - android:layout_centerInParent="true" - android:clipToPadding="false" - android:orientation="vertical" > - <include layout="@layout/recents_tv_card_info_field"/> - <LinearLayout - android:id="@+id/card_view_thumbnail" - android:layout_width="@dimen/recents_tv_card_width" - android:layout_height="@dimen/recents_tv_screenshot_height" - android:gravity="center" - android:orientation="vertical" - android:background="@drawable/recents_tv_card_thumbnail_background" - android:layout_centerHorizontal="true" > - - <ImageView - android:id="@+id/card_view_banner_icon" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:layout_centerHorizontal="true" - android:scaleType="centerCrop" - android:gravity="center" /> - - </LinearLayout> - <ImageView - android:id="@+id/dismiss_icon" - android:layout_width="@dimen/recents_tv_dismiss_icon_size" - android:layout_height="@dimen/recents_tv_dismiss_icon_size" - android:layout_gravity="center_horizontal" - android:layout_marginTop="@dimen/recents_tv_dismiss_icon_top_margin" - android:layout_marginBottom="@dimen/recents_tv_dismiss_icon_bottom_margin" - android:alpha="@integer/dismiss_unselected_alpha" - android:src="@drawable/recents_tv_dismiss_icon" /> - <TextView - android:id="@+id/card_dismiss_text" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:textSize="@dimen/recents_tv_dismiss_text_size" - android:fontFamily="@string/font_roboto_light" - android:textColor="@color/recents_tv_dismiss_text_color" - android:text="@string/recents_tv_dismiss" - android:alpha="0.0" - android:layout_gravity="center_horizontal" /> -</com.android.systemui.recents.tv.views.TaskCardView>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/pip_menu_activity.xml b/packages/SystemUI/res/layout/pip_menu_activity.xml index 5e49d058cd84..c6837fa30925 100644 --- a/packages/SystemUI/res/layout/pip_menu_activity.xml +++ b/packages/SystemUI/res/layout/pip_menu_activity.xml @@ -15,50 +15,55 @@ --> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:id="@+id/menu" + android:id="@+id/background" android:layout_width="match_parent" - android:layout_height="match_parent" - android:background="#4D000000"> - <!-- The above background is only for the dismiss button ripple to show. --> + android:layout_height="match_parent"> - <ImageView - android:id="@+id/dismiss" - android:layout_width="@dimen/pip_action_size" - android:layout_height="@dimen/pip_action_size" - android:layout_gravity="top|end" - android:padding="@dimen/pip_action_padding" - android:contentDescription="@string/pip_phone_close" - android:src="@drawable/ic_close_white" - android:background="?android:selectableItemBackgroundBorderless" /> + <!-- Menu layout --> + <FrameLayout + android:id="@+id/menu_container" + android:layout_width="match_parent" + android:layout_height="match_parent"> - <!-- The margins for this container is calculated in the code depending on whether the - actions_container is visible. --> - <FrameLayout - android:id="@+id/expand_container" - android:layout_width="match_parent" - android:layout_height="match_parent"> - <ImageView - android:id="@+id/expand_button" - android:layout_width="60dp" - android:layout_height="60dp" - android:layout_gravity="center" - android:contentDescription="@string/pip_phone_expand" - android:background="?android:selectableItemBackgroundBorderless" /> - </FrameLayout> + <ImageView + android:id="@+id/dismiss" + android:layout_width="@dimen/pip_action_size" + android:layout_height="@dimen/pip_action_size" + android:layout_gravity="top|end" + android:padding="@dimen/pip_action_padding" + android:contentDescription="@string/pip_phone_close" + android:src="@drawable/ic_close_white" + android:background="?android:selectableItemBackgroundBorderless" /> - <FrameLayout - android:id="@+id/actions_container" - android:layout_width="match_parent" - android:layout_height="@dimen/pip_action_size" - android:layout_gravity="bottom" - android:visibility="invisible"> - <LinearLayout - android:id="@+id/actions_group" - android:layout_width="wrap_content" - android:layout_height="match_parent" - android:layout_gravity="center_horizontal" - android:orientation="horizontal" - android:divider="@android:color/transparent" - android:showDividers="middle" /> - </FrameLayout> + <!-- The margins for this container is calculated in the code depending on whether the + actions_container is visible. --> + <FrameLayout + android:id="@+id/expand_container" + android:layout_width="match_parent" + android:layout_height="match_parent"> + <ImageView + android:id="@+id/expand_button" + android:layout_width="60dp" + android:layout_height="60dp" + android:layout_gravity="center" + android:contentDescription="@string/pip_phone_expand" + android:background="?android:selectableItemBackgroundBorderless" /> + </FrameLayout> + + <FrameLayout + android:id="@+id/actions_container" + android:layout_width="match_parent" + android:layout_height="@dimen/pip_action_size" + android:layout_gravity="bottom" + android:visibility="invisible"> + <LinearLayout + android:id="@+id/actions_group" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:layout_gravity="center_horizontal" + android:orientation="horizontal" + android:divider="@android:color/transparent" + android:showDividers="middle" /> + </FrameLayout> + </FrameLayout> </FrameLayout> diff --git a/packages/SystemUI/res/layout/quick_settings_footer_dialog.xml b/packages/SystemUI/res/layout/quick_settings_footer_dialog.xml index 2ba04fd31c0f..397fbf10dc81 100644 --- a/packages/SystemUI/res/layout/quick_settings_footer_dialog.xml +++ b/packages/SystemUI/res/layout/quick_settings_footer_dialog.xml @@ -88,7 +88,6 @@ android:scaleType="fitCenter" android:src="@drawable/ic_qs_network_logging" android:tint="?android:attr/textColorPrimaryInverse" - android:alpha="@dimen/qs_footer_dialog_network_logging_icon_alpha" android:adjustViewBounds="true"/> <LinearLayout android:layout_width="match_parent" diff --git a/packages/SystemUI/res/layout/tv_pip_recents_overlay.xml b/packages/SystemUI/res/layout/tv_pip_recents_overlay.xml deleted file mode 100644 index 949400c77d79..000000000000 --- a/packages/SystemUI/res/layout/tv_pip_recents_overlay.xml +++ /dev/null @@ -1,53 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- 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. ---> -<LinearLayout - xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:gravity="top|center_horizontal" - android:orientation="vertical"> - - <com.android.systemui.pip.tv.PipRecentsControlsView - android:id="@+id/pip_controls" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center_horizontal" - android:clipToPadding="false"> - - <View - android:id="@+id/scrim" - android:layout_width="160dp" - android:layout_height="32dp" - android:translationY="-46dp" - android:layout_gravity="top|center_horizontal" - android:background="@drawable/tv_pip_recents_overlay_scrim" - android:alpha="0" /> - <com.android.systemui.pip.tv.PipControlsView - android:id="@+id/pip_control_contents" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginTop="10dp" - android:layout_gravity="top|center_horizontal" /> - </com.android.systemui.pip.tv.PipRecentsControlsView> - - <!-- Placeholder view to handle focus change between Recents row and PIP controls - in talkback mode --> - <View - android:id="@+id/recents" - android:layout_width="1dp" - android:layout_height="1dp" - android:focusable="true" /> -</LinearLayout> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index ffaa7bae959c..72bdbf124c18 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -279,8 +279,6 @@ <dimen name="qs_footer_dialog_icon_size">24sp</dimen> <!-- Left and right margin of the icons --> <dimen name="qs_footer_dialog_icon_margin">8sp</dimen> - <!-- Alpha value of network logging icon --> - <dimen name="qs_footer_dialog_network_logging_icon_alpha">0.3</dimen> <!-- Zen mode panel: condition item button padding --> <dimen name="zen_mode_condition_detail_button_padding">8dp</dimen> diff --git a/packages/SystemUI/res/values/dimens_tv.xml b/packages/SystemUI/res/values/dimens_tv.xml index 23192291c6ef..30355d47e957 100644 --- a/packages/SystemUI/res/values/dimens_tv.xml +++ b/packages/SystemUI/res/values/dimens_tv.xml @@ -17,44 +17,6 @@ */ --> <resources> - <!-- Dimens for recents card in the recents view on tv --> - <dimen name="recents_tv_card_width">240dip</dimen> - <dimen name="recents_tv_screenshot_height">135dip</dimen> - <dimen name="recents_tv_card_extra_badge_size">20dip</dimen> - <dimen name="recents_tv_banner_width">130dip</dimen> - <dimen name="recents_tv_banner_height">72dip</dimen> - <dimen name="recents_tv_fallback_icon_width">40dip</dimen> - <dimen name="recents_tv_fallback_icon_height">40dip</dimen> - <dimen name="recents_tv_banner_margin_top">16dip</dimen> - <dimen name="recents_tv_icon_padding_bottom">8dip</dimen> - <dimen name="recents_tv_text_padding_start">12dip</dimen> - <dimen name="recents_tv_text_padding_bottom">12dip</dimen> - <dimen name="recents_tv_card_corner_radius">2dip</dimen> - - <!-- Padding for grid view in recents view on tv --> - <dimen name="recents_tv_gird_row_top_margin">215dip</dimen> - <dimen name="recents_tv_grid_max_row_height">268dip</dimen> - <dimen name="recents_tv_gird_card_spacing">8dip</dimen> - <dimen name="recents_tv_gird_focused_card_delta">44dip</dimen> - - <!-- Values for focus animation --> - <dimen name="recents_tv_unselected_item_z">6dp</dimen> - <dimen name="recents_tv_selected_item_z_delta">10dp</dimen> - - <!-- Values for text on recents cards on tv --> - <dimen name="recents_tv_title_text_size">12sp</dimen> - - <!-- Values for card dismiss state --> - <dimen name="recents_tv_dismiss_shift_down">48dip</dimen> - <dimen name="recents_tv_dismiss_top_margin">356dip</dimen> - <dimen name="recents_tv_dismiss_icon_size">19dip</dimen> - <dimen name="recents_tv_dismiss_icon_top_margin">38dip</dimen> - <dimen name="recents_tv_dismiss_icon_bottom_margin">1dip</dimen> - <dimen name="recents_tv_dismiss_text_size">12sp</dimen> - <!-- Extra space around the PIP and its outline in PIP onboarding activity --> <dimen name="tv_pip_bounds_space">3dp</dimen> - - <!-- Values for entering Recents and exiting Recents --> - <dimen name="recents_tv_home_recents_shift">125dip</dimen> </resources> diff --git a/packages/SystemUI/res/values/integers_tv.xml b/packages/SystemUI/res/values/integers_tv.xml index 3b62938c38aa..09547dad0705 100644 --- a/packages/SystemUI/res/values/integers_tv.xml +++ b/packages/SystemUI/res/values/integers_tv.xml @@ -14,17 +14,6 @@ limitations under the License. --> <resources> - <integer name="item_scale_anim_duration">150</integer> - <integer name="dismiss_short_duration">200</integer> - <integer name="dismiss_long_duration">400</integer> - - <integer name="recents_tv_pip_focus_anim_duration">200</integer> - - <!-- Duration for how long it takes cards to slide in or out when going to and from recents. --> - <integer name="recents_home_duration">400</integer> - <!-- Delay between the start of slide in animation for each card. --> - <integer name="recents_home_delay">40</integer> - <!-- Delay of the onboarding animation start after it launches --> <integer name="tv_pip_onboarding_anim_start_delay">1000</integer> <!-- Duration of the onboarding animation duration --> diff --git a/packages/SystemUI/res/values/strings_tv.xml b/packages/SystemUI/res/values/strings_tv.xml index f49d2019873c..fb9e1f25f4dd 100644 --- a/packages/SystemUI/res/values/strings_tv.xml +++ b/packages/SystemUI/res/values/strings_tv.xml @@ -37,15 +37,4 @@ <string name="pip_onboarding_description">This keeps your video in view until you play another one. Press and hold <b>HOME</b> to control it.</string> <!-- Button to close picture-in-picture (PIP) onboarding screen. --> <string name="pip_onboarding_button">Got it</string> - <!-- Dismiss icon description --> - <string name="recents_tv_dismiss">Dismiss</string> - <!-- Font for Recents --> - <!-- DO NOT TRANSLATE --> - <string name="font_roboto_regular" translatable="false">sans-serif</string> - <!-- DO NOT TRANSLATE --> - <string name="font_roboto_light" translatable="false">sans-serif-light</string> - <!-- Package names to be blacklisted in Recents, add package names into overlay as needed --> - <string-array name="recents_tv_blacklist_array"> - </string-array> - </resources> diff --git a/packages/SystemUI/res/values/styles_tv.xml b/packages/SystemUI/res/values/styles_tv.xml index 263e1a4cccf4..3f0caab23ab4 100644 --- a/packages/SystemUI/res/values/styles_tv.xml +++ b/packages/SystemUI/res/values/styles_tv.xml @@ -22,11 +22,4 @@ <item name="android:windowBackground">@android:color/transparent</item> <item name="android:backgroundDimEnabled">false</item> </style> - - <style name="RecentsTvTheme.Wallpaper" parent="@android:style/Theme.Material.NoActionBar.Overscan"> - <item name="android:windowBackground">@android:color/transparent</item> - <item name="android:backgroundDimEnabled">false</item> - <item name="android:colorBackgroundCacheHint">@null</item> - <item name="android:windowIsTranslucent">true</item> - </style> </resources> diff --git a/packages/SystemUI/res/values/values_tv.xml b/packages/SystemUI/res/values/values_tv.xml deleted file mode 100644 index 925941560c83..000000000000 --- a/packages/SystemUI/res/values/values_tv.xml +++ /dev/null @@ -1,20 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- 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. ---> -<resources xmlns:android="http://schemas.android.com/apk/res/android"> - <item format="float" type="integer" name="unselected_scale">1.0</item> - <item format="float" type="integer" name="selected_scale">1.259</item> - <item format="float" type="integer" name="dismiss_unselected_alpha">0.3</item> -</resources> diff --git a/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java b/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java index ee8641bbc5cf..14f2c4aea667 100644 --- a/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java +++ b/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java @@ -15,6 +15,8 @@ */ package com.android.systemui; +import static android.provider.Settings.System.SHOW_BATTERY_PERCENT; + import android.content.Context; import android.content.res.Resources; import android.content.res.TypedArray; @@ -50,8 +52,6 @@ import java.text.NumberFormat; public class BatteryMeterView extends LinearLayout implements BatteryStateChangeCallback, Tunable, DarkReceiver, ConfigurationListener { - public static final String SHOW_PERCENT_SETTING = "status_bar_show_battery_percent"; - private final BatteryMeterDrawableBase mDrawable; private final String mSlotBattery; private final ImageView mBatteryIconView; @@ -129,7 +129,7 @@ public class BatteryMeterView extends LinearLayout implements mBatteryController = Dependency.get(BatteryController.class); mBatteryController.addCallback(this); getContext().getContentResolver().registerContentObserver( - Settings.System.getUriFor(SHOW_PERCENT_SETTING), false, mSettingObserver); + Settings.System.getUriFor(SHOW_BATTERY_PERCENT), false, mSettingObserver); updateShowPercent(); Dependency.get(TunerService.class).addTunable(this, StatusBarIconController.ICON_BLACKLIST); Dependency.get(ConfigurationController.class).addCallback(this); @@ -175,7 +175,7 @@ public class BatteryMeterView extends LinearLayout implements private void updateShowPercent() { final boolean showing = mBatteryPercentView != null; if (0 != Settings.System.getInt(getContext().getContentResolver(), - BatteryMeterView.SHOW_PERCENT_SETTING, 0) || mForceShowPercent) { + SHOW_BATTERY_PERCENT, 0) || mForceShowPercent) { if (!showing) { mBatteryPercentView = loadPercentView(); if (mTextColor != 0) mBatteryPercentView.setTextColor(mTextColor); diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java index bb7e19de0689..4dfaf452fd51 100644 --- a/packages/SystemUI/src/com/android/systemui/Dependency.java +++ b/packages/SystemUI/src/com/android/systemui/Dependency.java @@ -30,6 +30,7 @@ import com.android.systemui.fragments.FragmentService; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.PluginDependencyProvider; import com.android.systemui.plugins.PluginManager; +import com.android.systemui.plugins.PluginManagerImpl; import com.android.systemui.statusbar.phone.ConfigurationControllerImpl; import com.android.systemui.statusbar.phone.DarkIconDispatcherImpl; import com.android.systemui.statusbar.phone.ManagedProfileController; @@ -73,6 +74,7 @@ import com.android.systemui.statusbar.policy.UserSwitcherController; import com.android.systemui.statusbar.policy.ZenModeController; import com.android.systemui.statusbar.policy.ZenModeControllerImpl; import com.android.systemui.tuner.TunerService; +import com.android.systemui.tuner.TunerServiceImpl; import com.android.systemui.util.leak.GarbageMonitor; import com.android.systemui.util.leak.LeakDetector; import com.android.systemui.util.leak.LeakReporter; @@ -200,7 +202,7 @@ public class Dependency extends SystemUI { new DeviceProvisionedControllerImpl(mContext)); mProviders.put(PluginManager.class, () -> - new PluginManager(mContext)); + new PluginManagerImpl(mContext)); mProviders.put(AssistManager.class, () -> new AssistManager(getDependency(DeviceProvisionedController.class), mContext)); @@ -223,7 +225,7 @@ public class Dependency extends SystemUI { getDependency(LeakReporter.class))); mProviders.put(TunerService.class, () -> - new TunerService(mContext)); + new TunerServiceImpl(mContext)); mProviders.put(StatusBarWindowManager.class, () -> new StatusBarWindowManager(mContext)); diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/InputConsumerController.java b/packages/SystemUI/src/com/android/systemui/pip/phone/InputConsumerController.java index 762d6bcb5188..867c15c4736f 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/InputConsumerController.java +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/InputConsumerController.java @@ -98,6 +98,9 @@ public class InputConsumerController { */ public void setRegistrationListener(RegistrationListener listener) { mRegistrationListener = listener; + if (mRegistrationListener != null) { + mRegistrationListener.onRegistrationChanged(mInputEventReceiver != null); + } } /** @@ -122,6 +125,9 @@ public class InputConsumerController { Log.e(TAG, "Failed to create PIP input consumer", e); } mInputEventReceiver = new PipInputEventReceiver(inputChannel, Looper.myLooper()); + if (mRegistrationListener != null) { + mRegistrationListener.onRegistrationChanged(true /* isRegistered */); + } } } @@ -137,6 +143,9 @@ public class InputConsumerController { } mInputEventReceiver.dispose(); mInputEventReceiver = null; + if (mRegistrationListener != null) { + mRegistrationListener.onRegistrationChanged(false /* isRegistered */); + } } } diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipAccessibilityInteractionConnection.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipAccessibilityInteractionConnection.java new file mode 100644 index 000000000000..968bd283c4b1 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipAccessibilityInteractionConnection.java @@ -0,0 +1,191 @@ +/* + * 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.pip.phone; + +import android.graphics.Rect; +import android.graphics.Region; +import android.os.Bundle; +import android.os.Handler; +import android.os.RemoteException; +import android.view.MagnificationSpec; +import android.view.accessibility.AccessibilityNodeInfo; +import android.view.accessibility.AccessibilityWindowInfo; +import android.view.accessibility.IAccessibilityInteractionConnection; +import android.view.accessibility.IAccessibilityInteractionConnectionCallback; + +import java.util.ArrayList; +import java.util.List; + +/** + * Expose the touch actions to accessibility as if this object were a window with a single view. + * That pseudo-view exposes all of the actions this object can perform. + */ +public class PipAccessibilityInteractionConnection + extends IAccessibilityInteractionConnection.Stub { + + public interface AccessibilityCallbacks { + void onAccessibilityShowMenu(); + } + + private static final long ACCESSIBILITY_NODE_ID = 1; + private List<AccessibilityNodeInfo> mAccessibilityNodeInfoList; + + private Handler mHandler; + private PipMotionHelper mMotionHelper; + private AccessibilityCallbacks mCallbacks; + + private Rect mTmpBounds = new Rect(); + + public PipAccessibilityInteractionConnection(PipMotionHelper motionHelper, + AccessibilityCallbacks callbacks, Handler handler) { + mHandler = handler; + mMotionHelper = motionHelper; + mCallbacks = callbacks; + } + + @Override + public void findAccessibilityNodeInfoByAccessibilityId(long accessibilityNodeId, + Region interactiveRegion, int interactionId, + IAccessibilityInteractionConnectionCallback callback, int flags, + int interrogatingPid, long interrogatingTid, MagnificationSpec spec, Bundle args) { + try { + callback.setFindAccessibilityNodeInfosResult( + (accessibilityNodeId == AccessibilityNodeInfo.ROOT_NODE_ID) + ? getNodeList() : null, interactionId); + } catch (RemoteException re) { + /* best effort - ignore */ + } + } + + @Override + public void performAccessibilityAction(long accessibilityNodeId, int action, + Bundle arguments, int interactionId, + IAccessibilityInteractionConnectionCallback callback, int flags, + int interrogatingPid, long interrogatingTid) { + // We only support one view. A request for anything else is invalid + boolean result = false; + if (accessibilityNodeId == AccessibilityNodeInfo.ROOT_NODE_ID) { + switch (action) { + case AccessibilityNodeInfo.ACTION_CLICK: + mHandler.post(() -> { + mCallbacks.onAccessibilityShowMenu(); + }); + result = true; + break; + case AccessibilityNodeInfo.ACTION_DISMISS: + mMotionHelper.dismissPip(); + result = true; + break; + case com.android.internal.R.id.accessibilityActionMoveWindow: + int newX = arguments.getInt( + AccessibilityNodeInfo.ACTION_ARGUMENT_MOVE_WINDOW_X); + int newY = arguments.getInt( + AccessibilityNodeInfo.ACTION_ARGUMENT_MOVE_WINDOW_Y); + Rect pipBounds = new Rect(); + pipBounds.set(mMotionHelper.getBounds()); + mTmpBounds.offsetTo(newX, newY); + mMotionHelper.movePip(mTmpBounds); + result = true; + break; + case AccessibilityNodeInfo.ACTION_EXPAND: + mMotionHelper.expandPip(); + result = true; + break; + default: + // Leave result as false + } + } + try { + callback.setPerformAccessibilityActionResult(result, interactionId); + } catch (RemoteException re) { + /* best effort - ignore */ + } + } + + @Override + public void findAccessibilityNodeInfosByViewId(long accessibilityNodeId, + String viewId, Region interactiveRegion, int interactionId, + IAccessibilityInteractionConnectionCallback callback, int flags, + int interrogatingPid, long interrogatingTid, MagnificationSpec spec) { + // We have no view with a proper ID + try { + callback.setFindAccessibilityNodeInfoResult(null, interactionId); + } catch (RemoteException re) { + /* best effort - ignore */ + } + } + + @Override + public void findAccessibilityNodeInfosByText(long accessibilityNodeId, String text, + Region interactiveRegion, int interactionId, + IAccessibilityInteractionConnectionCallback callback, int flags, + int interrogatingPid, long interrogatingTid, MagnificationSpec spec) { + // We have no view with text + try { + callback.setFindAccessibilityNodeInfoResult(null, interactionId); + } catch (RemoteException re) { + /* best effort - ignore */ + } + } + + @Override + public void findFocus(long accessibilityNodeId, int focusType, Region interactiveRegion, + int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags, + int interrogatingPid, long interrogatingTid, MagnificationSpec spec) { + // We have no view that can take focus + try { + callback.setFindAccessibilityNodeInfoResult(null, interactionId); + } catch (RemoteException re) { + /* best effort - ignore */ + } + } + + @Override + public void focusSearch(long accessibilityNodeId, int direction, Region interactiveRegion, + int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags, + int interrogatingPid, long interrogatingTid, MagnificationSpec spec) { + // We have no view that can take focus + try { + callback.setFindAccessibilityNodeInfoResult(null, interactionId); + } catch (RemoteException re) { + /* best effort - ignore */ + } + } + + public static AccessibilityNodeInfo obtainRootAccessibilityNodeInfo() { + AccessibilityNodeInfo info = AccessibilityNodeInfo.obtain(); + info.setSourceNodeId(AccessibilityNodeInfo.ROOT_NODE_ID, + AccessibilityWindowInfo.PICTURE_IN_PICTURE_ACTION_REPLACER_WINDOW_ID); + info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK); + info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_DISMISS); + info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_MOVE_WINDOW); + info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_EXPAND); + info.setImportantForAccessibility(true); + info.setClickable(true); + info.setVisibleToUser(true); + return info; + } + + private List<AccessibilityNodeInfo> getNodeList() { + if (mAccessibilityNodeInfoList == null) { + mAccessibilityNodeInfoList = new ArrayList<>(1); + } + AccessibilityNodeInfo info = obtainRootAccessibilityNodeInfo(); + mAccessibilityNodeInfoList.clear(); + mAccessibilityNodeInfoList.add(info); + return mAccessibilityNodeInfoList; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java index 86e2c4956070..2f9c3fc3368a 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java @@ -20,10 +20,12 @@ import static com.android.systemui.pip.phone.PipMenuActivityController.EXTRA_ACT import static com.android.systemui.pip.phone.PipMenuActivityController.EXTRA_CONTROLLER_MESSENGER; import static com.android.systemui.pip.phone.PipMenuActivityController.EXTRA_MOVEMENT_BOUNDS; import static com.android.systemui.pip.phone.PipMenuActivityController.EXTRA_STACK_BOUNDS; +import static com.android.systemui.pip.phone.PipMenuActivityController.EXTRA_SHOW_MENU; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ObjectAnimator; +import android.animation.ValueAnimator; import android.annotation.Nullable; import android.app.Activity; import android.app.ActivityManager; @@ -35,6 +37,8 @@ import android.content.res.Resources; import android.graphics.Color; import android.graphics.PointF; import android.graphics.Rect; +import android.graphics.drawable.ColorDrawable; +import android.graphics.drawable.Drawable; import android.graphics.drawable.Icon; import android.os.Bundle; import android.os.Handler; @@ -71,13 +75,19 @@ public class PipMenuActivity extends Activity { public static final int MESSAGE_POKE_MENU = 2; public static final int MESSAGE_HIDE_MENU = 3; public static final int MESSAGE_UPDATE_ACTIONS = 4; + public static final int MESSAGE_UPDATE_DISMISS_FRACTION = 5; private static final long INITIAL_DISMISS_DELAY = 2000; private static final long POST_INTERACTION_DISMISS_DELAY = 1500; private static final long MENU_FADE_DURATION = 125; + private static final float MENU_BACKGROUND_ALPHA = 0.3f; + private static final float DISMISS_BACKGROUND_ALPHA = 0.8f; + private boolean mMenuVisible; private final List<RemoteAction> mActions = new ArrayList<>(); + private View mViewRoot; + private Drawable mBackgroundDrawable; private View mMenuContainer; private LinearLayout mActionsGroup; private View mDismissButton; @@ -85,6 +95,14 @@ public class PipMenuActivity extends Activity { private int mBetweenActionPaddingLand; private ObjectAnimator mMenuContainerAnimator; + private ValueAnimator.AnimatorUpdateListener mMenuBgUpdateListener = + new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + final float alpha = (float) animation.getAnimatedValue(); + mBackgroundDrawable.setAlpha((int) (MENU_BACKGROUND_ALPHA*alpha*255)); + } + }; private PointF mDownPosition = new PointF(); private PointF mDownDelta = new PointF(); @@ -109,6 +127,10 @@ public class PipMenuActivity extends Activity { Pair<Rect, ParceledListSlice> data = (Pair<Rect, ParceledListSlice>) msg.obj; setActions(data.first, data.second.getList()); break; + case MESSAGE_UPDATE_DISMISS_FRACTION: + float fraction = (float) msg.obj; + updateDismissFraction(fraction); + break; } } }); @@ -130,7 +152,12 @@ public class PipMenuActivity extends Activity { super.onCreate(savedInstanceState); setContentView(R.layout.pip_menu_activity); - mMenuContainer = findViewById(R.id.menu); + mBackgroundDrawable = new ColorDrawable(Color.BLACK); + mBackgroundDrawable.setAlpha(0); + mViewRoot = findViewById(R.id.background); + mViewRoot.setBackground(mBackgroundDrawable); + mMenuContainer = findViewById(R.id.menu_container); + mMenuContainer.setAlpha(0); mMenuContainer.setOnClickListener((v) -> { expandPip(); }); @@ -222,10 +249,10 @@ public class PipMenuActivity extends Activity { 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 @@ public class PipMenuActivity extends Activity { repostDelayedFinish(INITIAL_DISMISS_DELAY); } }); + mMenuContainerAnimator.addUpdateListener(mMenuBgUpdateListener); mMenuContainerAnimator.start(); } else { repostDelayedFinish(POST_INTERACTION_DISMISS_DELAY); @@ -269,20 +297,24 @@ public class PipMenuActivity extends Activity { } } }); + mMenuContainerAnimator.addUpdateListener(mMenuBgUpdateListener); mMenuContainerAnimator.start(); } } private void updateFromIntent(Intent intent) { - Rect stackBounds = Rect.unflattenFromString(intent.getStringExtra(EXTRA_STACK_BOUNDS)); - Rect movementBounds = Rect.unflattenFromString(intent.getStringExtra( - EXTRA_MOVEMENT_BOUNDS)); mToControllerMessenger = intent.getParcelableExtra(EXTRA_CONTROLLER_MESSENGER); ParceledListSlice actions = intent.getParcelableExtra(EXTRA_ACTIONS); if (actions != null) { - setActions(stackBounds, actions.getList()); + mActions.clear(); + mActions.addAll(actions.getList()); + } + if (intent.getBooleanExtra(EXTRA_SHOW_MENU, false)) { + Rect stackBounds = Rect.unflattenFromString(intent.getStringExtra(EXTRA_STACK_BOUNDS)); + Rect movementBounds = Rect.unflattenFromString(intent.getStringExtra( + EXTRA_MOVEMENT_BOUNDS)); + showMenu(stackBounds, movementBounds); } - showMenu(stackBounds, movementBounds); } private void updateExpandButtonFromBounds(Rect stackBounds, Rect movementBounds) { @@ -365,6 +397,19 @@ public class PipMenuActivity extends Activity { } } + 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 badf64b90b8f..7dc455bd0d2c 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java @@ -42,7 +42,7 @@ import java.util.ArrayList; import java.util.List; /** - * Manages the PiP menu activity. + * Manages the PiP menu activity which can show menu options or a scrim. * * The current media session provides actions whenever there are no valid actions provided by the * current PiP activity. Otherwise, those actions always take precedence. @@ -55,6 +55,7 @@ public class PipMenuActivityController { public static final String EXTRA_ACTIONS = "actions"; public static final String EXTRA_STACK_BOUNDS = "stack_bounds"; public static final String EXTRA_MOVEMENT_BOUNDS = "movement_bounds"; + public static final String EXTRA_SHOW_MENU = "show_menu"; public static final int MESSAGE_MENU_VISIBILITY_CHANGED = 100; public static final int MESSAGE_EXPAND_PIP = 101; @@ -101,6 +102,7 @@ public class PipMenuActivityController { private ParceledListSlice mMediaActions; private boolean mMenuVisible; + private boolean mStartActivityRequested; private Messenger mToActivityMessenger; private Messenger mMessenger = new Messenger(new Handler() { @Override @@ -135,6 +137,7 @@ public class PipMenuActivityController { } 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 @@ public class PipMenuActivityController { } /** + * Updates the appearance of the menu and scrim on top of the PiP while dismissing. + */ + public void setDismissFraction(float fraction) { + if (mToActivityMessenger != null) { + Message m = Message.obtain(); + m.what = PipMenuActivity.MESSAGE_UPDATE_DISMISS_FRACTION; + m.obj = fraction; + try { + mToActivityMessenger.send(m); + } catch (RemoteException e) { + Log.e(TAG, "Could not notify menu to show", e); + } + } else if (!mStartActivityRequested) { + startMenuActivity(null /* stackBounds */, null /* movementBounds */, + false /* showMenu */); + } + } + + /** * Shows the menu activity. */ public void showMenu(Rect stackBounds, Rect movementBounds) { @@ -191,28 +213,8 @@ public class PipMenuActivityController { } catch (RemoteException e) { Log.e(TAG, "Could not notify menu to show", e); } - } else { - // Start the menu activity on the top task of the pinned stack - try { - StackInfo pinnedStackInfo = mActivityManager.getStackInfo(PINNED_STACK_ID); - if (pinnedStackInfo != null && pinnedStackInfo.taskIds != null && - pinnedStackInfo.taskIds.length > 0) { - Intent intent = new Intent(mContext, PipMenuActivity.class); - intent.putExtra(EXTRA_CONTROLLER_MESSENGER, mMessenger); - intent.putExtra(EXTRA_ACTIONS, resolveMenuActions()); - intent.putExtra(EXTRA_STACK_BOUNDS, stackBounds.flattenToString()); - intent.putExtra(EXTRA_MOVEMENT_BOUNDS, movementBounds.flattenToString()); - ActivityOptions options = ActivityOptions.makeCustomAnimation(mContext, 0, 0); - options.setLaunchTaskId( - pinnedStackInfo.taskIds[pinnedStackInfo.taskIds.length - 1]); - options.setTaskOverlay(true, true /* canResume */); - mContext.startActivityAsUser(intent, options.toBundle(), UserHandle.CURRENT); - } else { - Log.e(TAG, "No PIP tasks found"); - } - } catch (RemoteException e) { - Log.e(TAG, "Error showing PIP menu activity", e); - } + } else if (!mStartActivityRequested) { + startMenuActivity(stackBounds, movementBounds, true /* showMenu */); } } @@ -272,6 +274,39 @@ public class PipMenuActivityController { } /** + * Starts the menu activity on the top task of the pinned stack. + */ + private void startMenuActivity(Rect stackBounds, Rect movementBounds, boolean showMenu) { + try { + StackInfo pinnedStackInfo = mActivityManager.getStackInfo(PINNED_STACK_ID); + if (pinnedStackInfo != null && pinnedStackInfo.taskIds != null && + pinnedStackInfo.taskIds.length > 0) { + Intent intent = new Intent(mContext, PipMenuActivity.class); + intent.putExtra(EXTRA_CONTROLLER_MESSENGER, mMessenger); + intent.putExtra(EXTRA_ACTIONS, resolveMenuActions()); + if (stackBounds != null) { + intent.putExtra(EXTRA_STACK_BOUNDS, stackBounds.flattenToString()); + } + if (movementBounds != null) { + intent.putExtra(EXTRA_MOVEMENT_BOUNDS, movementBounds.flattenToString()); + } + intent.putExtra(EXTRA_SHOW_MENU, showMenu); + ActivityOptions options = ActivityOptions.makeCustomAnimation(mContext, 0, 0); + options.setLaunchTaskId( + pinnedStackInfo.taskIds[pinnedStackInfo.taskIds.length - 1]); + options.setTaskOverlay(true, true /* canResume */); + mContext.startActivityAsUser(intent, options.toBundle(), UserHandle.CURRENT); + mStartActivityRequested = true; + } else { + Log.e(TAG, "No PIP tasks found"); + } + } catch (RemoteException e) { + mStartActivityRequested = false; + Log.e(TAG, "Error showing PIP menu activity", e); + } + } + + /** * Updates the PiP menu activity with the best set of actions provided. */ private void updateMenuActions() { diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java index 49d89a2bdd68..c4cf28c9a320 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java @@ -65,9 +65,9 @@ public class PipMotionHelper { private static final int IME_SHIFT_DURATION = 300; // The fraction of the stack width that the user has to drag offscreen to minimize the PiP - private static final float MINIMIZE_OFFSCREEN_FRACTION = 0.2f; - // The fraction of the stack height that the user has to drag offscreen to minimize the PiP - private static final float DISMISS_OFFSCREEN_FRACTION = 0.35f; + private static final float MINIMIZE_OFFSCREEN_FRACTION = 0.3f; + // The fraction of the stack height that the user has to drag offscreen to dismiss the PiP + private static final float DISMISS_OFFSCREEN_FRACTION = 0.15f; private Context mContext; private IActivityManager mActivityManager; @@ -234,12 +234,16 @@ public class PipMotionHelper { /** * Animates the PiP to the minimized state, slightly offscreen. */ - Rect animateToClosestMinimizedState(Rect movementBounds) { + Rect animateToClosestMinimizedState(Rect movementBounds, + AnimatorUpdateListener updateListener) { cancelAnimations(); Rect toBounds = getClosestMinimizedBounds(mBounds, movementBounds); if (!mBounds.equals(toBounds)) { mBoundsAnimator = createAnimationToBounds(mBounds, toBounds, MINIMIZE_STACK_MAX_DURATION, LINEAR_OUT_SLOW_IN, mUpdateBoundsListener); + if (updateListener != null) { + mBoundsAnimator.addUpdateListener(updateListener); + } mBoundsAnimator.start(); } return toBounds; @@ -248,7 +252,8 @@ public class PipMotionHelper { /** * Flings the PiP to the closest snap target. */ - Rect flingToSnapTarget(float velocity, float velocityX, float velocityY, Rect movementBounds) { + Rect flingToSnapTarget(float velocity, float velocityX, float velocityY, Rect movementBounds, + AnimatorUpdateListener listener) { cancelAnimations(); Rect toBounds = mSnapAlgorithm.findClosestSnapBounds(movementBounds, mBounds, velocityX, velocityY); @@ -258,6 +263,9 @@ public class PipMotionHelper { mFlingAnimationUtils.apply(mBoundsAnimator, 0, distanceBetweenRectOffsets(mBounds, toBounds), velocity); + if (listener != null) { + mBoundsAnimator.addUpdateListener(listener); + } mBoundsAnimator.start(); } return toBounds; @@ -266,12 +274,15 @@ public class PipMotionHelper { /** * Animates the PiP to the closest snap target. */ - Rect animateToClosestSnapTarget(Rect movementBounds) { + Rect animateToClosestSnapTarget(Rect movementBounds, AnimatorUpdateListener listener) { cancelAnimations(); Rect toBounds = mSnapAlgorithm.findClosestSnapBounds(movementBounds, mBounds); if (!mBounds.equals(toBounds)) { mBoundsAnimator = createAnimationToBounds(mBounds, toBounds, SNAP_STACK_DURATION, FAST_OUT_SLOW_IN, mUpdateBoundsListener); + if (listener != null) { + mBoundsAnimator.addUpdateListener(listener); + } mBoundsAnimator.start(); } return toBounds; @@ -316,7 +327,7 @@ public class PipMotionHelper { /** * Animates the dismissal of the PiP off the edge of the screen. */ - Rect animateDragToEdgeDismiss(Rect pipBounds) { + Rect animateDragToEdgeDismiss(Rect pipBounds, AnimatorUpdateListener listener) { cancelAnimations(); Point displaySize = new Point(); mContext.getDisplay().getRealSize(displaySize); @@ -330,6 +341,9 @@ public class PipMotionHelper { dismissPip(); } }); + if (listener != null) { + mBoundsAnimator.addUpdateListener(listener); + } mBoundsAnimator.start(); return toBounds; } diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java index 17c344853638..c52fc3e87b0e 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java @@ -16,27 +16,23 @@ 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; import android.graphics.PointF; import android.graphics.Rect; -import android.graphics.Region; -import android.os.Bundle; import android.os.Handler; import android.os.RemoteException; import android.util.Log; import android.util.Size; import android.view.IPinnedStackController; -import android.view.MagnificationSpec; import android.view.MotionEvent; import android.view.ViewConfiguration; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityManager; import android.view.accessibility.AccessibilityNodeInfo; -import android.view.accessibility.AccessibilityWindowInfo; -import android.view.accessibility.IAccessibilityInteractionConnection; -import android.view.accessibility.IAccessibilityInteractionConnectionCallback; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; @@ -45,8 +41,6 @@ import com.android.systemui.R; import com.android.systemui.statusbar.FlingAnimationUtils; import java.io.PrintWriter; -import java.util.ArrayList; -import java.util.List; /** * Manages all the touch handling for PIP on the Phone, including moving, dismissing and expanding @@ -63,7 +57,7 @@ public class PipTouchHandler { // Allow dragging the PIP to a location to close it private static final boolean ENABLE_DISMISS_DRAG_TO_TARGET = false; - private static final boolean ENABLE_DISMISS_DRAG_TO_EDGE = false; + private static final boolean ENABLE_DISMISS_DRAG_TO_EDGE = true; private final Context mContext; private final IActivityManager mActivityManager; @@ -95,13 +89,13 @@ public class PipTouchHandler { } } }; - - private Runnable mShowMenuRunnable = new Runnable() { - @Override - public void run() { - mMenuController.showMenu(mMotionHelper.getBounds(), mMovementBounds); - } - }; + private ValueAnimator.AnimatorUpdateListener mUpdateScrimListener = + new AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + updateDismissFraction(); + } + }; // Behaviour states private boolean mIsMenuVisible; @@ -139,7 +133,7 @@ public class PipTouchHandler { @Override public void onPipMinimize() { setMinimizedStateInternal(true); - mMotionHelper.animateToClosestMinimizedState(mMovementBounds); + mMotionHelper.animateToClosestMinimizedState(mMovementBounds, null /* updateListener */); } @Override @@ -263,7 +257,12 @@ public class PipTouchHandler { private void onRegistrationChanged(boolean isRegistered) { mAccessibilityManager.setPictureInPictureActionReplacingConnection(isRegistered - ? new AccessibilityInteractionConnection() : null); + ? new PipAccessibilityInteractionConnection(mMotionHelper, + this::onAccessibilityShowMenu, mHandler) : null); + } + + private void onAccessibilityShowMenu() { + mMenuController.showMenu(mMotionHelper.getBounds(), mMovementBounds); } private boolean handleTouchEvent(MotionEvent ev) { @@ -314,7 +313,8 @@ public class PipTouchHandler { if (!mSendingHoverAccessibilityEvents) { AccessibilityEvent event = AccessibilityEvent.obtain( AccessibilityEvent.TYPE_VIEW_HOVER_ENTER); - AccessibilityNodeInfo info = obtainRootAccessibilityNodeInfo(); + AccessibilityNodeInfo info = + PipAccessibilityInteractionConnection.obtainRootAccessibilityNodeInfo(); event.setSource(info); info.recycle(); mAccessibilityManager.sendAccessibilityEvent(event); @@ -326,7 +326,8 @@ public class PipTouchHandler { if (mSendingHoverAccessibilityEvents) { AccessibilityEvent event = AccessibilityEvent.obtain( AccessibilityEvent.TYPE_VIEW_HOVER_EXIT); - AccessibilityNodeInfo info = obtainRootAccessibilityNodeInfo(); + AccessibilityNodeInfo info = + PipAccessibilityInteractionConnection.obtainRootAccessibilityNodeInfo(); event.setSource(info); info.recycle(); mAccessibilityManager.sendAccessibilityEvent(event); @@ -339,6 +340,22 @@ public class PipTouchHandler { } /** + * Updates the appearance of the menu and scrim on top of the PiP while dismissing. + */ + void updateDismissFraction() { + if (mMenuController != null) { + Rect bounds = mMotionHelper.getBounds(); + final float target = mMovementBounds.bottom + bounds.height(); + float fraction = 0f; + if (bounds.bottom > target) { + final float distance = bounds.bottom - target; + fraction = Math.min(distance / bounds.height(), 1f); + } + mMenuController.setDismissFraction(fraction); + } + } + + /** * Sets the controller to update the system of changes from user interaction. */ void setPinnedStackController(IPinnedStackController controller) { @@ -473,6 +490,9 @@ public class PipTouchHandler { if (ENABLE_DISMISS_DRAG_TO_TARGET) { mDismissViewController.updateDismissTarget(mTmpBounds); } + if (ENABLE_DISMISS_DRAG_TO_EDGE) { + updateDismissFraction(); + } return true; } return false; @@ -510,7 +530,8 @@ public class PipTouchHandler { boolean isFlingToBot = isFlingTowardsEdge(touchState, 4 /* bottom */); if (ENABLE_DISMISS_DRAG_TO_EDGE && (mMotionHelper.shouldDismissPip() || isFlingToBot)) { - mMotionHelper.animateDragToEdgeDismiss(mMotionHelper.getBounds()); + mMotionHelper.animateDragToEdgeDismiss(mMotionHelper.getBounds(), + mUpdateScrimListener); MetricsLogger.action(mContext, MetricsEvent.ACTION_PICTURE_IN_PICTURE_DISMISSED, METRIC_VALUE_DISMISSED_BY_DRAG); @@ -525,7 +546,8 @@ public class PipTouchHandler { // minimize offset adjusted mMenuController.hideMenu(); } else { - mMotionHelper.animateToClosestMinimizedState(mMovementBounds); + mMotionHelper.animateToClosestMinimizedState(mMovementBounds, + mUpdateScrimListener); } return true; } @@ -544,13 +566,14 @@ public class PipTouchHandler { final PointF vel = mTouchState.getVelocity(); final float velocity = PointF.length(vel.x, vel.y); if (velocity > mFlingAnimationUtils.getMinVelocityPxPerSecond()) { - mMotionHelper.flingToSnapTarget(velocity, vel.x, vel.y, mMovementBounds); + mMotionHelper.flingToSnapTarget(velocity, vel.x, vel.y, mMovementBounds, + mUpdateScrimListener); } else { - mMotionHelper.animateToClosestSnapTarget(mMovementBounds); + mMotionHelper.animateToClosestSnapTarget(mMovementBounds, mUpdateScrimListener); } } else if (mIsMinimized) { // This was a tap, so no longer minimized - mMotionHelper.animateToClosestSnapTarget(mMovementBounds); + mMotionHelper.animateToClosestSnapTarget(mMovementBounds, null /* listener */); setMinimizedStateInternal(false); } else if (!mIsMenuVisible) { mMenuController.showMenu(mMotionHelper.getBounds(), mMovementBounds); @@ -625,143 +648,4 @@ public class PipTouchHandler { mMotionHelper.dump(pw, innerPrefix); } - private static AccessibilityNodeInfo obtainRootAccessibilityNodeInfo() { - AccessibilityNodeInfo info = AccessibilityNodeInfo.obtain(); - info.setSourceNodeId(AccessibilityNodeInfo.ROOT_NODE_ID, - AccessibilityWindowInfo.PICTURE_IN_PICTURE_ACTION_REPLACER_WINDOW_ID); - info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK); - info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_DISMISS); - info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_MOVE_WINDOW); - info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_EXPAND); - info.setImportantForAccessibility(true); - info.setClickable(true); - info.setVisibleToUser(true); - return info; - } - - /** - * Expose the touch actions to accessibility as if this object were a window with a single view. - * That pseudo-view exposes all of the actions this object can perform. - */ - class AccessibilityInteractionConnection extends IAccessibilityInteractionConnection.Stub { - static final long ACCESSIBILITY_NODE_ID = 1; - List<AccessibilityNodeInfo> mAccessibilityNodeInfoList; - - @Override - public void findAccessibilityNodeInfoByAccessibilityId(long accessibilityNodeId, - Region interactiveRegion, int interactionId, - IAccessibilityInteractionConnectionCallback callback, int flags, - int interrogatingPid, long interrogatingTid, MagnificationSpec spec, Bundle args) { - try { - callback.setFindAccessibilityNodeInfosResult( - (accessibilityNodeId == AccessibilityNodeInfo.ROOT_NODE_ID) - ? getNodeList() : null, interactionId); - } catch (RemoteException re) { - /* best effort - ignore */ - } - } - - @Override - public void performAccessibilityAction(long accessibilityNodeId, int action, - Bundle arguments, int interactionId, - IAccessibilityInteractionConnectionCallback callback, int flags, - int interrogatingPid, long interrogatingTid) { - // We only support one view. A request for anything else is invalid - boolean result = false; - if (accessibilityNodeId == AccessibilityNodeInfo.ROOT_NODE_ID) { - switch (action) { - case AccessibilityNodeInfo.ACTION_CLICK: - mHandler.post(mShowMenuRunnable); - result = true; - break; - case AccessibilityNodeInfo.ACTION_DISMISS: - mMotionHelper.dismissPip(); - result = true; - break; - case com.android.internal.R.id.accessibilityActionMoveWindow: - int newX = arguments.getInt( - AccessibilityNodeInfo.ACTION_ARGUMENT_MOVE_WINDOW_X); - int newY = arguments.getInt( - AccessibilityNodeInfo.ACTION_ARGUMENT_MOVE_WINDOW_Y); - Rect pipBounds = new Rect(); - pipBounds.set(mMotionHelper.getBounds()); - mTmpBounds.offsetTo(newX, newY); - mMotionHelper.movePip(mTmpBounds); - result = true; - break; - case AccessibilityNodeInfo.ACTION_EXPAND: - mMotionHelper.expandPip(); - result = true; - break; - default: - // Leave result as false - } - } - try { - callback.setPerformAccessibilityActionResult(result, interactionId); - } catch (RemoteException re) { - /* best effort - ignore */ - } - } - - @Override - public void findAccessibilityNodeInfosByViewId(long accessibilityNodeId, - String viewId, Region interactiveRegion, int interactionId, - IAccessibilityInteractionConnectionCallback callback, int flags, - int interrogatingPid, long interrogatingTid, MagnificationSpec spec) { - // We have no view with a proper ID - try { - callback.setFindAccessibilityNodeInfoResult(null, interactionId); - } catch (RemoteException re) { - /* best effort - ignore */ - } - } - - @Override - public void findAccessibilityNodeInfosByText(long accessibilityNodeId, String text, - Region interactiveRegion, int interactionId, - IAccessibilityInteractionConnectionCallback callback, int flags, - int interrogatingPid, long interrogatingTid, MagnificationSpec spec) { - // We have no view with text - try { - callback.setFindAccessibilityNodeInfoResult(null, interactionId); - } catch (RemoteException re) { - /* best effort - ignore */ - } - } - - @Override - public void findFocus(long accessibilityNodeId, int focusType, Region interactiveRegion, - int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags, - int interrogatingPid, long interrogatingTid, MagnificationSpec spec) { - // We have no view that can take focus - try { - callback.setFindAccessibilityNodeInfoResult(null, interactionId); - } catch (RemoteException re) { - /* best effort - ignore */ - } - } - - @Override - public void focusSearch(long accessibilityNodeId, int direction, Region interactiveRegion, - int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags, - int interrogatingPid, long interrogatingTid, MagnificationSpec spec) { - // We have no view that can take focus - try { - callback.setFindAccessibilityNodeInfoResult(null, interactionId); - } catch (RemoteException re) { - /* best effort - ignore */ - } - } - - private List<AccessibilityNodeInfo> getNodeList() { - if (mAccessibilityNodeInfoList == null) { - mAccessibilityNodeInfoList = new ArrayList<>(1); - } - AccessibilityNodeInfo info = obtainRootAccessibilityNodeInfo(); - mAccessibilityNodeInfoList.clear(); - mAccessibilityNodeInfoList.add(info); - return mAccessibilityNodeInfoList; - } - } } diff --git a/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java b/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java index 9a8974d18905..b71c87d4cc23 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java +++ b/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java @@ -86,14 +86,6 @@ public class PipManager implements BasePipManager { * State when PIP menu dialog is shown. */ public static final int STATE_PIP_MENU = 2; - /** - * State when PIP is shown in Recents. - */ - public static final int STATE_PIP_RECENTS = 3; - /** - * State when PIP is shown in Recents and it's focused to allow an user to control. - */ - public static final int STATE_PIP_RECENTS_FOCUSED = 4; private static final int TASK_ID_NO_PIP = -1; private static final int INVALID_RESOURCE_TYPE = -1; @@ -119,7 +111,6 @@ public class PipManager implements BasePipManager { private int mSuspendPipResizingReason; private Context mContext; - private PipRecentsOverlayManager mPipRecentsOverlayManager; private IActivityManager mActivityManager; private IWindowManager mWindowManager; private MediaSessionManager mMediaSessionManager; @@ -132,9 +123,6 @@ public class PipManager implements BasePipManager { private Rect mDefaultPipBounds = new Rect(); private Rect mSettingsPipBounds; private Rect mMenuModePipBounds; - private Rect mRecentsPipBounds; - private Rect mRecentsFocusedPipBounds; - private int mRecentsFocusChangedAnimationDurationMs; private boolean mInitialized; private int mPipTaskId = TASK_ID_NO_PIP; private ComponentName mPipComponentName; @@ -259,7 +247,6 @@ public class PipManager implements BasePipManager { } loadConfigurationsAndApply(); - mPipRecentsOverlayManager = new PipRecentsOverlayManager(context); mMediaSessionManager = (MediaSessionManager) mContext.getSystemService(Context.MEDIA_SESSION_SERVICE); @@ -276,12 +263,6 @@ public class PipManager implements BasePipManager { R.string.pip_settings_bounds)); mMenuModePipBounds = Rect.unflattenFromString(res.getString( R.string.pip_menu_bounds)); - mRecentsPipBounds = Rect.unflattenFromString(res.getString( - R.string.pip_recents_bounds)); - mRecentsFocusedPipBounds = Rect.unflattenFromString(res.getString( - R.string.pip_recents_focused_bounds)); - mRecentsFocusChangedAnimationDurationMs = res.getInteger( - R.integer.recents_tv_pip_focus_anim_duration); // Reset the PIP bounds and apply. PIP bounds can be changed by two reasons. // 1. Configuration changed due to the language change (RTL <-> RTL) @@ -295,7 +276,6 @@ public class PipManager implements BasePipManager { */ public void onConfigurationChanged() { loadConfigurationsAndApply(); - mPipRecentsOverlayManager.onConfigurationChanged(mContext); } /** @@ -385,8 +365,6 @@ public class PipManager implements BasePipManager { */ void resizePinnedStack(int state) { if (DEBUG) Log.d(TAG, "resizePinnedStack() state=" + state); - boolean wasRecentsShown = - (mState == STATE_PIP_RECENTS || mState == STATE_PIP_RECENTS_FOCUSED); boolean wasStateNoPip = (mState == STATE_NO_PIP); mState = state; for (int i = mListeners.size() - 1; i >= 0; --i) { @@ -413,22 +391,12 @@ public class PipManager implements BasePipManager { case STATE_PIP_OVERLAY: mCurrentPipBounds = mPipBounds; break; - case STATE_PIP_RECENTS: - mCurrentPipBounds = mRecentsPipBounds; - break; - case STATE_PIP_RECENTS_FOCUSED: - mCurrentPipBounds = mRecentsFocusedPipBounds; - break; default: mCurrentPipBounds = mPipBounds; break; } try { int animationDurationMs = -1; - if (wasRecentsShown - && (mState == STATE_PIP_RECENTS || mState == STATE_PIP_RECENTS_FOCUSED)) { - animationDurationMs = mRecentsFocusChangedAnimationDurationMs; - } mActivityManager.resizeStack(PINNED_STACK_ID, mCurrentPipBounds, true, true, true, animationDurationMs); } catch (RemoteException e) { @@ -444,23 +412,11 @@ public class PipManager implements BasePipManager { } /** - * Returns the focused PIP bound while Recents is shown. - * This is used to place PIP controls in Recents. - */ - public Rect getRecentsFocusedPipBounds() { - return mRecentsFocusedPipBounds; - } - - /** * Shows PIP menu UI by launching {@link PipMenuActivity}. It also locates the pinned * stack to the centered PIP bound {@link R.config_centeredPictureInPictureBounds}. */ private void showPipMenu() { if (DEBUG) Log.d(TAG, "showPipMenu()"); - if (mPipRecentsOverlayManager.isRecentsShown()) { - if (DEBUG) Log.d(TAG, "Ignore showing PIP menu"); - return; - } mState = STATE_PIP_MENU; for (int i = mListeners.size() - 1; i >= 0; --i) { mListeners.get(i).onShowPipMenu(); @@ -692,11 +648,6 @@ public class PipManager implements BasePipManager { mMediaSessionManager.addOnActiveSessionsChangedListener( mActiveMediaSessionListener, null); updateMediaController(mMediaSessionManager.getActiveSessions(null)); - if (mPipRecentsOverlayManager.isRecentsShown()) { - // If an activity becomes PIPed again after the fullscreen, the Recents is shown - // behind so we need to resize the pinned stack and show the correct overlay. - resizePinnedStack(STATE_PIP_RECENTS); - } for (int i = mListeners.size() - 1; i >= 0; i--) { mListeners.get(i).onPipEntered(); } @@ -721,18 +672,7 @@ public class PipManager implements BasePipManager { } switch (mState) { case STATE_PIP_OVERLAY: - if (!mPipRecentsOverlayManager.isRecentsShown()) { - showPipOverlay(); - break; - } else { - // This happens only if an activity is PIPed after the Recents is shown. - // See {@link PipRecentsOverlayManager.requestFocus} for more details. - resizePinnedStack(mState); - break; - } - case STATE_PIP_RECENTS: - case STATE_PIP_RECENTS_FOCUSED: - mPipRecentsOverlayManager.addPipRecentsOverlayView(); + showPipOverlay(); break; case STATE_PIP_MENU: showPipMenu(); @@ -780,13 +720,6 @@ public class PipManager implements BasePipManager { return sPipManager; } - /** - * Gets an instance of {@link PipRecentsOverlayManager}. - */ - public PipRecentsOverlayManager getPipRecentsOverlayManager() { - return mPipRecentsOverlayManager; - } - private void updatePipVisibility(final boolean visible) { SystemServicesProxy.getInstance(mContext).setTvPipVisibility(visible); } diff --git a/packages/SystemUI/src/com/android/systemui/pip/tv/PipRecentsControlsView.java b/packages/SystemUI/src/com/android/systemui/pip/tv/PipRecentsControlsView.java deleted file mode 100644 index a891d122f374..000000000000 --- a/packages/SystemUI/src/com/android/systemui/pip/tv/PipRecentsControlsView.java +++ /dev/null @@ -1,174 +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.pip.tv; - -import android.animation.Animator; -import android.animation.AnimatorInflater; -import android.animation.AnimatorSet; -import android.content.Context; -import android.graphics.Rect; -import android.util.AttributeSet; -import android.view.KeyEvent; -import android.view.View; -import android.widget.FrameLayout; - -import com.android.systemui.R; - -/** - * An FrameLayout that contains {@link PipControlsView} with its scrim. - */ -public class PipRecentsControlsView extends FrameLayout { - /** - * An interface to listen user action. - */ - public interface Listener extends PipControlsView.Listener { - /** - * Called when an user presses BACK key and up. - */ - abstract void onBackPressed(); - } - - private final PipManager mPipManager = PipManager.getInstance(); - private PipControlsView mPipControlsView; - private View mScrim; - private Animator mFocusGainAnimator; - private AnimatorSet mFocusLossAnimatorSet; - - public PipRecentsControlsView(Context context) { - this(context, null, 0, 0); - } - - public PipRecentsControlsView(Context context, AttributeSet attrs) { - this(context, attrs, 0, 0); - } - - public PipRecentsControlsView(Context context, AttributeSet attrs, int defStyleAttr) { - this(context, attrs, defStyleAttr, 0); - } - - public PipRecentsControlsView( - Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { - super(context, attrs, defStyleAttr, defStyleRes); - } - - @Override - public void onFinishInflate() { - super.onFinishInflate(); - - mPipControlsView = (PipControlsView) findViewById(R.id.pip_control_contents); - mScrim = findViewById(R.id.scrim); - - mFocusGainAnimator = loadAnimator(mPipControlsView, - R.anim.tv_pip_controls_in_recents_focus_gain_animation); - - mFocusLossAnimatorSet = new AnimatorSet(); - mFocusLossAnimatorSet.playSequentially( - loadAnimator(mPipControlsView, - R.anim.tv_pip_controls_in_recents_focus_loss_animation), - loadAnimator(mScrim, R.anim.tv_pip_controls_in_recents_scrim_fade_in_animation)); - - Rect pipBounds = mPipManager.getRecentsFocusedPipBounds(); - setPadding(0, pipBounds.bottom, 0, 0); - } - - private Animator loadAnimator(View view, int animatorResId) { - Animator animator = AnimatorInflater.loadAnimator(getContext(), animatorResId); - animator.setTarget(view); - return animator; - } - - /** - * Starts focus gain animation. - */ - public void startFocusGainAnimation() { - // Hides the scrim view as soon as possible, before the PIP resize animation starts. - // If we don't, PIP will be moved down a bit and a gap between the scrim and PIP will be - // shown at the bottom of the PIP. - mScrim.setAlpha(0); - PipControlButtonView focus = mPipControlsView.getFocusedButton(); - if (focus != null) { - focus.startFocusGainAnimation(); - } - startAnimator(mFocusGainAnimator, mFocusLossAnimatorSet); - } - - /** - * Starts focus loss animation. - */ - public void startFocusLossAnimation() { - PipControlButtonView focus = mPipControlsView.getFocusedButton(); - if (focus != null) { - focus.startFocusLossAnimation(); - } - startAnimator(mFocusLossAnimatorSet, mFocusGainAnimator); - } - - /** - * Resets the view to the initial state. (i.e. end of the focus gain) - */ - public void reset() { - cancelAnimator(mFocusGainAnimator); - cancelAnimator(mFocusLossAnimatorSet); - - // Reset to initial state (i.e. end of focused) - mScrim.setAlpha(0); - mPipControlsView.setTranslationY(0); - mPipControlsView.setScaleX(1); - mPipControlsView.setScaleY(1); - mPipControlsView.reset(); - } - - private static void startAnimator(Animator animator, Animator previousAnimator) { - cancelAnimator(previousAnimator); - if (!animator.isStarted()) { - animator.start(); - } - } - - private static void cancelAnimator(Animator animator) { - if (animator.isStarted()) { - animator.cancel(); - } - } - - /** - * Sets listeners. - */ - public void setListener(Listener listener) { - mPipControlsView.setListener(listener); - } - - @Override - public boolean dispatchKeyEvent(KeyEvent event) { - if (!event.isCanceled()) { - if (event.getKeyCode() == KeyEvent.KEYCODE_BACK - && event.getAction() == KeyEvent.ACTION_UP) { - if (mPipControlsView.mListener != null) { - ((PipRecentsControlsView.Listener) mPipControlsView.mListener).onBackPressed(); - } - return true; - } else if (event.getKeyCode() == KeyEvent.KEYCODE_DPAD_DOWN) { - if (event.getAction() == KeyEvent.ACTION_DOWN) { - mPipManager.getPipRecentsOverlayManager().clearFocus(); - } - // Consume the down event always to prevent warning logs from ViewRootImpl. - return true; - } - } - return super.dispatchKeyEvent(event); - } -} diff --git a/packages/SystemUI/src/com/android/systemui/pip/tv/PipRecentsOverlayManager.java b/packages/SystemUI/src/com/android/systemui/pip/tv/PipRecentsOverlayManager.java deleted file mode 100644 index 835bcbc51f0c..000000000000 --- a/packages/SystemUI/src/com/android/systemui/pip/tv/PipRecentsOverlayManager.java +++ /dev/null @@ -1,231 +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.pip.tv; - -import android.content.Context; -import android.graphics.PixelFormat; -import android.util.Log; -import android.view.LayoutInflater; -import android.view.View; -import android.view.WindowManager; -import android.view.WindowManager.LayoutParams; -import android.view.accessibility.AccessibilityEvent; - -import com.android.systemui.R; -import com.android.systemui.recents.misc.SystemServicesProxy; - -import static android.view.Gravity.CENTER_HORIZONTAL; -import static android.view.Gravity.TOP; -import static android.view.View.MeasureSpec.UNSPECIFIED; - -public class PipRecentsOverlayManager { - private static final String TAG = "PipRecentsOverlayManager"; - - public interface Callback { - void onClosed(); - void onBackPressed(); - void onRecentsFocused(); - } - - private final PipManager mPipManager = PipManager.getInstance(); - private final WindowManager mWindowManager; - private final SystemServicesProxy mSystemServicesProxy; - private View mOverlayView; - private PipRecentsControlsView mPipControlsView; - private View mRecentsView; - private boolean mTalkBackEnabled; - - private LayoutParams mPipRecentsControlsViewLayoutParams; - private LayoutParams mPipRecentsControlsViewFocusedLayoutParams; - - private boolean mHasFocusableInRecents; - private boolean mIsPipRecentsOverlayShown; - private boolean mIsRecentsShown; - private boolean mIsPipFocusedInRecent; - private Callback mCallback; - private PipRecentsControlsView.Listener mPipControlsViewListener = - new PipRecentsControlsView.Listener() { - @Override - public void onClosed() { - if (mCallback != null) { - mCallback.onClosed(); - } - } - - @Override - public void onBackPressed() { - if (mCallback != null) { - mCallback.onBackPressed(); - } - } - }; - - PipRecentsOverlayManager(Context context) { - mWindowManager = (WindowManager) context.getSystemService(WindowManager.class); - mSystemServicesProxy = SystemServicesProxy.getInstance(context); - initViews(context); - } - - private void initViews(Context context) { - LayoutInflater inflater = (LayoutInflater) context - .getSystemService(Context.LAYOUT_INFLATER_SERVICE); - mOverlayView = inflater.inflate(R.layout.tv_pip_recents_overlay, null); - mPipControlsView = (PipRecentsControlsView) mOverlayView.findViewById(R.id.pip_controls); - mRecentsView = mOverlayView.findViewById(R.id.recents); - mRecentsView.setOnFocusChangeListener(new View.OnFocusChangeListener() { - @Override - public void onFocusChange(View v, boolean hasFocus) { - if (hasFocus) { - clearFocus(); - } - } - }); - - mOverlayView.measure(UNSPECIFIED, UNSPECIFIED); - mPipRecentsControlsViewLayoutParams = new WindowManager.LayoutParams( - mOverlayView.getMeasuredWidth(), mOverlayView.getMeasuredHeight(), - LayoutParams.TYPE_SYSTEM_DIALOG, - LayoutParams.FLAG_NOT_FOCUSABLE | LayoutParams.FLAG_NOT_TOUCHABLE, - PixelFormat.TRANSLUCENT); - mPipRecentsControlsViewLayoutParams.gravity = TOP | CENTER_HORIZONTAL; - mPipRecentsControlsViewFocusedLayoutParams = new WindowManager.LayoutParams( - mOverlayView.getMeasuredWidth(), mOverlayView.getMeasuredHeight(), - LayoutParams.TYPE_SYSTEM_DIALOG, - 0, - PixelFormat.TRANSLUCENT); - mPipRecentsControlsViewFocusedLayoutParams.gravity = TOP | CENTER_HORIZONTAL; - } - - /** - * Add Recents overlay view. - * This is expected to be called after the PIP animation is over. - */ - void addPipRecentsOverlayView() { - if (mIsPipRecentsOverlayShown) { - return; - } - mTalkBackEnabled = mSystemServicesProxy.isTouchExplorationEnabled(); - mRecentsView.setVisibility(mTalkBackEnabled ? View.VISIBLE : View.GONE); - mIsPipRecentsOverlayShown = true; - mIsPipFocusedInRecent = true; - mWindowManager.addView(mOverlayView, mPipRecentsControlsViewFocusedLayoutParams); - } - - /** - * Remove Recents overlay view. - * This should be called when Recents or PIP is closed. - */ - public void removePipRecentsOverlayView() { - if (!mIsPipRecentsOverlayShown) { - return; - } - mWindowManager.removeView(mOverlayView); - // Resets the controls view when its removed. - // If not, changing focus in reset will be show animation when Recents is resumed. - mPipControlsView.reset(); - mIsPipRecentsOverlayShown = false; - } - - /** - * Request focus to the PIP Recents overlay. - * This should be called only by {@link com.android.systemui.recents.tv.RecentsTvActivity}. - * @param hasFocusableInRecents {@code true} if Recents can have focus. (i.e. Has a recent task) - */ - public void requestFocus(boolean hasFocusableInRecents) { - mHasFocusableInRecents = hasFocusableInRecents; - if (!mIsPipRecentsOverlayShown || !mIsRecentsShown || mIsPipFocusedInRecent - || !mPipManager.isPipShown()) { - return; - } - mIsPipFocusedInRecent = true; - mPipControlsView.startFocusGainAnimation(); - mWindowManager.updateViewLayout(mOverlayView, mPipRecentsControlsViewFocusedLayoutParams); - mPipManager.resizePinnedStack(PipManager.STATE_PIP_RECENTS_FOCUSED); - if (mTalkBackEnabled) { - mPipControlsView.requestFocus(); - mPipControlsView.sendAccessibilityEvent( - AccessibilityEvent.TYPE_VIEW_FOCUSED); - } - } - - /** - * Request focus to the PIP Recents overlay. - */ - public void clearFocus() { - if (!mIsPipRecentsOverlayShown || !mIsRecentsShown || !mIsPipFocusedInRecent - || !mPipManager.isPipShown() || !mHasFocusableInRecents) { - return; - } - mIsPipFocusedInRecent = false; - mPipControlsView.startFocusLossAnimation(); - mWindowManager.updateViewLayout(mOverlayView, mPipRecentsControlsViewLayoutParams); - mPipManager.resizePinnedStack(PipManager.STATE_PIP_RECENTS); - if (mCallback != null) { - mCallback.onRecentsFocused(); - } - } - - public void setCallback(Callback listener) { - mCallback = listener; - mPipControlsView.setListener(mCallback != null ? mPipControlsViewListener : null); - } - - /** - * Called when Recents is resumed. - * PIPed activity will be resized accordingly and overlay will show available buttons. - */ - public void onRecentsResumed() { - if (!mPipManager.isPipShown()) { - return; - } - mIsRecentsShown = true; - mIsPipFocusedInRecent = true; - mPipManager.resizePinnedStack(PipManager.STATE_PIP_RECENTS_FOCUSED); - // Overlay view will be added after the resize animation ends, if any. - } - - /** - * Called when Recents is paused. - * PIPed activity will be resized accordingly and overlay will hide available buttons. - */ - public void onRecentsPaused() { - mIsRecentsShown = false; - mIsPipFocusedInRecent = false; - removePipRecentsOverlayView(); - - if (mPipManager.isPipShown()) { - mPipManager.resizePinnedStack(PipManager.STATE_PIP_OVERLAY); - } - } - - /** - * Returns {@code true} if recents is shown. - */ - boolean isRecentsShown() { - return mIsRecentsShown; - } - - /** - * Updates the PIP per configuration changed. - */ - void onConfigurationChanged(Context context) { - if (mIsRecentsShown) { - Log.w(TAG, "Configuration is changed while Recents is shown"); - } - initViews(context); - } -} diff --git a/packages/SystemUI/src/com/android/systemui/plugins/PluginInstanceManager.java b/packages/SystemUI/src/com/android/systemui/plugins/PluginInstanceManager.java index 79f78c92f8b9..07ac52d718aa 100644 --- a/packages/SystemUI/src/com/android/systemui/plugins/PluginInstanceManager.java +++ b/packages/SystemUI/src/com/android/systemui/plugins/PluginInstanceManager.java @@ -62,10 +62,10 @@ public class PluginInstanceManager<T extends Plugin> { final PluginHandler mPluginHandler; private final boolean isDebuggable; private final PackageManager mPm; - private final PluginManager mManager; + private final PluginManagerImpl mManager; PluginInstanceManager(Context context, String action, PluginListener<T> listener, - boolean allowMultiple, Looper looper, VersionInfo version, PluginManager manager) { + boolean allowMultiple, Looper looper, VersionInfo version, PluginManagerImpl manager) { this(context, context.getPackageManager(), action, listener, allowMultiple, looper, version, manager, Build.IS_DEBUGGABLE); } @@ -73,7 +73,7 @@ public class PluginInstanceManager<T extends Plugin> { @VisibleForTesting PluginInstanceManager(Context context, PackageManager pm, String action, PluginListener<T> listener, boolean allowMultiple, Looper looper, VersionInfo version, - PluginManager manager, boolean debuggable) { + PluginManagerImpl manager, boolean debuggable) { mMainHandler = new MainHandler(Looper.getMainLooper()); mPluginHandler = new PluginHandler(looper); mManager = manager; @@ -346,7 +346,7 @@ public class PluginInstanceManager<T extends Plugin> { .setContentText("Check to see if an OTA is available.\n" + e.getMessage()); } - Intent i = new Intent(PluginManager.DISABLE_PLUGIN).setData( + Intent i = new Intent(PluginManagerImpl.DISABLE_PLUGIN).setData( Uri.parse("package://" + component.flattenToString())); PendingIntent pi = PendingIntent.getBroadcast(mContext, 0, i, 0); nb.addAction(new Action.Builder(null, "Disable plugin", pi).build()); diff --git a/packages/SystemUI/src/com/android/systemui/plugins/PluginManager.java b/packages/SystemUI/src/com/android/systemui/plugins/PluginManager.java index 9ad862d4c33a..298eaf18dce5 100644 --- a/packages/SystemUI/src/com/android/systemui/plugins/PluginManager.java +++ b/packages/SystemUI/src/com/android/systemui/plugins/PluginManager.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016 The Android Open Source Project + * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of the License at @@ -14,276 +14,33 @@ package com.android.systemui.plugins; -import android.app.Notification; -import android.app.Notification.Action; -import android.app.NotificationManager; -import android.app.PendingIntent; -import android.content.BroadcastReceiver; -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageManager; -import android.content.pm.PackageManager.NameNotFoundException; -import android.content.res.Resources; -import android.net.Uri; -import android.os.Build; -import android.os.Handler; -import android.os.HandlerThread; -import android.os.Looper; -import android.os.SystemProperties; -import android.os.UserHandle; import android.text.TextUtils; -import android.util.ArrayMap; -import android.util.ArraySet; -import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; -import com.android.systemui.Dependency; -import com.android.systemui.plugins.PluginInstanceManager.PluginContextWrapper; -import com.android.systemui.plugins.PluginInstanceManager.PluginInfo; import com.android.systemui.plugins.annotations.ProvidesInterface; -import dalvik.system.PathClassLoader; +public interface PluginManager { -import java.lang.Thread.UncaughtExceptionHandler; -import java.util.Map; - -/** - * @see Plugin - */ -public class PluginManager extends BroadcastReceiver { - - public static final String PLUGIN_CHANGED = "com.android.systemui.action.PLUGIN_CHANGED"; - - static final String DISABLE_PLUGIN = "com.android.systemui.action.DISABLE_PLUGIN"; + String PLUGIN_CHANGED = "com.android.systemui.action.PLUGIN_CHANGED"; // must be one of the channels created in NotificationChannels.java - static final String NOTIFICATION_CHANNEL_ID = "ALR"; - - private static PluginManager sInstance; - - private final HandlerThread mBackgroundThread; - private final ArrayMap<PluginListener<?>, PluginInstanceManager> mPluginMap - = new ArrayMap<>(); - private final Map<String, ClassLoader> mClassLoaders = new ArrayMap<>(); - private final ArraySet<String> mOneShotPackages = new ArraySet<>(); - private final Context mContext; - private final PluginInstanceManagerFactory mFactory; - private final boolean isDebuggable; - private final PluginPrefs mPluginPrefs; - private ClassLoaderFilter mParentClassLoader; - private boolean mListening; - private boolean mHasOneShot; - - public PluginManager(Context context) { - this(context, new PluginInstanceManagerFactory(), - Build.IS_DEBUGGABLE, Thread.getDefaultUncaughtExceptionHandler()); - } - - @VisibleForTesting - PluginManager(Context context, PluginInstanceManagerFactory factory, boolean debuggable, - UncaughtExceptionHandler defaultHandler) { - mContext = context; - mFactory = factory; - mBackgroundThread = new HandlerThread("Plugins"); - mBackgroundThread.start(); - isDebuggable = debuggable; - mPluginPrefs = new PluginPrefs(mContext); - - PluginExceptionHandler uncaughtExceptionHandler = new PluginExceptionHandler( - defaultHandler); - Thread.setDefaultUncaughtExceptionHandler(uncaughtExceptionHandler); - if (isDebuggable) { - new Handler(mBackgroundThread.getLooper()).post(() -> { - // Plugin dependencies that don't have another good home can go here, but - // dependencies that have better places to init can happen elsewhere. - Dependency.get(PluginDependencyProvider.class) - .allowPluginDependency(ActivityStarter.class); - }); - } - } - - public <T extends Plugin> T getOneShotPlugin(Class<T> cls) { - ProvidesInterface info = cls.getDeclaredAnnotation(ProvidesInterface.class); - if (info == null) { - throw new RuntimeException(cls + " doesn't provide an interface"); - } - if (TextUtils.isEmpty(info.action())) { - throw new RuntimeException(cls + " doesn't provide an action"); - } - return getOneShotPlugin(info.action(), cls); - } - - public <T extends Plugin> T getOneShotPlugin(String action, Class<?> cls) { - if (!isDebuggable) { - // Never ever ever allow these on production builds, they are only for prototyping. - return null; - } - if (Looper.myLooper() != Looper.getMainLooper()) { - throw new RuntimeException("Must be called from UI thread"); - } - PluginInstanceManager<T> p = mFactory.createPluginInstanceManager(mContext, action, null, - false, mBackgroundThread.getLooper(), cls, this); - mPluginPrefs.addAction(action); - PluginInfo<T> info = p.getPlugin(); - if (info != null) { - mOneShotPackages.add(info.mPackage); - mHasOneShot = true; - startListening(); - return info.mPlugin; - } - return null; - } - - public <T extends Plugin> void addPluginListener(PluginListener<T> listener, Class<?> cls) { - addPluginListener(listener, cls, false); - } - - public <T extends Plugin> void addPluginListener(PluginListener<T> listener, Class<?> cls, - boolean allowMultiple) { - addPluginListener(getAction(cls), listener, cls, allowMultiple); - } - - public <T extends Plugin> void addPluginListener(String action, PluginListener<T> listener, - Class<?> cls) { - addPluginListener(action, listener, cls, false); - } - - public <T extends Plugin> void addPluginListener(String action, PluginListener<T> listener, - Class cls, boolean allowMultiple) { - if (!isDebuggable) { - // Never ever ever allow these on production builds, they are only for prototyping. - return; - } - mPluginPrefs.addAction(action); - PluginInstanceManager p = mFactory.createPluginInstanceManager(mContext, action, listener, - allowMultiple, mBackgroundThread.getLooper(), cls, this); - p.loadAll(); - mPluginMap.put(listener, p); - startListening(); - } - - public void removePluginListener(PluginListener<?> listener) { - if (!isDebuggable) { - // Never ever ever allow these on production builds, they are only for prototyping. - return; - } - if (!mPluginMap.containsKey(listener)) return; - mPluginMap.remove(listener).destroy(); - stopListening(); - } - - private void startListening() { - if (mListening) return; - mListening = true; - IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED); - filter.addAction(Intent.ACTION_PACKAGE_CHANGED); - filter.addAction(Intent.ACTION_PACKAGE_REMOVED); - filter.addAction(PLUGIN_CHANGED); - filter.addAction(DISABLE_PLUGIN); - filter.addDataScheme("package"); - mContext.registerReceiver(this, filter); - filter = new IntentFilter(Intent.ACTION_USER_UNLOCKED); - mContext.registerReceiver(this, filter); - } - - private void stopListening() { - // Never stop listening if a one-shot is present. - if (!mListening || mHasOneShot) return; - mListening = false; - mContext.unregisterReceiver(this); - } - - @Override - public void onReceive(Context context, Intent intent) { - if (Intent.ACTION_USER_UNLOCKED.equals(intent.getAction())) { - for (PluginInstanceManager manager : mPluginMap.values()) { - manager.loadAll(); - } - } else if (DISABLE_PLUGIN.equals(intent.getAction())) { - Uri uri = intent.getData(); - ComponentName component = ComponentName.unflattenFromString( - uri.toString().substring(10)); - mContext.getPackageManager().setComponentEnabledSetting(component, - PackageManager.COMPONENT_ENABLED_STATE_DISABLED, - PackageManager.DONT_KILL_APP); - mContext.getSystemService(NotificationManager.class).cancel(component.getClassName(), - SystemMessage.NOTE_PLUGIN); - } else { - Uri data = intent.getData(); - String pkg = data.getEncodedSchemeSpecificPart(); - if (mOneShotPackages.contains(pkg)) { - int icon = mContext.getResources().getIdentifier("tuner", "drawable", - mContext.getPackageName()); - int color = Resources.getSystem().getIdentifier( - "system_notification_accent_color", "color", "android"); - String label = pkg; - try { - PackageManager pm = mContext.getPackageManager(); - label = pm.getApplicationInfo(pkg, 0).loadLabel(pm).toString(); - } catch (NameNotFoundException e) { - } - // Localization not required as this will never ever appear in a user build. - final Notification.Builder nb = - new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID) - .setSmallIcon(icon) - .setWhen(0) - .setShowWhen(false) - .setPriority(Notification.PRIORITY_MAX) - .setVisibility(Notification.VISIBILITY_PUBLIC) - .setColor(mContext.getColor(color)) - .setContentTitle("Plugin \"" + label + "\" has updated") - .setContentText("Restart SysUI for changes to take effect."); - Intent i = new Intent("com.android.systemui.action.RESTART").setData( - Uri.parse("package://" + pkg)); - PendingIntent pi = PendingIntent.getBroadcast(mContext, 0, i, 0); - nb.addAction(new Action.Builder(null, "Restart SysUI", pi).build()); - mContext.getSystemService(NotificationManager.class).notifyAsUser(pkg, - SystemMessage.NOTE_PLUGIN, nb.build(), UserHandle.ALL); - } - clearClassLoader(pkg); - if (!Intent.ACTION_PACKAGE_REMOVED.equals(intent.getAction())) { - for (PluginInstanceManager manager : mPluginMap.values()) { - manager.onPackageChange(pkg); - } - } else { - for (PluginInstanceManager manager : mPluginMap.values()) { - manager.onPackageRemoved(pkg); - } - } - } - } + String NOTIFICATION_CHANNEL_ID = "ALR"; - public ClassLoader getClassLoader(String sourceDir, String pkg) { - if (mClassLoaders.containsKey(pkg)) { - return mClassLoaders.get(pkg); - } - ClassLoader classLoader = new PathClassLoader(sourceDir, getParentClassLoader()); - mClassLoaders.put(pkg, classLoader); - return classLoader; - } + <T extends Plugin> T getOneShotPlugin(Class<T> cls); + <T extends Plugin> T getOneShotPlugin(String action, Class<?> cls); - private void clearClassLoader(String pkg) { - mClassLoaders.remove(pkg); - } + <T extends Plugin> void addPluginListener(PluginListener<T> listener, Class<?> cls); + <T extends Plugin> void addPluginListener(PluginListener<T> listener, Class<?> cls, + boolean allowMultiple); + <T extends Plugin> void addPluginListener(String action, PluginListener<T> listener, + Class<?> cls); + <T extends Plugin> void addPluginListener(String action, PluginListener<T> listener, + Class cls, boolean allowMultiple); - ClassLoader getParentClassLoader() { - if (mParentClassLoader == null) { - // Lazily load this so it doesn't have any effect on devices without plugins. - mParentClassLoader = new ClassLoaderFilter(getClass().getClassLoader(), - "com.android.systemui.plugin"); - } - return mParentClassLoader; - } + void removePluginListener(PluginListener<?> listener); - public Context getContext(ApplicationInfo info, String pkg) throws NameNotFoundException { - ClassLoader classLoader = getClassLoader(info.sourceDir, pkg); - return new PluginContextWrapper(mContext.createApplicationContext(info, 0), classLoader); - } + <T> boolean dependsOn(Plugin p, Class<T> cls); - public static <P> String getAction(Class<P> cls) { + static <P> String getAction(Class<P> cls) { ProvidesInterface info = cls.getDeclaredAnnotation(ProvidesInterface.class); if (info == null) { throw new RuntimeException(cls + " doesn't provide an interface"); @@ -293,82 +50,4 @@ public class PluginManager extends BroadcastReceiver { } return info.action(); } - - public <T> boolean dependsOn(Plugin p, Class<T> cls) { - for (int i = 0; i < mPluginMap.size(); i++) { - if (mPluginMap.valueAt(i).dependsOn(p, cls)) { - return true; - } - } - return false; - } - - @VisibleForTesting - public static class PluginInstanceManagerFactory { - public <T extends Plugin> PluginInstanceManager createPluginInstanceManager(Context context, - String action, PluginListener<T> listener, boolean allowMultiple, Looper looper, - Class<?> cls, PluginManager manager) { - return new PluginInstanceManager(context, action, listener, allowMultiple, looper, - new VersionInfo().addClass(cls), manager); - } - } - - // This allows plugins to include any libraries or copied code they want by only including - // classes from the plugin library. - private static class ClassLoaderFilter extends ClassLoader { - private final String mPackage; - private final ClassLoader mBase; - - public ClassLoaderFilter(ClassLoader base, String pkg) { - super(ClassLoader.getSystemClassLoader()); - mBase = base; - mPackage = pkg; - } - - @Override - protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { - if (!name.startsWith(mPackage)) super.loadClass(name, resolve); - return mBase.loadClass(name); - } - } - - private class PluginExceptionHandler implements UncaughtExceptionHandler { - private final UncaughtExceptionHandler mHandler; - - private PluginExceptionHandler(UncaughtExceptionHandler handler) { - mHandler = handler; - } - - @Override - public void uncaughtException(Thread thread, Throwable throwable) { - if (SystemProperties.getBoolean("plugin.debugging", false)) { - mHandler.uncaughtException(thread, throwable); - return; - } - // Search for and disable plugins that may have been involved in this crash. - boolean disabledAny = checkStack(throwable); - if (!disabledAny) { - // We couldn't find any plugins involved in this crash, just to be safe - // disable all the plugins, so we can be sure that SysUI is running as - // best as possible. - for (PluginInstanceManager manager : mPluginMap.values()) { - manager.disableAll(); - } - } - - // Run the normal exception handler so we can crash and cleanup our state. - mHandler.uncaughtException(thread, throwable); - } - - private boolean checkStack(Throwable throwable) { - if (throwable == null) return false; - boolean disabledAny = false; - for (StackTraceElement element : throwable.getStackTrace()) { - for (PluginInstanceManager manager : mPluginMap.values()) { - disabledAny |= manager.checkAndDisable(element.getClassName()); - } - } - return disabledAny | checkStack(throwable.getCause()); - } - } } diff --git a/packages/SystemUI/src/com/android/systemui/plugins/PluginManagerImpl.java b/packages/SystemUI/src/com/android/systemui/plugins/PluginManagerImpl.java new file mode 100644 index 000000000000..1fb6c8704587 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/plugins/PluginManagerImpl.java @@ -0,0 +1,358 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package com.android.systemui.plugins; + +import android.app.Notification; +import android.app.Notification.Action; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.content.res.Resources; +import android.net.Uri; +import android.os.Build; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Looper; +import android.os.SystemProperties; +import android.os.UserHandle; +import android.text.TextUtils; +import android.util.ArrayMap; +import android.util.ArraySet; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; +import com.android.systemui.Dependency; +import com.android.systemui.plugins.PluginInstanceManager.PluginContextWrapper; +import com.android.systemui.plugins.PluginInstanceManager.PluginInfo; +import com.android.systemui.plugins.annotations.ProvidesInterface; + +import dalvik.system.PathClassLoader; + +import java.lang.Thread.UncaughtExceptionHandler; +import java.util.Map; + +/** + * @see Plugin + */ +public class PluginManagerImpl extends BroadcastReceiver implements PluginManager { + + static final String DISABLE_PLUGIN = "com.android.systemui.action.DISABLE_PLUGIN"; + + private static PluginManager sInstance; + + private final HandlerThread mBackgroundThread; + private final ArrayMap<PluginListener<?>, PluginInstanceManager> mPluginMap + = new ArrayMap<>(); + private final Map<String, ClassLoader> mClassLoaders = new ArrayMap<>(); + private final ArraySet<String> mOneShotPackages = new ArraySet<>(); + private final Context mContext; + private final PluginInstanceManagerFactory mFactory; + private final boolean isDebuggable; + private final PluginPrefs mPluginPrefs; + private ClassLoaderFilter mParentClassLoader; + private boolean mListening; + private boolean mHasOneShot; + + public PluginManagerImpl(Context context) { + this(context, new PluginInstanceManagerFactory(), + Build.IS_DEBUGGABLE, Thread.getDefaultUncaughtExceptionHandler()); + } + + @VisibleForTesting + PluginManagerImpl(Context context, PluginInstanceManagerFactory factory, boolean debuggable, + UncaughtExceptionHandler defaultHandler) { + mContext = context; + mFactory = factory; + mBackgroundThread = new HandlerThread("Plugins"); + mBackgroundThread.start(); + isDebuggable = debuggable; + mPluginPrefs = new PluginPrefs(mContext); + + PluginExceptionHandler uncaughtExceptionHandler = new PluginExceptionHandler( + defaultHandler); + Thread.setDefaultUncaughtExceptionHandler(uncaughtExceptionHandler); + if (isDebuggable) { + new Handler(mBackgroundThread.getLooper()).post(() -> { + // Plugin dependencies that don't have another good home can go here, but + // dependencies that have better places to init can happen elsewhere. + Dependency.get(PluginDependencyProvider.class) + .allowPluginDependency(ActivityStarter.class); + }); + } + } + + public <T extends Plugin> T getOneShotPlugin(Class<T> cls) { + ProvidesInterface info = cls.getDeclaredAnnotation(ProvidesInterface.class); + if (info == null) { + throw new RuntimeException(cls + " doesn't provide an interface"); + } + if (TextUtils.isEmpty(info.action())) { + throw new RuntimeException(cls + " doesn't provide an action"); + } + return getOneShotPlugin(info.action(), cls); + } + + public <T extends Plugin> T getOneShotPlugin(String action, Class<?> cls) { + if (!isDebuggable) { + // Never ever ever allow these on production builds, they are only for prototyping. + return null; + } + if (Looper.myLooper() != Looper.getMainLooper()) { + throw new RuntimeException("Must be called from UI thread"); + } + PluginInstanceManager<T> p = mFactory.createPluginInstanceManager(mContext, action, null, + false, mBackgroundThread.getLooper(), cls, this); + mPluginPrefs.addAction(action); + PluginInfo<T> info = p.getPlugin(); + if (info != null) { + mOneShotPackages.add(info.mPackage); + mHasOneShot = true; + startListening(); + return info.mPlugin; + } + return null; + } + + public <T extends Plugin> void addPluginListener(PluginListener<T> listener, Class<?> cls) { + addPluginListener(listener, cls, false); + } + + public <T extends Plugin> void addPluginListener(PluginListener<T> listener, Class<?> cls, + boolean allowMultiple) { + addPluginListener(PluginManager.getAction(cls), listener, cls, allowMultiple); + } + + public <T extends Plugin> void addPluginListener(String action, PluginListener<T> listener, + Class<?> cls) { + addPluginListener(action, listener, cls, false); + } + + public <T extends Plugin> void addPluginListener(String action, PluginListener<T> listener, + Class cls, boolean allowMultiple) { + if (!isDebuggable) { + // Never ever ever allow these on production builds, they are only for prototyping. + return; + } + mPluginPrefs.addAction(action); + PluginInstanceManager p = mFactory.createPluginInstanceManager(mContext, action, listener, + allowMultiple, mBackgroundThread.getLooper(), cls, this); + p.loadAll(); + mPluginMap.put(listener, p); + startListening(); + } + + public void removePluginListener(PluginListener<?> listener) { + if (!isDebuggable) { + // Never ever ever allow these on production builds, they are only for prototyping. + return; + } + if (!mPluginMap.containsKey(listener)) return; + mPluginMap.remove(listener).destroy(); + stopListening(); + } + + private void startListening() { + if (mListening) return; + mListening = true; + IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED); + filter.addAction(Intent.ACTION_PACKAGE_CHANGED); + filter.addAction(Intent.ACTION_PACKAGE_REMOVED); + filter.addAction(PLUGIN_CHANGED); + filter.addAction(DISABLE_PLUGIN); + filter.addDataScheme("package"); + mContext.registerReceiver(this, filter); + filter = new IntentFilter(Intent.ACTION_USER_UNLOCKED); + mContext.registerReceiver(this, filter); + } + + private void stopListening() { + // Never stop listening if a one-shot is present. + if (!mListening || mHasOneShot) return; + mListening = false; + mContext.unregisterReceiver(this); + } + + @Override + public void onReceive(Context context, Intent intent) { + if (Intent.ACTION_USER_UNLOCKED.equals(intent.getAction())) { + for (PluginInstanceManager manager : mPluginMap.values()) { + manager.loadAll(); + } + } else if (DISABLE_PLUGIN.equals(intent.getAction())) { + Uri uri = intent.getData(); + ComponentName component = ComponentName.unflattenFromString( + uri.toString().substring(10)); + mContext.getPackageManager().setComponentEnabledSetting(component, + PackageManager.COMPONENT_ENABLED_STATE_DISABLED, + PackageManager.DONT_KILL_APP); + mContext.getSystemService(NotificationManager.class).cancel(component.getClassName(), + SystemMessage.NOTE_PLUGIN); + } else { + Uri data = intent.getData(); + String pkg = data.getEncodedSchemeSpecificPart(); + if (mOneShotPackages.contains(pkg)) { + int icon = mContext.getResources().getIdentifier("tuner", "drawable", + mContext.getPackageName()); + int color = Resources.getSystem().getIdentifier( + "system_notification_accent_color", "color", "android"); + String label = pkg; + try { + PackageManager pm = mContext.getPackageManager(); + label = pm.getApplicationInfo(pkg, 0).loadLabel(pm).toString(); + } catch (NameNotFoundException e) { + } + // Localization not required as this will never ever appear in a user build. + final Notification.Builder nb = + new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID) + .setSmallIcon(icon) + .setWhen(0) + .setShowWhen(false) + .setPriority(Notification.PRIORITY_MAX) + .setVisibility(Notification.VISIBILITY_PUBLIC) + .setColor(mContext.getColor(color)) + .setContentTitle("Plugin \"" + label + "\" has updated") + .setContentText("Restart SysUI for changes to take effect."); + Intent i = new Intent("com.android.systemui.action.RESTART").setData( + Uri.parse("package://" + pkg)); + PendingIntent pi = PendingIntent.getBroadcast(mContext, 0, i, 0); + nb.addAction(new Action.Builder(null, "Restart SysUI", pi).build()); + mContext.getSystemService(NotificationManager.class).notifyAsUser(pkg, + SystemMessage.NOTE_PLUGIN, nb.build(), UserHandle.ALL); + } + clearClassLoader(pkg); + if (!Intent.ACTION_PACKAGE_REMOVED.equals(intent.getAction())) { + for (PluginInstanceManager manager : mPluginMap.values()) { + manager.onPackageChange(pkg); + } + } else { + for (PluginInstanceManager manager : mPluginMap.values()) { + manager.onPackageRemoved(pkg); + } + } + } + } + + public ClassLoader getClassLoader(String sourceDir, String pkg) { + if (mClassLoaders.containsKey(pkg)) { + return mClassLoaders.get(pkg); + } + ClassLoader classLoader = new PathClassLoader(sourceDir, getParentClassLoader()); + mClassLoaders.put(pkg, classLoader); + return classLoader; + } + + private void clearClassLoader(String pkg) { + mClassLoaders.remove(pkg); + } + + ClassLoader getParentClassLoader() { + if (mParentClassLoader == null) { + // Lazily load this so it doesn't have any effect on devices without plugins. + mParentClassLoader = new ClassLoaderFilter(getClass().getClassLoader(), + "com.android.systemui.plugin"); + } + return mParentClassLoader; + } + + public Context getContext(ApplicationInfo info, String pkg) throws NameNotFoundException { + ClassLoader classLoader = getClassLoader(info.sourceDir, pkg); + return new PluginContextWrapper(mContext.createApplicationContext(info, 0), classLoader); + } + + public <T> boolean dependsOn(Plugin p, Class<T> cls) { + for (int i = 0; i < mPluginMap.size(); i++) { + if (mPluginMap.valueAt(i).dependsOn(p, cls)) { + return true; + } + } + return false; + } + + @VisibleForTesting + public static class PluginInstanceManagerFactory { + public <T extends Plugin> PluginInstanceManager createPluginInstanceManager(Context context, + String action, PluginListener<T> listener, boolean allowMultiple, Looper looper, + Class<?> cls, PluginManagerImpl manager) { + return new PluginInstanceManager(context, action, listener, allowMultiple, looper, + new VersionInfo().addClass(cls), manager); + } + } + + // This allows plugins to include any libraries or copied code they want by only including + // classes from the plugin library. + private static class ClassLoaderFilter extends ClassLoader { + private final String mPackage; + private final ClassLoader mBase; + + public ClassLoaderFilter(ClassLoader base, String pkg) { + super(ClassLoader.getSystemClassLoader()); + mBase = base; + mPackage = pkg; + } + + @Override + protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { + if (!name.startsWith(mPackage)) super.loadClass(name, resolve); + return mBase.loadClass(name); + } + } + + private class PluginExceptionHandler implements UncaughtExceptionHandler { + private final UncaughtExceptionHandler mHandler; + + private PluginExceptionHandler(UncaughtExceptionHandler handler) { + mHandler = handler; + } + + @Override + public void uncaughtException(Thread thread, Throwable throwable) { + if (SystemProperties.getBoolean("plugin.debugging", false)) { + mHandler.uncaughtException(thread, throwable); + return; + } + // Search for and disable plugins that may have been involved in this crash. + boolean disabledAny = checkStack(throwable); + if (!disabledAny) { + // We couldn't find any plugins involved in this crash, just to be safe + // disable all the plugins, so we can be sure that SysUI is running as + // best as possible. + for (PluginInstanceManager manager : mPluginMap.values()) { + manager.disableAll(); + } + } + + // Run the normal exception handler so we can crash and cleanup our state. + mHandler.uncaughtException(thread, throwable); + } + + private boolean checkStack(Throwable throwable) { + if (throwable == null) return false; + boolean disabledAny = false; + for (StackTraceElement element : throwable.getStackTrace()) { + for (PluginInstanceManager manager : mPluginMap.values()) { + disabledAny |= manager.checkAndDisable(element.getClassName()); + } + } + return disabledAny | checkStack(throwable.getCause()); + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/recents/Recents.java b/packages/SystemUI/src/com/android/systemui/recents/Recents.java index e635162807e2..d3e939f8b0b4 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/Recents.java +++ b/packages/SystemUI/src/com/android/systemui/recents/Recents.java @@ -60,7 +60,6 @@ import com.android.systemui.recents.events.component.ShowUserToastEvent; import com.android.systemui.recents.events.ui.RecentsDrawnEvent; import com.android.systemui.recents.misc.SystemServicesProxy; import com.android.systemui.recents.model.RecentsTaskLoader; -import com.android.systemui.recents.tv.RecentsTvImpl; import com.android.systemui.stackdivider.Divider; import com.android.systemui.statusbar.CommandQueue; @@ -86,7 +85,6 @@ public class Recents extends SystemUI public final static Set<String> RECENTS_ACTIVITIES = new HashSet<>(); static { RECENTS_ACTIVITIES.add(RecentsImpl.RECENTS_ACTIVITY); - RECENTS_ACTIVITIES.add(RecentsTvImpl.RECENTS_TV_ACTIVITY); } // Purely for experimentation @@ -205,13 +203,7 @@ public class Recents extends SystemUI sTaskLoader = new RecentsTaskLoader(mContext); sConfiguration = new RecentsConfiguration(mContext); mHandler = new Handler(); - UiModeManager uiModeManager = (UiModeManager) mContext. - getSystemService(Context.UI_MODE_SERVICE); - if (uiModeManager.getCurrentModeType() == Configuration.UI_MODE_TYPE_TELEVISION) { - mImpl = new RecentsTvImpl(mContext); - } else { - mImpl = new RecentsImpl(mContext); - } + mImpl = new RecentsImpl(mContext); // Check if there is a recents override package if ("userdebug".equals(Build.TYPE) || "eng".equals(Build.TYPE)) { diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/activity/LaunchTvTaskEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/activity/LaunchTvTaskEvent.java deleted file mode 100644 index 04ca68f293f2..000000000000 --- a/packages/SystemUI/src/com/android/systemui/recents/events/activity/LaunchTvTaskEvent.java +++ /dev/null @@ -1,39 +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.recents.events.activity; - - -import android.graphics.Rect; -import com.android.systemui.recents.events.EventBus; -import com.android.systemui.recents.model.Task; -import com.android.systemui.recents.tv.views.TaskCardView; - -public class LaunchTvTaskEvent extends EventBus.Event { - - public final TaskCardView taskView; - public final Task task; - public final Rect targetTaskBounds; - public final int targetTaskStack; - - public LaunchTvTaskEvent(TaskCardView taskView, Task task, Rect targetTaskBounds, - int targetTaskStack) { - this.taskView = taskView; - this.task = task; - this.targetTaskBounds = targetTaskBounds; - this.targetTaskStack = targetTaskStack; - } - -} diff --git a/packages/SystemUI/src/com/android/systemui/recents/tv/RecentsTvActivity.java b/packages/SystemUI/src/com/android/systemui/recents/tv/RecentsTvActivity.java deleted file mode 100644 index a691a424e779..000000000000 --- a/packages/SystemUI/src/com/android/systemui/recents/tv/RecentsTvActivity.java +++ /dev/null @@ -1,606 +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.recents.tv; - -import android.app.Activity; -import android.app.ActivityOptions; -import android.content.Intent; -import android.graphics.Rect; -import android.os.Bundle; -import android.os.UserHandle; -import android.util.Log; -import android.view.KeyEvent; -import android.view.View; -import android.view.ViewTreeObserver.OnPreDrawListener; -import android.view.WindowManager; -import android.view.accessibility.AccessibilityEvent; -import android.widget.FrameLayout.LayoutParams; - -import com.android.systemui.R; -import com.android.systemui.pip.tv.PipManager; -import com.android.systemui.pip.tv.PipRecentsOverlayManager; -import com.android.systemui.recents.Recents; -import com.android.systemui.recents.RecentsActivityLaunchState; -import com.android.systemui.recents.RecentsConfiguration; -import com.android.systemui.recents.RecentsImpl; -import com.android.systemui.recents.events.EventBus; -import com.android.systemui.recents.events.activity.CancelEnterRecentsWindowAnimationEvent; -import com.android.systemui.recents.events.activity.DismissRecentsToHomeAnimationStarted; -import com.android.systemui.recents.events.activity.EnterRecentsWindowAnimationCompletedEvent; -import com.android.systemui.recents.events.activity.HideRecentsEvent; -import com.android.systemui.recents.events.activity.LaunchTaskFailedEvent; -import com.android.systemui.recents.events.activity.ToggleRecentsEvent; -import com.android.systemui.recents.events.component.RecentsVisibilityChangedEvent; -import com.android.systemui.recents.events.ui.AllTaskViewsDismissedEvent; -import com.android.systemui.recents.events.ui.DeleteTaskDataEvent; -import com.android.systemui.recents.events.ui.UserInteractionEvent; -import com.android.systemui.recents.events.ui.focus.DismissFocusedTaskViewEvent; -import com.android.systemui.recents.misc.SystemServicesProxy; -import com.android.systemui.recents.model.RecentsPackageMonitor; -import com.android.systemui.recents.model.RecentsTaskLoadPlan; -import com.android.systemui.recents.model.RecentsTaskLoader; -import com.android.systemui.recents.model.Task; -import com.android.systemui.recents.model.TaskStack; -import com.android.systemui.recents.tv.animations.HomeRecentsEnterExitAnimationHolder; -import com.android.systemui.recents.tv.views.RecentsTvView; -import com.android.systemui.recents.tv.views.TaskCardView; -import com.android.systemui.recents.tv.views.TaskStackHorizontalGridView; -import com.android.systemui.recents.tv.views.TaskStackHorizontalViewAdapter; -import com.android.systemui.statusbar.phone.StatusBar; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -/** - * The main TV recents activity started by the RecentsImpl. - */ -public class RecentsTvActivity extends Activity implements OnPreDrawListener { - private final static String TAG = "RecentsTvActivity"; - private final static boolean DEBUG = false; - - public final static int EVENT_BUS_PRIORITY = Recents.EVENT_BUS_PRIORITY + 1; - private final static String RECENTS_HOME_INTENT_EXTRA = - "com.android.systemui.recents.tv.RecentsTvActivity.RECENTS_HOME_INTENT_EXTRA"; - - private boolean mFinishedOnStartup; - private RecentsPackageMonitor mPackageMonitor; - private long mLastTabKeyEventTime; - private boolean mIgnoreAltTabRelease; - private boolean mLaunchedFromHome; - private boolean mTalkBackEnabled; - - private RecentsTvView mRecentsView; - private View mPipView; - private TaskStackHorizontalViewAdapter mTaskStackViewAdapter; - private TaskStackHorizontalGridView mTaskStackHorizontalGridView; - private FinishRecentsRunnable mFinishLaunchHomeRunnable; - private HomeRecentsEnterExitAnimationHolder mHomeRecentsEnterExitAnimationHolder; - - private final PipManager mPipManager = PipManager.getInstance(); - private final PipManager.Listener mPipListener = new PipManager.Listener() { - @Override - public void onPipEntered() { - updatePipUI(); - } - - @Override - public void onPipActivityClosed() { - updatePipUI(); - } - - @Override - public void onShowPipMenu() { - updatePipUI(); - } - - @Override - public void onMoveToFullscreen() { - // Recents should be dismissed when PIP moves to fullscreen. If not, Recents will - // be unnecessarily shown in the scenario: PIP->Fullscreen->PIP. - // Do not show Recents close animation because PIP->Fullscreen animation will be shown - // instead. - dismissRecentsToLaunchTargetTaskOrHome(false); - } - - @Override - public void onPipResizeAboutToStart() { } - }; - private PipRecentsOverlayManager mPipRecentsOverlayManager; - private final PipRecentsOverlayManager.Callback mPipRecentsOverlayManagerCallback = - new PipRecentsOverlayManager.Callback() { - @Override - public void onClosed() { - dismissRecentsToLaunchTargetTaskOrHome(true); - } - - @Override - public void onBackPressed() { - RecentsTvActivity.this.onBackPressed(); - } - - @Override - public void onRecentsFocused() { - if (mTalkBackEnabled) { - mTaskStackHorizontalGridView.requestFocus(); - mTaskStackHorizontalGridView.sendAccessibilityEvent( - AccessibilityEvent.TYPE_VIEW_FOCUSED); - } - mTaskStackHorizontalGridView.startFocusGainAnimation(); - } - }; - - private final View.OnFocusChangeListener mPipViewFocusChangeListener = - new View.OnFocusChangeListener() { - @Override - public void onFocusChange(View v, boolean hasFocus) { - if (hasFocus) { - requestPipControlsFocus(); - } - } - }; - - /** - * A common Runnable to finish Recents by launching Home with an animation depending on the - * last activity launch state. Generally we always launch home when we exit Recents rather than - * just finishing the activity since we don't know what is behind Recents in the task stack. - */ - class FinishRecentsRunnable implements Runnable { - Intent mLaunchIntent; - - /** - * Creates a finish runnable that starts the specified intent. - */ - public FinishRecentsRunnable(Intent launchIntent) { - mLaunchIntent = launchIntent; - } - - @Override - public void run() { - try { - ActivityOptions opts = ActivityOptions.makeCustomAnimation(RecentsTvActivity.this, - R.anim.recents_to_launcher_enter, R.anim.recents_to_launcher_exit); - startActivityAsUser(mLaunchIntent, opts.toBundle(), UserHandle.CURRENT); - } catch (Exception e) { - Log.e(TAG, getString(R.string.recents_launch_error_message, "Home"), e); - } - } - } - - private void updateRecentsTasks() { - RecentsTaskLoader loader = Recents.getTaskLoader(); - RecentsTaskLoadPlan plan = RecentsImpl.consumeInstanceLoadPlan(); - if (plan == null) { - plan = loader.createLoadPlan(this); - } - - RecentsConfiguration config = Recents.getConfiguration(); - RecentsActivityLaunchState launchState = config.getLaunchState(); - if (!plan.hasTasks()) { - loader.preloadTasks(plan, -1, !launchState.launchedFromHome); - } - - int numVisibleTasks = TaskCardView.getNumberOfVisibleTasks(getApplicationContext()); - mLaunchedFromHome = launchState.launchedFromHome; - TaskStack stack = plan.getTaskStack(); - RecentsTaskLoadPlan.Options loadOpts = new RecentsTaskLoadPlan.Options(); - loadOpts.runningTaskId = launchState.launchedToTaskId; - loadOpts.numVisibleTasks = numVisibleTasks; - loadOpts.numVisibleTaskThumbnails = numVisibleTasks; - loader.loadTasks(this, plan, loadOpts); - - List stackTasks = stack.getStackTasks(); - Collections.reverse(stackTasks); - if (mTaskStackViewAdapter == null) { - mTaskStackViewAdapter = new TaskStackHorizontalViewAdapter(stackTasks); - mTaskStackHorizontalGridView = mRecentsView - .setTaskStackViewAdapter(mTaskStackViewAdapter); - mHomeRecentsEnterExitAnimationHolder = new HomeRecentsEnterExitAnimationHolder( - getApplicationContext(), mTaskStackHorizontalGridView); - } else { - mTaskStackViewAdapter.setNewStackTasks(stackTasks); - } - mRecentsView.init(stack); - - if (launchState.launchedToTaskId != -1) { - ArrayList<Task> tasks = stack.getStackTasks(); - int taskCount = tasks.size(); - for (int i = 0; i < taskCount; i++) { - Task t = tasks.get(i); - if (t.key.id == launchState.launchedToTaskId) { - t.isLaunchTarget = true; - break; - } - } - } - } - - boolean dismissRecentsToLaunchTargetTaskOrHome(boolean animate) { - SystemServicesProxy ssp = Recents.getSystemServices(); - if (ssp.isRecentsActivityVisible()) { - // If we have a focused Task, launch that Task now - if (mRecentsView.launchPreviousTask(animate)) { - return true; - } - // If none of the other cases apply, then just go Home - dismissRecentsToHome(animate /* animateTaskViews */); - } - return false; - } - - boolean dismissRecentsToFocusedTaskOrHome() { - SystemServicesProxy ssp = Recents.getSystemServices(); - if (ssp.isRecentsActivityVisible()) { - // If we have a focused Task, launch that Task now - if (mRecentsView.launchFocusedTask()) return true; - // If none of the other cases apply, then just go Home - dismissRecentsToHome(true /* animateTaskViews */); - return true; - } - return false; - } - - void dismissRecentsToHome(boolean animateTaskViews) { - Runnable closeSystemWindows = new Runnable() { - @Override - public void run() { - Recents.getSystemServices().sendCloseSystemWindows( - StatusBar.SYSTEM_DIALOG_REASON_HOME_KEY); - } - }; - DismissRecentsToHomeAnimationStarted dismissEvent = - new DismissRecentsToHomeAnimationStarted(animateTaskViews); - dismissEvent.addPostAnimationCallback(mFinishLaunchHomeRunnable); - dismissEvent.addPostAnimationCallback(closeSystemWindows); - - if (mTaskStackHorizontalGridView.getChildCount() > 0 && animateTaskViews) { - mHomeRecentsEnterExitAnimationHolder.startExitAnimation(dismissEvent); - } else { - closeSystemWindows.run(); - mFinishLaunchHomeRunnable.run(); - } - } - - boolean dismissRecentsToHomeIfVisible(boolean animated) { - SystemServicesProxy ssp = Recents.getSystemServices(); - if (ssp.isRecentsActivityVisible()) { - // Return to Home - dismissRecentsToHome(animated); - return true; - } - return false; - } - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - mFinishedOnStartup = false; - - // In the case that the activity starts up before the Recents component has initialized - // (usually when debugging/pushing the SysUI apk), just finish this activity. - SystemServicesProxy ssp = Recents.getSystemServices(); - if (ssp == null) { - mFinishedOnStartup = true; - finish(); - return; - } - mPipRecentsOverlayManager = PipManager.getInstance().getPipRecentsOverlayManager(); - - // Register this activity with the event bus - EventBus.getDefault().register(this, EVENT_BUS_PRIORITY); - - mPackageMonitor = new RecentsPackageMonitor(); - mPackageMonitor.register(this); - - // Set the Recents layout - setContentView(R.layout.recents_on_tv); - - mRecentsView = (RecentsTvView) findViewById(R.id.recents_view); - mRecentsView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE | - View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | - View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION); - - mPipView = findViewById(R.id.pip); - mPipView.setOnFocusChangeListener(mPipViewFocusChangeListener); - // Place mPipView at the PIP bounds for fine tuned focus handling. - Rect pipBounds = mPipManager.getRecentsFocusedPipBounds(); - LayoutParams lp = (LayoutParams) mPipView.getLayoutParams(); - lp.width = pipBounds.width(); - lp.height = pipBounds.height(); - lp.leftMargin = pipBounds.left; - lp.topMargin = pipBounds.top; - mPipView.setLayoutParams(lp); - - mPipRecentsOverlayManager.setCallback(mPipRecentsOverlayManagerCallback); - - getWindow().getAttributes().privateFlags |= - WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY; - - // Create the home intent runnable - Intent homeIntent = new Intent(Intent.ACTION_MAIN, null); - homeIntent.addCategory(Intent.CATEGORY_HOME); - homeIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | - Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); - homeIntent.putExtra(RECENTS_HOME_INTENT_EXTRA, true); - mFinishLaunchHomeRunnable = new FinishRecentsRunnable(homeIntent); - - mPipManager.addListener(mPipListener); - } - - @Override - protected void onNewIntent(Intent intent) { - super.onNewIntent(intent); - setIntent(intent); - } - - @Override - public void onEnterAnimationComplete() { - super.onEnterAnimationComplete(); - if(mLaunchedFromHome) { - mHomeRecentsEnterExitAnimationHolder.startEnterAnimation(mPipManager.isPipShown()); - } - EventBus.getDefault().send(new EnterRecentsWindowAnimationCompletedEvent()); - } - - @Override - public void onResume() { - super.onResume(); - mPipRecentsOverlayManager.onRecentsResumed(); - // Update the recent tasks - updateRecentsTasks(); - - // If this is a new instance from a configuration change, then we have to manually trigger - // the enter animation state, or if recents was relaunched by AM, without going through - // the normal mechanisms - RecentsConfiguration config = Recents.getConfiguration(); - RecentsActivityLaunchState launchState = config.getLaunchState(); - boolean wasLaunchedByAm = !launchState.launchedFromHome && - !launchState.launchedFromApp; - if (wasLaunchedByAm) { - EventBus.getDefault().send(new EnterRecentsWindowAnimationCompletedEvent()); - } - - // Notify that recents is now visible - SystemServicesProxy ssp = Recents.getSystemServices(); - EventBus.getDefault().send(new RecentsVisibilityChangedEvent(this, true)); - if(mTaskStackHorizontalGridView.getStack().getTaskCount() > 1 && !mLaunchedFromHome) { - // If there are 2 or more tasks, and we are not launching from home - // set the selected position to the 2nd task to allow for faster app switching - mTaskStackHorizontalGridView.setSelectedPosition(1); - } else { - mTaskStackHorizontalGridView.setSelectedPosition(0); - } - mRecentsView.getViewTreeObserver().addOnPreDrawListener(this); - - View dismissPlaceholder = findViewById(R.id.dismiss_placeholder); - mTalkBackEnabled = ssp.isTouchExplorationEnabled(); - if (mTalkBackEnabled) { - dismissPlaceholder.setAccessibilityTraversalBefore(R.id.task_list); - dismissPlaceholder.setAccessibilityTraversalAfter(R.id.dismiss_placeholder); - mTaskStackHorizontalGridView.setAccessibilityTraversalAfter(R.id.dismiss_placeholder); - mTaskStackHorizontalGridView.setAccessibilityTraversalBefore(R.id.pip); - dismissPlaceholder.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - mTaskStackHorizontalGridView.requestFocus(); - mTaskStackHorizontalGridView. - sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED); - Task focusedTask = mTaskStackHorizontalGridView.getFocusedTask(); - if (focusedTask != null) { - mTaskStackViewAdapter.removeTask(focusedTask); - EventBus.getDefault().send(new DeleteTaskDataEvent(focusedTask)); - } - } - }); - } - - // Initialize PIP UI - if (mPipManager.isPipShown()) { - if (mTalkBackEnabled) { - // If talkback is on, use the mPipView to handle focus changes - // between recents row and PIP controls. - mPipView.setVisibility(View.VISIBLE); - } else { - mPipView.setVisibility(View.GONE); - } - // When PIP view has focus, recents overlay view will takes the focus - // as if it's the part of the Recents UI. - mPipRecentsOverlayManager.requestFocus(mTaskStackViewAdapter.getItemCount() > 0); - } else { - mPipView.setVisibility(View.GONE); - mPipRecentsOverlayManager.removePipRecentsOverlayView(); - } - } - - @Override - public void onPause() { - super.onPause(); - mPipRecentsOverlayManager.onRecentsPaused(); - } - - @Override - protected void onStop() { - super.onStop(); - - mIgnoreAltTabRelease = false; - // Notify that recents is now hidden - EventBus.getDefault().send(new RecentsVisibilityChangedEvent(this, false)); - - // Workaround for b/22542869, if the RecentsActivity is started again, but without going - // through SystemUI, we need to reset the config launch flags to ensure that we do not - // wait on the system to send a signal that was never queued. - RecentsConfiguration config = Recents.getConfiguration(); - RecentsActivityLaunchState launchState = config.getLaunchState(); - launchState.reset(); - - // Workaround for b/28333917. - finish(); - } - - @Override - protected void onDestroy() { - super.onDestroy(); - - mPipManager.removeListener(mPipListener); - // In the case that the activity finished on startup, just skip the unregistration below - if (mFinishedOnStartup) { - return; - } - - // Unregister any broadcast receivers for the task loader - mPackageMonitor.unregister(); - - EventBus.getDefault().unregister(this); - } - - @Override - public void onTrimMemory(int level) { - RecentsTaskLoader loader = Recents.getTaskLoader(); - if (loader != null) { - loader.onTrimMemory(level); - } - } - - @Override - public boolean onKeyDown(int keyCode, KeyEvent event) { - switch (keyCode) { - case KeyEvent.KEYCODE_DEL: - case KeyEvent.KEYCODE_FORWARD_DEL: { - EventBus.getDefault().send(new DismissFocusedTaskViewEvent()); - return true; - } - default: - break; - } - return super.onKeyDown(keyCode, event); - } - - @Override - public void onUserInteraction() { - EventBus.getDefault().send(new UserInteractionEvent()); - } - - @Override - public void onBackPressed() { - // Back behaves like the recents button so just trigger a toggle event - EventBus.getDefault().send(new ToggleRecentsEvent()); - } - - /**** EventBus events ****/ - - public final void onBusEvent(ToggleRecentsEvent event) { - RecentsActivityLaunchState launchState = Recents.getConfiguration().getLaunchState(); - if (launchState.launchedFromHome) { - dismissRecentsToHome(true /* animateTaskViews */); - } else { - dismissRecentsToLaunchTargetTaskOrHome(true); - } - } - - public final void onBusEvent(HideRecentsEvent event) { - if (event.triggeredFromAltTab) { - // If we are hiding from releasing Alt-Tab, dismiss Recents to the focused app - if (!mIgnoreAltTabRelease) { - dismissRecentsToFocusedTaskOrHome(); - } - } else if (event.triggeredFromHomeKey) { - dismissRecentsToHome(true /* animateTaskViews */); - } else { - // Do nothing - } - } - - public final void onBusEvent(CancelEnterRecentsWindowAnimationEvent event) { - RecentsActivityLaunchState launchState = Recents.getConfiguration().getLaunchState(); - int launchToTaskId = launchState.launchedToTaskId; - if (launchToTaskId != -1 && - (event.launchTask == null || launchToTaskId != event.launchTask.key.id)) { - SystemServicesProxy ssp = Recents.getSystemServices(); - ssp.cancelWindowTransition(launchState.launchedToTaskId); - ssp.cancelThumbnailTransition(getTaskId()); - } - } - - public final void onBusEvent(DeleteTaskDataEvent event) { - // Remove any stored data from the loader - RecentsTaskLoader loader = Recents.getTaskLoader(); - loader.deleteTaskData(event.task, false); - - // Remove the task from activity manager - SystemServicesProxy ssp = Recents.getSystemServices(); - ssp.removeTask(event.task.key.id); - } - - public final void onBusEvent(AllTaskViewsDismissedEvent event) { - if (mPipManager.isPipShown()) { - mRecentsView.showEmptyView(); - mPipRecentsOverlayManager.requestFocus(false); - } else { - dismissRecentsToHome(false); - } - } - - public final void onBusEvent(LaunchTaskFailedEvent event) { - // Return to Home - dismissRecentsToHome(true /* animateTaskViews */); - } - - @Override - public boolean onPreDraw() { - mRecentsView.getViewTreeObserver().removeOnPreDrawListener(this); - // Sets the initial values for enter animation. - // Animation will be started in {@link #onEnterAnimationComplete()} - if (mLaunchedFromHome) { - mHomeRecentsEnterExitAnimationHolder - .setEnterFromHomeStartingAnimationValues(mPipManager.isPipShown()); - } else { - mHomeRecentsEnterExitAnimationHolder - .setEnterFromAppStartingAnimationValues(mPipManager.isPipShown()); - } - // We post to make sure that this information is delivered after this traversals is - // finished. - mRecentsView.post(new Runnable() { - @Override - public void run() { - Recents.getSystemServices().endProlongedAnimations(); - } - }); - return true; - } - - private void updatePipUI() { - if (!mPipManager.isPipShown()) { - mPipRecentsOverlayManager.removePipRecentsOverlayView(); - mTaskStackHorizontalGridView.startFocusLossAnimation(); - } else { - Log.w(TAG, "An activity entered PIP mode while Recents is shown"); - } - } - - /** - * Requests the focus to the PIP controls. - * This starts the relevant recents row animation - * and give focus to the recents overlay if needed. - */ - public void requestPipControlsFocus() { - if (!mPipManager.isPipShown()) { - return; - } - - mTaskStackHorizontalGridView.startFocusLossAnimation(); - mPipRecentsOverlayManager.requestFocus(mTaskStackViewAdapter.getItemCount() > 0); - } -} diff --git a/packages/SystemUI/src/com/android/systemui/recents/tv/RecentsTvImpl.java b/packages/SystemUI/src/com/android/systemui/recents/tv/RecentsTvImpl.java deleted file mode 100644 index ac9a217b2892..000000000000 --- a/packages/SystemUI/src/com/android/systemui/recents/tv/RecentsTvImpl.java +++ /dev/null @@ -1,143 +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.recents.tv; - -import android.app.ActivityManager; -import android.app.ActivityOptions; -import android.content.Context; -import android.content.Intent; -import android.graphics.Bitmap; -import android.graphics.Rect; -import android.os.SystemClock; -import android.os.UserHandle; - -import com.android.systemui.recents.Recents; -import com.android.systemui.recents.RecentsActivityLaunchState; -import com.android.systemui.recents.RecentsConfiguration; -import com.android.systemui.recents.RecentsImpl; -import com.android.systemui.recents.events.EventBus; -import com.android.systemui.recents.events.activity.RecentsActivityStartingEvent; -import com.android.systemui.recents.misc.SystemServicesProxy; -import com.android.systemui.recents.model.RecentsTaskLoader; -import com.android.systemui.recents.model.TaskStack; -import com.android.systemui.recents.model.ThumbnailData; -import com.android.systemui.recents.tv.views.TaskCardView; -import com.android.systemui.pip.tv.PipManager; - -public class RecentsTvImpl extends RecentsImpl{ - public final static String RECENTS_TV_ACTIVITY = - "com.android.systemui.recents.tv.RecentsTvActivity"; - - private static final PipManager mPipManager = PipManager.getInstance(); - - public RecentsTvImpl(Context context) { - super(context); - } - - @Override - protected void startRecentsActivity(ActivityManager.RunningTaskInfo runningTask, - boolean isHomeStackVisible, boolean animate, int growTarget) { - RecentsTaskLoader loader = Recents.getTaskLoader(); - - // In the case where alt-tab is triggered, we never get a preloadRecents() call, so we - // should always preload the tasks now. If we are dragging in recents, reload them as - // the stacks might have changed. - if (mTriggeredFromAltTab || sInstanceLoadPlan == null) { - // Create a new load plan if preloadRecents() was never triggered - sInstanceLoadPlan = loader.createLoadPlan(mContext); - } - if (mTriggeredFromAltTab || !sInstanceLoadPlan.hasTasks()) { - loader.preloadTasks(sInstanceLoadPlan, runningTask.id, !isHomeStackVisible); - } - TaskStack stack = sInstanceLoadPlan.getTaskStack(); - - if (!animate) { - ActivityOptions opts = ActivityOptions.makeCustomAnimation(mContext, -1, -1); - startRecentsActivity(runningTask, opts, false /* fromHome */, false /* fromThumbnail*/); - return; - } - - boolean hasRecentTasks = stack.getTaskCount() > 0; - boolean useThumbnailTransition = (runningTask != null) && !isHomeStackVisible && hasRecentTasks; - - if (useThumbnailTransition) { - // Try starting with a thumbnail transition - ActivityOptions opts = getThumbnailTransitionActivityOptionsForTV(runningTask, - stack.getTaskCount()); - if (opts != null) { - startRecentsActivity(runningTask, opts, false /* fromHome */, true /* fromThumbnail */); - } else { - // Fall through below to the non-thumbnail transition - useThumbnailTransition = false; - } - } - - if (!useThumbnailTransition) { - startRecentsActivity(runningTask, null, true /* fromHome */, false /* fromThumbnail */); - } - mLastToggleTime = SystemClock.elapsedRealtime(); - } - - protected void startRecentsActivity(ActivityManager.RunningTaskInfo runningTask, - ActivityOptions opts, boolean fromHome, boolean fromThumbnail) { - // Update the configuration based on the launch options - RecentsConfiguration config = Recents.getConfiguration(); - RecentsActivityLaunchState launchState = config.getLaunchState(); - launchState.launchedFromHome = fromHome; - launchState.launchedFromApp = fromThumbnail; - launchState.launchedToTaskId = (runningTask != null) ? runningTask.id : -1; - launchState.launchedWithAltTab = mTriggeredFromAltTab; - - Intent intent = new Intent(); - intent.setClassName(RECENTS_PACKAGE, RECENTS_TV_ACTIVITY); - intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK - | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS - | Intent.FLAG_ACTIVITY_TASK_ON_HOME); - - if (opts != null) { - mContext.startActivityAsUser(intent, opts.toBundle(), UserHandle.CURRENT); - } else { - mContext.startActivityAsUser(intent, UserHandle.CURRENT); - } - EventBus.getDefault().send(new RecentsActivityStartingEvent()); - } - - /** - * Creates the activity options for an app->recents transition on TV. - */ - private ActivityOptions getThumbnailTransitionActivityOptionsForTV( - ActivityManager.RunningTaskInfo runningTask, int numTasks) { - Rect rect = TaskCardView.getStartingCardThumbnailRect( - mContext, !mPipManager.isPipShown(), numTasks); - SystemServicesProxy ssp = Recents.getSystemServices(); - ThumbnailData thumbnailData = ssp.getTaskThumbnail(runningTask.id); - if (thumbnailData.thumbnail != null) { - Bitmap thumbnail = Bitmap.createScaledBitmap(thumbnailData.thumbnail, rect.width(), - rect.height(), false); - return ActivityOptions.makeThumbnailAspectScaleDownAnimation(mDummyStackView, - thumbnail, (int) rect.left, (int) rect.top, (int) rect.width(), - (int) rect.height(), mHandler, null); - } - // If both the screenshot and thumbnail fails, then just fall back to the default transition - return getUnknownTransitionActivityOptions(); - } - - @Override - public void onVisibilityChanged(Context context, boolean visible) { - Recents.getSystemServices().setRecentsVisibility(visible); - } -} diff --git a/packages/SystemUI/src/com/android/systemui/recents/tv/animations/DismissAnimationsHolder.java b/packages/SystemUI/src/com/android/systemui/recents/tv/animations/DismissAnimationsHolder.java deleted file mode 100644 index 65f5fffc5acc..000000000000 --- a/packages/SystemUI/src/com/android/systemui/recents/tv/animations/DismissAnimationsHolder.java +++ /dev/null @@ -1,162 +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.recents.tv.animations; - -import android.animation.Animator.AnimatorListener; -import android.content.res.Resources; -import android.graphics.drawable.TransitionDrawable; -import android.view.View; -import android.widget.ImageView; -import android.widget.LinearLayout; -import android.widget.TextView; -import com.android.systemui.Interpolators; -import com.android.systemui.recents.tv.views.TaskCardView; - -import com.android.systemui.R; - -public class DismissAnimationsHolder { - private LinearLayout mInfoField; - private View mThumbnailView; - - private int mDismissEnterYDelta; - private int mDismissStartYDelta; - - private ImageView mCardDismissIcon; - private TransitionDrawable mDismissDrawable; - private TextView mDismissText; - - private float mDismissIconNotInDismissStateAlpha; - private long mShortDuration; - private long mLongDuration; - - public DismissAnimationsHolder(TaskCardView taskCardView) { - - mInfoField = (LinearLayout) taskCardView.findViewById(R.id.card_info_field); - mThumbnailView = taskCardView.findViewById(R.id.card_view_thumbnail); - mCardDismissIcon = (ImageView) taskCardView.findViewById(R.id.dismiss_icon); - mDismissDrawable = (TransitionDrawable) mCardDismissIcon.getDrawable(); - mDismissDrawable.setCrossFadeEnabled(true); - mDismissText = (TextView) taskCardView.findViewById(R.id.card_dismiss_text); - - Resources res = taskCardView.getResources(); - mDismissEnterYDelta = res.getDimensionPixelOffset(R.dimen.recents_tv_dismiss_shift_down); - mDismissStartYDelta = mDismissEnterYDelta * 2; - mShortDuration = res.getInteger(R.integer.dismiss_short_duration); - mLongDuration = res.getInteger(R.integer.dismiss_long_duration); - mDismissIconNotInDismissStateAlpha = res.getFloat(R.integer.dismiss_unselected_alpha); - } - - public void startEnterAnimation() { - mCardDismissIcon.animate() - .setDuration(mShortDuration) - .setInterpolator(Interpolators.FAST_OUT_SLOW_IN) - .alpha(1.0f) - .withStartAction(new Runnable() { - @Override - public void run() { - mDismissDrawable.startTransition(0); - } - }); - - mDismissText.animate() - .setDuration(mShortDuration) - .setInterpolator(Interpolators.FAST_OUT_SLOW_IN) - .alpha(1.0f); - - mInfoField.animate() - .setDuration(mShortDuration) - .setInterpolator(Interpolators.FAST_OUT_SLOW_IN) - .translationY(mDismissEnterYDelta) - .alpha(0.5f); - - mThumbnailView.animate() - .setDuration(mShortDuration) - .setInterpolator(Interpolators.FAST_OUT_SLOW_IN) - .translationY(mDismissEnterYDelta) - .alpha(0.5f); - } - - public void startExitAnimation() { - mCardDismissIcon.animate() - .setDuration(mShortDuration) - .setInterpolator(Interpolators.FAST_OUT_SLOW_IN) - .alpha(mDismissIconNotInDismissStateAlpha) - .withEndAction(new Runnable() { - @Override - public void run() { - mDismissDrawable.reverseTransition(0); - } - }); - - mDismissText.animate() - .setDuration(mShortDuration) - .setInterpolator(Interpolators.FAST_OUT_SLOW_IN) - .alpha(0.0f); - - mInfoField.animate() - .setDuration(mShortDuration) - .setInterpolator(Interpolators.FAST_OUT_SLOW_IN) - .translationY(0) - .alpha(1.0f); - - mThumbnailView.animate() - .setDuration(mShortDuration) - .setInterpolator(Interpolators.FAST_OUT_SLOW_IN) - .translationY(0) - .alpha(1.0f); - } - - public void startDismissAnimation(AnimatorListener listener) { - mCardDismissIcon.animate() - .setDuration(mShortDuration) - .setInterpolator(Interpolators.FAST_OUT_SLOW_IN) - .alpha(0.0f) - .withEndAction(new Runnable() { - @Override - public void run() { - mDismissDrawable.reverseTransition(0); - } - }); - - mDismissText.animate() - .setDuration(mShortDuration) - .setInterpolator(Interpolators.FAST_OUT_SLOW_IN) - .alpha(0.0f); - - mInfoField.animate() - .setDuration(mLongDuration) - .setInterpolator(Interpolators.FAST_OUT_SLOW_IN) - .translationY(mDismissStartYDelta) - .alpha(0.0f) - .setListener(listener); - - mThumbnailView.animate() - .setDuration(mLongDuration) - .setInterpolator(Interpolators.FAST_OUT_SLOW_IN) - .translationY(mDismissStartYDelta) - .alpha(0.0f); - } - - public void reset() { - mInfoField.setAlpha(1.0f); - mInfoField.setTranslationY(0); - mInfoField.animate().setListener(null); - mThumbnailView.setAlpha(1.0f); - mThumbnailView.setTranslationY(0); - mCardDismissIcon.setAlpha(0.0f); - mDismissText.setAlpha(0.0f); - } -} diff --git a/packages/SystemUI/src/com/android/systemui/recents/tv/animations/HomeRecentsEnterExitAnimationHolder.java b/packages/SystemUI/src/com/android/systemui/recents/tv/animations/HomeRecentsEnterExitAnimationHolder.java deleted file mode 100644 index a673c8c67696..000000000000 --- a/packages/SystemUI/src/com/android/systemui/recents/tv/animations/HomeRecentsEnterExitAnimationHolder.java +++ /dev/null @@ -1,110 +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.recents.tv.animations; - -import android.content.Context; -import com.android.systemui.Interpolators; -import com.android.systemui.R; -import com.android.systemui.recents.events.activity.DismissRecentsToHomeAnimationStarted; -import com.android.systemui.recents.tv.views.TaskCardView; -import com.android.systemui.recents.tv.views.TaskStackHorizontalGridView; - - -public class HomeRecentsEnterExitAnimationHolder { - - private Context mContext; - private TaskStackHorizontalGridView mGridView; - private float mDimAlpha; - private long mDelay; - private int mDuration; - private int mTranslationX; - - public HomeRecentsEnterExitAnimationHolder(Context context, - TaskStackHorizontalGridView gridView) { - mContext = context; - mGridView = gridView; - mDimAlpha = mContext.getResources().getFloat(R.dimen.recents_recents_row_dim_alpha); - mTranslationX = mContext.getResources() - .getDimensionPixelSize(R.dimen.recents_tv_home_recents_shift); - mDelay = mContext.getResources().getInteger(R.integer.recents_home_delay); - mDuration = mContext.getResources().getInteger(R.integer.recents_home_duration); - } - - public void startEnterAnimation(boolean isPipShown) { - for(int i = 0; i < mGridView.getChildCount(); i++) { - TaskCardView view = (TaskCardView) mGridView.getChildAt(i); - long delay = Math.max(mDelay * i, 0); - view.setTranslationX(-mTranslationX); - view.animate() - .alpha(isPipShown ? mDimAlpha : 1.0f) - .translationX(0) - .setDuration(mDuration) - .setStartDelay(delay) - .setInterpolator(Interpolators.FAST_OUT_SLOW_IN); - } - } - - public void startExitAnimation(DismissRecentsToHomeAnimationStarted dismissEvent) { - for(int i = mGridView.getChildCount() - 1; i >= 0; i--) { - TaskCardView view = (TaskCardView) mGridView.getChildAt(i); - long delay = Math.max(mDelay * (mGridView.getChildCount() - 1 - i), 0); - view.animate() - .alpha(0.0f) - .translationXBy(-mTranslationX) - .setDuration(mDuration) - .setStartDelay(delay) - .setInterpolator(Interpolators.FAST_OUT_SLOW_IN); - if(i == 0) { - view.animate().setListener(dismissEvent.getAnimationTrigger() - .decrementOnAnimationEnd()); - dismissEvent.getAnimationTrigger().increment(); - } - } - } - - /** - * Sets the initial values Recents enter animation - * when Recents is started from the Launcher. - */ - public void setEnterFromHomeStartingAnimationValues(boolean isPipShown) { - for(int i = 0; i < mGridView.getChildCount(); i++) { - TaskCardView view = (TaskCardView) mGridView.getChildAt(i); - view.setTranslationX(0); - view.setAlpha(0.0f); - view.getInfoFieldView().setAlpha(isPipShown ? 0 : 1f); - if (isPipShown && view.hasFocus()) { - view.getViewFocusAnimator().changeSize(false); - } - } - } - - /** - * Sets the initial values Recents enter animation - * when Recents is started from an app. - */ - public void setEnterFromAppStartingAnimationValues(boolean isPipShown) { - for(int i = 0; i < mGridView.getChildCount(); i++) { - TaskCardView view = (TaskCardView) mGridView.getChildAt(i); - view.setTranslationX(0); - view.setAlpha(isPipShown ? mDimAlpha : 1f); - view.getInfoFieldView().setAlpha(isPipShown ? 0 : 1f); - if (isPipShown && view.hasFocus()) { - view.getViewFocusAnimator().changeSize(false); - } - } - } -} diff --git a/packages/SystemUI/src/com/android/systemui/recents/tv/animations/RecentsRowFocusAnimationHolder.java b/packages/SystemUI/src/com/android/systemui/recents/tv/animations/RecentsRowFocusAnimationHolder.java deleted file mode 100644 index 8a4cf399b813..000000000000 --- a/packages/SystemUI/src/com/android/systemui/recents/tv/animations/RecentsRowFocusAnimationHolder.java +++ /dev/null @@ -1,94 +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.recents.tv.animations; - -import android.animation.Animator; -import android.animation.AnimatorSet; -import android.animation.ObjectAnimator; -import android.content.res.Resources; -import android.view.View; - -import com.android.systemui.Interpolators; -import com.android.systemui.R; -import com.android.systemui.recents.tv.views.TaskCardView; - -/** - * Recents row's focus animation with PIP controls. - */ -public class RecentsRowFocusAnimationHolder { - private final View mView; - private final View mTitleView; - - private AnimatorSet mFocusGainAnimatorSet; - private AnimatorSet mFocusLossAnimatorSet; - - public RecentsRowFocusAnimationHolder(View view, View titleView) { - mView = view; - mTitleView = titleView; - - Resources res = view.getResources(); - int duration = res.getInteger(R.integer.recents_tv_pip_focus_anim_duration); - float dimAlpha = res.getFloat(R.dimen.recents_recents_row_dim_alpha); - - mFocusGainAnimatorSet = new AnimatorSet(); - mFocusGainAnimatorSet.playTogether( - ObjectAnimator.ofFloat(mView, "alpha", 1f), - ObjectAnimator.ofFloat(mTitleView, "alpha", 1f)); - mFocusGainAnimatorSet.setDuration(duration); - mFocusGainAnimatorSet.setInterpolator(Interpolators.FAST_OUT_SLOW_IN); - - mFocusLossAnimatorSet = new AnimatorSet(); - mFocusLossAnimatorSet.playTogether( - // Animation doesn't start from the current value (1f) sometimes, - // so specify the desired initial value here. - ObjectAnimator.ofFloat(mView, "alpha", 1f, dimAlpha), - ObjectAnimator.ofFloat(mTitleView, "alpha", 0f)); - mFocusLossAnimatorSet.setDuration(duration); - mFocusLossAnimatorSet.setInterpolator(Interpolators.FAST_OUT_SLOW_IN); - } - - /** - * Starts the Recents row's focus gain animation. - */ - public void startFocusGainAnimation() { - cancelAnimator(mFocusLossAnimatorSet); - mFocusGainAnimatorSet.start(); - } - - /** - * Starts the Recents row's focus loss animation. - */ - public void startFocusLossAnimation() { - cancelAnimator(mFocusGainAnimatorSet); - mFocusLossAnimatorSet.start(); - } - - /** - * Resets the views immediately and ends the animations. - */ - public void reset() { - cancelAnimator(mFocusLossAnimatorSet); - cancelAnimator(mFocusGainAnimatorSet); - mView.setAlpha(1f); - mTitleView.setAlpha(1f); - } - - private static void cancelAnimator(Animator animator) { - if (animator.isStarted()) { - animator.cancel(); - } - } -} diff --git a/packages/SystemUI/src/com/android/systemui/recents/tv/animations/ViewFocusAnimator.java b/packages/SystemUI/src/com/android/systemui/recents/tv/animations/ViewFocusAnimator.java deleted file mode 100644 index 72fd7a4114ac..000000000000 --- a/packages/SystemUI/src/com/android/systemui/recents/tv/animations/ViewFocusAnimator.java +++ /dev/null @@ -1,154 +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.recents.tv.animations; - -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.animation.ObjectAnimator; -import android.content.res.Resources; -import android.util.TypedValue; -import android.view.View; -import android.view.ViewGroup; -import android.view.animation.AccelerateDecelerateInterpolator; -import android.view.animation.Interpolator; - -import com.android.systemui.R; -import com.android.systemui.recents.tv.views.TaskCardView; - -public class ViewFocusAnimator implements View.OnFocusChangeListener { - private final float mUnselectedScale; - private final float mSelectedScale; - private final float mSelectedScaleDelta; - private final float mUnselectedZ; - private final float mSelectedZDelta; - private final float mUnselectedSpacing; - private final float mSelectedSpacingDelta; - private final float mDismissIconAlpha; - private final int mAnimDuration; - private final Interpolator mFocusInterpolator; - - protected TaskCardView mTargetView; - private float mFocusProgress; - - ObjectAnimator mFocusAnimation; - - public ViewFocusAnimator(TaskCardView view) { - mTargetView = view; - final Resources res = view.getResources(); - - mTargetView.setOnFocusChangeListener(this); - - TypedValue out = new TypedValue(); - res.getValue(R.integer.unselected_scale, out, true); - mUnselectedScale = out.getFloat(); - res.getValue(R.integer.selected_scale, out, true); - mSelectedScale = out.getFloat(); - mSelectedScaleDelta = mSelectedScale - mUnselectedScale; - - mUnselectedZ = res.getDimensionPixelOffset(R.dimen.recents_tv_unselected_item_z); - mSelectedZDelta = res.getDimensionPixelOffset(R.dimen.recents_tv_selected_item_z_delta); - - mUnselectedSpacing = res.getDimensionPixelOffset(R.dimen.recents_tv_gird_card_spacing); - mSelectedSpacingDelta = res.getDimensionPixelOffset(R.dimen.recents_tv_gird_focused_card_delta); - - mAnimDuration = res.getInteger(R.integer.item_scale_anim_duration); - - mFocusInterpolator = new AccelerateDecelerateInterpolator(); - - mFocusAnimation = ObjectAnimator.ofFloat(this, "focusProgress", 0.0f); - mFocusAnimation.setDuration(mAnimDuration); - mFocusAnimation.setInterpolator(mFocusInterpolator); - - mDismissIconAlpha = res.getFloat(R.integer.dismiss_unselected_alpha); - - setFocusProgress(0.0f); - - mFocusAnimation.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationStart(Animator animation) { - mTargetView.setHasTransientState(true); - } - - @Override - public void onAnimationEnd(Animator animation) { - mTargetView.setHasTransientState(false); - } - }); - } - - private void setFocusProgress(float level) { - mFocusProgress = level; - - float scale = mUnselectedScale + (level * mSelectedScaleDelta); - float z = mUnselectedZ + (level * mSelectedZDelta); - float spacing = mUnselectedSpacing + (level * mSelectedSpacingDelta); - - mTargetView.setScaleX(scale); - mTargetView.setScaleY(scale); - - mTargetView.setPadding((int) spacing, mTargetView.getPaddingTop(), - (int) spacing, mTargetView.getPaddingBottom()); - - mTargetView.getDismissIconView().setAlpha(mDismissIconAlpha * level); - mTargetView.getThumbnailView().setZ(z); - mTargetView.getDismissIconView().setZ(z); - } - - private void animateFocus(boolean focused) { - if (mFocusAnimation.isStarted()) { - mFocusAnimation.cancel(); - } - - float target = focused ? 1.0f : 0.0f; - - if (mFocusProgress != target) { - mFocusAnimation.setFloatValues(mFocusProgress, target); - mFocusAnimation.start(); - } - } - - @Override - public void onFocusChange(View v, boolean hasFocus) { - if (v != mTargetView) { - return; - } - changeSize(hasFocus); - } - - /** - * Changes the size of the {@link TaskCardView} to show its focused state. - */ - public void changeSize(boolean hasFocus) { - ViewGroup.LayoutParams lp = mTargetView.getLayoutParams(); - int width = lp.width; - int height = lp.height; - - if (width < 0 && height < 0) { - mTargetView.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED); - } - - if (mTargetView.isAttachedToWindow() && mTargetView.hasWindowFocus() && - mTargetView.getVisibility() == View.VISIBLE) { - animateFocus(hasFocus); - } else { - // Set focus immediately. - if (mFocusAnimation.isStarted()) { - mFocusAnimation.cancel(); - } - setFocusProgress(hasFocus ? 1.0f : 0.0f); - } - } -} diff --git a/packages/SystemUI/src/com/android/systemui/recents/tv/views/RecentsTvTransitionHelper.java b/packages/SystemUI/src/com/android/systemui/recents/tv/views/RecentsTvTransitionHelper.java deleted file mode 100644 index 2894cd885a38..000000000000 --- a/packages/SystemUI/src/com/android/systemui/recents/tv/views/RecentsTvTransitionHelper.java +++ /dev/null @@ -1,144 +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.recents.tv.views; - -import android.annotation.Nullable; -import android.app.ActivityOptions; -import android.content.Context; -import android.graphics.Bitmap; -import android.graphics.Rect; -import android.os.Bundle; -import android.os.Handler; -import android.os.IRemoteCallback; -import android.os.RemoteException; -import android.util.Log; -import android.view.WindowManagerGlobal; -import com.android.internal.annotations.GuardedBy; -import com.android.systemui.recents.Recents; -import com.android.systemui.recents.events.EventBus; -import com.android.systemui.recents.events.activity.*; -import com.android.systemui.recents.misc.SystemServicesProxy; -import com.android.systemui.recents.model.Task; -import com.android.systemui.recents.model.TaskStack; - -import static android.app.ActivityManager.StackId.INVALID_STACK_ID; - - -public class RecentsTvTransitionHelper { - private static final String TAG = "RecentsTvTransitionHelper"; - - private Context mContext; - private Handler mHandler; - - public RecentsTvTransitionHelper(Context context, Handler handler) { - mContext = context; - mHandler = handler; - } - - public void launchTaskFromRecents(final TaskStack stack, @Nullable final Task task, - final TaskStackHorizontalGridView stackView, final TaskCardView taskView, - final Rect bounds, int destinationStack) { - final ActivityOptions opts = ActivityOptions.makeBasic(); - if (bounds != null) { - opts.setLaunchBounds(bounds.isEmpty() ? null : bounds); - } - - final ActivityOptions.OnAnimationStartedListener animStartedListener; - if (task.thumbnail != null && task.thumbnail.getWidth() > 0 && - task.thumbnail.getHeight() > 0) { - animStartedListener = new ActivityOptions.OnAnimationStartedListener() { - @Override - public void onAnimationStarted() { - // If we are launching into another task, cancel the previous task's - // window transition - EventBus.getDefault().send(new CancelEnterRecentsWindowAnimationEvent(task)); - EventBus.getDefault().send(new ExitRecentsWindowFirstAnimationFrameEvent()); - } - }; - } else { - // This is only the case if the task is not on screen (scrolled offscreen for example) - animStartedListener = new ActivityOptions.OnAnimationStartedListener() { - @Override - public void onAnimationStarted() { - EventBus.getDefault().send(new ExitRecentsWindowFirstAnimationFrameEvent()); - } - }; - } - - if (taskView == null) { - // If there is no task view, then we do not need to worry about animating out occluding - // task views, and we can launch immediately - startTaskActivity(stack, task, taskView, opts, animStartedListener); - } else { - LaunchTvTaskStartedEvent launchStartedEvent = new LaunchTvTaskStartedEvent(taskView); - EventBus.getDefault().send(launchStartedEvent); - startTaskActivity(stack, task, taskView, opts, animStartedListener); - } - } - - private void startTaskActivity(TaskStack stack, Task task, @Nullable TaskCardView taskView, - ActivityOptions opts,final ActivityOptions.OnAnimationStartedListener animStartedListener) { - SystemServicesProxy ssp = Recents.getSystemServices(); - if (ssp.startActivityFromRecents(mContext, task.key, task.title, opts, INVALID_STACK_ID)) { - // Keep track of the index of the task launch - int taskIndexFromFront = 0; - int taskIndex = stack.indexOfStackTask(task); - if (taskIndex > -1) { - taskIndexFromFront = stack.getTaskCount() - taskIndex - 1; - } - EventBus.getDefault().send(new LaunchTaskSucceededEvent(taskIndexFromFront)); - } else { - // Keep track of failed launches - EventBus.getDefault().send(new LaunchTaskFailedEvent()); - } - - Rect taskRect = taskView.getFocusedThumbnailRect(); - // Check both the rect and the thumbnail for null. The rect can be null if the user - // decides to disallow animations, so automatic scrolling does not happen properly. - - // The thumbnail can be null if the app was partially launched on TV. In this case - // we do not override the transition. - if (taskRect == null || task.thumbnail == null) { - return; - } - - IRemoteCallback.Stub callback = null; - if (animStartedListener != null) { - callback = new IRemoteCallback.Stub() { - @Override - public void sendResult(Bundle data) throws RemoteException { - mHandler.post(new Runnable() { - @Override - public void run() { - if (animStartedListener != null) { - animStartedListener.onAnimationStarted(); - } - } - }); - } - }; - } - try { - Bitmap thumbnail = Bitmap.createScaledBitmap(task.thumbnail, taskRect.width(), - taskRect.height(), false); - WindowManagerGlobal.getWindowManagerService() - .overridePendingAppTransitionAspectScaledThumb(thumbnail, taskRect.left, - taskRect.top, taskRect.width(), taskRect.height(), callback, true); - } catch (RemoteException e) { - Log.w(TAG, "Failed to override transition: " + e); - } - } -} diff --git a/packages/SystemUI/src/com/android/systemui/recents/tv/views/RecentsTvView.java b/packages/SystemUI/src/com/android/systemui/recents/tv/views/RecentsTvView.java deleted file mode 100644 index 9db8071bc277..000000000000 --- a/packages/SystemUI/src/com/android/systemui/recents/tv/views/RecentsTvView.java +++ /dev/null @@ -1,269 +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.recents.tv.views; - -import android.content.Context; -import android.graphics.Rect; -import android.os.Handler; -import android.support.v7.widget.RecyclerView; -import android.util.AttributeSet; -import android.util.Log; -import android.view.LayoutInflater; -import android.view.View; -import android.view.WindowInsets; -import android.widget.FrameLayout; - -import com.android.systemui.R; -import com.android.systemui.recents.Recents; -import com.android.systemui.recents.RecentsActivity; -import com.android.systemui.recents.RecentsActivityLaunchState; -import com.android.systemui.recents.RecentsConfiguration; -import com.android.systemui.recents.events.EventBus; -import com.android.systemui.recents.events.activity.CancelEnterRecentsWindowAnimationEvent; -import com.android.systemui.recents.events.activity.DismissRecentsToHomeAnimationStarted; -import com.android.systemui.recents.events.activity.LaunchTvTaskEvent; -import com.android.systemui.recents.events.component.RecentsVisibilityChangedEvent; -import com.android.systemui.recents.misc.SystemServicesProxy; -import com.android.systemui.recents.model.Task; -import com.android.systemui.recents.model.TaskStack; -import com.android.systemui.recents.tv.animations.RecentsRowFocusAnimationHolder; -import android.support.v7.widget.RecyclerView.OnScrollListener; -import static android.app.ActivityManager.StackId.INVALID_STACK_ID; - -/** - * Top level layout of recents for TV. This will show the TaskStacks using a HorizontalGridView. - */ -public class RecentsTvView extends FrameLayout { - - private static final String TAG = "RecentsTvView"; - private static final boolean DEBUG = false; - - private TaskStack mStack; - private TaskStackHorizontalGridView mTaskStackHorizontalView; - private View mEmptyView; - private View mDismissPlaceholder; - private RecentsRowFocusAnimationHolder mEmptyViewFocusAnimationHolder; - private boolean mAwaitingFirstLayout = true; - private Rect mSystemInsets = new Rect(); - private RecentsTvTransitionHelper mTransitionHelper; - private final Handler mHandler = new Handler(); - private OnScrollListener mScrollListener; - public RecentsTvView(Context context) { - this(context, null); - } - - public RecentsTvView(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public RecentsTvView(Context context, AttributeSet attrs, int defStyleAttr) { - this(context, attrs, defStyleAttr, 0); - } - - public RecentsTvView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { - super(context, attrs, defStyleAttr, defStyleRes); - - setWillNotDraw(false); - - LayoutInflater inflater = LayoutInflater.from(context); - mEmptyView = inflater.inflate(R.layout.recents_tv_empty, this, false); - addView(mEmptyView); - - mTransitionHelper = new RecentsTvTransitionHelper(mContext, mHandler); - } - - @Override - protected void onFinishInflate() { - super.onFinishInflate(); - mDismissPlaceholder = findViewById(R.id.dismiss_placeholder); - mTaskStackHorizontalView = (TaskStackHorizontalGridView) findViewById(R.id.task_list); - } - - /** - * Initialize the view. - */ - public void init(TaskStack stack) { - RecentsConfiguration config = Recents.getConfiguration(); - RecentsActivityLaunchState launchState = config.getLaunchState(); - mStack = stack; - - mTaskStackHorizontalView.init(stack); - - if (stack.getStackTaskCount() > 0) { - hideEmptyView(); - } else { - showEmptyView(); - } - - // Layout with the new stack - requestLayout(); - } - - public boolean launchFocusedTask() { - if (mTaskStackHorizontalView != null) { - Task task = mTaskStackHorizontalView.getFocusedTask(); - if (task != null) { - launchTaskFomRecents(task, true); - return true; - } - } - return false; - } - - /** Launches the task that recents was launched from if possible */ - public boolean launchPreviousTask(boolean animate) { - if (mTaskStackHorizontalView != null) { - TaskStack stack = mTaskStackHorizontalView.getStack(); - Task task = stack.getLaunchTarget(); - if (task != null) { - launchTaskFomRecents(task, animate); - return true; - } - } - return false; - } - - /** - * Launch the given task from recents with animation. If the task is not focused, this will - * attempt to scroll to focus the task before launching. - * @param task - */ - private void launchTaskFomRecents(final Task task, boolean animate) { - if (!animate) { - SystemServicesProxy ssp = Recents.getSystemServices(); - ssp.startActivityFromRecents(getContext(), task.key, task.title, null, - INVALID_STACK_ID); - return; - } - mTaskStackHorizontalView.requestFocus(); - Task focusedTask = mTaskStackHorizontalView.getFocusedTask(); - if (focusedTask != null && task != focusedTask) { - if (mScrollListener != null) { - mTaskStackHorizontalView.removeOnScrollListener(mScrollListener); - } - mScrollListener = new OnScrollListener() { - @Override - public void onScrollStateChanged(RecyclerView recyclerView, int newState) { - super.onScrollStateChanged(recyclerView, newState); - if (newState == RecyclerView.SCROLL_STATE_IDLE) { - TaskCardView cardView = mTaskStackHorizontalView.getChildViewForTask(task); - if (cardView != null) { - mTransitionHelper.launchTaskFromRecents(mStack, task, - mTaskStackHorizontalView, cardView, null, INVALID_STACK_ID); - } else { - // This should not happen normally. If this happens then the data in - // the grid view was altered during the scroll. Log error and launch - // task with no animation. - Log.e(TAG, "Card view for task : " + task + ", returned null."); - SystemServicesProxy ssp = Recents.getSystemServices(); - ssp.startActivityFromRecents(getContext(), task.key, task.title, null, - INVALID_STACK_ID); - } - mTaskStackHorizontalView.removeOnScrollListener(mScrollListener); - } - } - }; - mTaskStackHorizontalView.addOnScrollListener(mScrollListener); - mTaskStackHorizontalView.setSelectedPositionSmooth( - ((TaskStackHorizontalViewAdapter) mTaskStackHorizontalView.getAdapter()) - .getPositionOfTask(task)); - } else { - mTransitionHelper.launchTaskFromRecents(mStack, task, mTaskStackHorizontalView, - mTaskStackHorizontalView.getChildViewForTask(task), null, - INVALID_STACK_ID); - } - } - - /** - * Hides the task stack and shows the empty view. - */ - public void showEmptyView() { - mEmptyView.setVisibility(View.VISIBLE); - mTaskStackHorizontalView.setVisibility(View.GONE); - if (Recents.getSystemServices().isTouchExplorationEnabled()) { - mDismissPlaceholder.setVisibility(View.GONE); - } - } - - /** - * Shows the task stack and hides the empty view. - */ - public void hideEmptyView() { - mEmptyView.setVisibility(View.GONE); - mTaskStackHorizontalView.setVisibility(View.VISIBLE); - if (Recents.getSystemServices().isTouchExplorationEnabled()) { - mDismissPlaceholder.setVisibility(View.VISIBLE); - } - } - - /** - * Returns the last known system insets. - */ - public Rect getSystemInsets() { - return mSystemInsets; - } - - @Override - protected void onAttachedToWindow() { - EventBus.getDefault().register(this, RecentsActivity.EVENT_BUS_PRIORITY + 1); - super.onAttachedToWindow(); - } - - @Override - protected void onDetachedFromWindow() { - super.onDetachedFromWindow(); - EventBus.getDefault().unregister(this); - } - - @Override - public WindowInsets onApplyWindowInsets(WindowInsets insets) { - mSystemInsets.set(insets.getSystemWindowInsets()); - requestLayout(); - return insets; - } - - /**** EventBus Events ****/ - - public final void onBusEvent(LaunchTvTaskEvent event) { - mTransitionHelper.launchTaskFromRecents(mStack, event.task, mTaskStackHorizontalView, - event.taskView, event.targetTaskBounds, event.targetTaskStack); - } - - public final void onBusEvent(DismissRecentsToHomeAnimationStarted event) { - // If we are going home, cancel the previous task's window transition - EventBus.getDefault().send(new CancelEnterRecentsWindowAnimationEvent(null)); - } - - public final void onBusEvent(RecentsVisibilityChangedEvent event) { - if (!event.visible) { - // Reset the view state - mAwaitingFirstLayout = true; - } - } - - public TaskStackHorizontalGridView setTaskStackViewAdapter( - TaskStackHorizontalViewAdapter taskStackViewAdapter) { - if (mTaskStackHorizontalView != null) { - mTaskStackHorizontalView.setAdapter(taskStackViewAdapter); - taskStackViewAdapter.setTaskStackHorizontalGridView(mTaskStackHorizontalView); - } - return mTaskStackHorizontalView; - } - - public TaskStackHorizontalGridView getGridView() { - return mTaskStackHorizontalView; - } -} diff --git a/packages/SystemUI/src/com/android/systemui/recents/tv/views/TaskCardView.java b/packages/SystemUI/src/com/android/systemui/recents/tv/views/TaskCardView.java deleted file mode 100644 index e7575608b819..000000000000 --- a/packages/SystemUI/src/com/android/systemui/recents/tv/views/TaskCardView.java +++ /dev/null @@ -1,355 +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.recents.tv.views; - -import android.animation.Animator; -import android.content.Context; -import android.content.pm.PackageManager; -import android.content.res.Configuration; -import android.content.res.Resources; -import android.graphics.Bitmap; -import android.graphics.Outline; -import android.graphics.Point; -import android.graphics.Rect; -import android.graphics.drawable.Drawable; -import android.util.AttributeSet; -import android.util.Log; -import android.util.TypedValue; -import android.view.Display; -import android.view.KeyEvent; -import android.view.View; -import android.view.ViewOutlineProvider; -import android.view.WindowManager; -import android.widget.ImageView; -import android.widget.LinearLayout; -import android.widget.TextView; - -import com.android.systemui.R; -import com.android.systemui.recents.Recents; -import com.android.systemui.recents.misc.SystemServicesProxy; -import com.android.systemui.recents.model.Task; -import com.android.systemui.recents.tv.RecentsTvActivity; -import com.android.systemui.recents.tv.animations.DismissAnimationsHolder; -import com.android.systemui.recents.tv.animations.RecentsRowFocusAnimationHolder; -import com.android.systemui.recents.tv.animations.ViewFocusAnimator; - -public class TaskCardView extends LinearLayout { - - private static final String TAG = "TaskCardView"; - private View mThumbnailView; - private View mDismissIconView; - private View mInfoFieldView; - private TextView mTitleTextView; - private ImageView mBadgeView; - private Task mTask; - private boolean mDismissState; - private boolean mTouchExplorationEnabled; - private int mCornerRadius; - - private ViewFocusAnimator mViewFocusAnimator; - private DismissAnimationsHolder mDismissAnimationsHolder; - private RecentsRowFocusAnimationHolder mRecentsRowFocusAnimationHolder; - - public TaskCardView(Context context) { - this(context, null); - } - - public TaskCardView(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public TaskCardView(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - mDismissState = false; - Configuration config = getResources().getConfiguration(); - setLayoutDirection(config.getLayoutDirection()); - } - - @Override - protected void onFinishInflate() { - super.onFinishInflate(); - mThumbnailView = findViewById(R.id.card_view_thumbnail); - mInfoFieldView = findViewById(R.id.card_info_field); - mTitleTextView = (TextView) findViewById(R.id.card_title_text); - mBadgeView = (ImageView) findViewById(R.id.card_extra_badge); - mDismissIconView = findViewById(R.id.dismiss_icon); - mDismissAnimationsHolder = new DismissAnimationsHolder(this); - mCornerRadius = getResources().getDimensionPixelSize( - R.dimen.recents_task_view_rounded_corners_radius); - mRecentsRowFocusAnimationHolder = new RecentsRowFocusAnimationHolder(this, mInfoFieldView); - SystemServicesProxy ssp = Recents.getSystemServices(); - mTouchExplorationEnabled = ssp.isTouchExplorationEnabled(); - if (!mTouchExplorationEnabled) { - mDismissIconView.setVisibility(VISIBLE); - } else { - mDismissIconView.setVisibility(GONE); - } - mViewFocusAnimator = new ViewFocusAnimator(this); - } - - public void init(Task task) { - mTask = task; - mTitleTextView.setText(task.title); - mBadgeView.setImageDrawable(task.icon); - setThumbnailView(); - setContentDescription(task.titleDescription); - mDismissState = false; - mDismissAnimationsHolder.reset(); - mRecentsRowFocusAnimationHolder.reset(); - } - - public Task getTask() { - return mTask; - } - - @Override - public void getFocusedRect(Rect r) { - mThumbnailView.getFocusedRect(r); - } - - public Rect getFocusedThumbnailRect() { - Rect r = new Rect(); - mThumbnailView.getGlobalVisibleRect(r); - return r; - } - - public static Rect getStartingCardThumbnailRect( - Context context, boolean hasFocus, int numberOfTasks) { - if(numberOfTasks > 1) { - return getStartingCardThumbnailRectForStartPosition(context, hasFocus); - } else { - return getStartingCardThumbnailRectForFocusedPosition(context, hasFocus); - } - } - - private static Rect getStartingCardThumbnailRectForStartPosition( - Context context, boolean hasFocus) { - Resources res = context.getResources(); - - int width = res.getDimensionPixelOffset(R.dimen.recents_tv_card_width); - int totalSpacing = res.getDimensionPixelOffset(R.dimen.recents_tv_gird_card_spacing) * 2; - if (hasFocus) { - totalSpacing += res.getDimensionPixelOffset(R.dimen.recents_tv_gird_focused_card_delta); - } - int height = res.getDimensionPixelOffset(R.dimen.recents_tv_screenshot_height); - int topMargin = res.getDimensionPixelOffset(R.dimen.recents_tv_gird_row_top_margin); - int headerHeight = res.getDimensionPixelOffset(R.dimen.recents_tv_card_extra_badge_size) + - res.getDimensionPixelOffset(R.dimen.recents_tv_icon_padding_bottom); - - WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); - Display display = wm.getDefaultDisplay(); - Point size = new Point(); - display.getSize(size); - int screenWidth = size.x; - - return new Rect(screenWidth / 2 + width / 2 + totalSpacing, - topMargin + headerHeight, - screenWidth / 2 + width / 2 + totalSpacing + width, - topMargin + headerHeight + height); - } - - private static Rect getStartingCardThumbnailRectForFocusedPosition( - Context context, boolean hasFocus) { - Resources res = context.getResources(); - - TypedValue out = new TypedValue(); - res.getValue(R.integer.selected_scale, out, true); - float scale = hasFocus ? out.getFloat() : 1; - - int width = res.getDimensionPixelOffset(R.dimen.recents_tv_card_width); - int widthDelta = (int) (width * scale - width); - int height = res.getDimensionPixelOffset(R.dimen.recents_tv_screenshot_height); - int heightDelta = (int) (height * scale - height); - int topMargin = res.getDimensionPixelOffset(R.dimen.recents_tv_gird_row_top_margin); - - int headerHeight = res.getDimensionPixelOffset(R.dimen.recents_tv_card_extra_badge_size) + - res.getDimensionPixelOffset(R.dimen.recents_tv_icon_padding_bottom); - int headerHeightDelta = (int) (headerHeight * scale - headerHeight); - - int dismissAreaHeight = - res.getDimensionPixelOffset(R.dimen.recents_tv_dismiss_icon_top_margin) + - res.getDimensionPixelOffset(R.dimen.recents_tv_dismiss_icon_bottom_margin) + - res.getDimensionPixelOffset(R.dimen.recents_tv_dismiss_icon_size) + - res.getDimensionPixelOffset(R.dimen.recents_tv_dismiss_text_size); - - int dismissAreaHeightDelta = (int) (dismissAreaHeight * scale - dismissAreaHeight); - - int totalHeightDelta = heightDelta + headerHeightDelta + dismissAreaHeightDelta; - - WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); - Display display = wm.getDefaultDisplay(); - Point size = new Point(); - display.getSize(size); - int screenWidth = size.x; - - return new Rect(screenWidth / 2 - width / 2 - widthDelta / 2, - topMargin - totalHeightDelta / 2 + (int) (headerHeight * scale), - screenWidth / 2 + width / 2 + widthDelta / 2, - topMargin - totalHeightDelta / 2 + (int) (headerHeight * scale) + - (int) (height * scale)); - } - - @Override - public boolean dispatchKeyEvent(KeyEvent event) { - // Override dispatchKeyEvent() instead of onKeyDown() to prevent warning from ViewRootImpl. - switch (event.getKeyCode()) { - case KeyEvent.KEYCODE_DPAD_DOWN : { - if (!isInDismissState() && event.getAction() == KeyEvent.ACTION_DOWN) { - setDismissState(true); - return true; - } - break; - } - case KeyEvent.KEYCODE_DPAD_UP : { - if (event.getAction() == KeyEvent.ACTION_DOWN) { - if (isInDismissState()) { - setDismissState(false); - } else { - ((RecentsTvActivity) getContext()).requestPipControlsFocus(); - } - } - return true; - } - - // Eat right and left key presses when we are in dismiss state - case KeyEvent.KEYCODE_DPAD_LEFT : - case KeyEvent.KEYCODE_DPAD_RIGHT : { - if (isInDismissState()) { - return true; - } - break; - } - } - return super.dispatchKeyEvent(event); - } - - private void setDismissState(boolean dismissState) { - if (mDismissState != dismissState) { - mDismissState = dismissState; - // Check for touch exploration to ensure dismiss icon/text do not - // get animated. This should be removed based on decision from - // b/29208918 - if (!mTouchExplorationEnabled) { - if (dismissState) { - mDismissAnimationsHolder.startEnterAnimation(); - } else { - mDismissAnimationsHolder.startExitAnimation(); - } - } - } - } - - public boolean isInDismissState() { - return mDismissState; - } - - public void startDismissTaskAnimation(Animator.AnimatorListener listener) { - mDismissState = false; - mDismissAnimationsHolder.startDismissAnimation(listener); - } - - public ViewFocusAnimator getViewFocusAnimator() { - return mViewFocusAnimator; - } - - public RecentsRowFocusAnimationHolder getRecentsRowFocusAnimationHolder() { - return mRecentsRowFocusAnimationHolder; - } - - private void setThumbnailView() { - ImageView screenshotView = (ImageView) findViewById(R.id.card_view_banner_icon); - PackageManager pm = getContext().getPackageManager(); - if (mTask.thumbnail != null) { - setAsScreenShotView(mTask.thumbnail, screenshotView); - } else { - try { - Drawable banner = null; - if (mTask.key != null) { - banner = pm.getActivityBanner(mTask.key.baseIntent); - } - if (banner != null) { - setAsBannerView(banner, screenshotView); - } else { - setAsIconView(mTask.icon, screenshotView); - } - } catch (PackageManager.NameNotFoundException e) { - Log.e(TAG, "Package not found : " + e); - setAsIconView(mTask.icon, screenshotView); - } - } - } - - private void setAsScreenShotView(Bitmap screenshot, ImageView screenshotView) { - LayoutParams lp = (LayoutParams) screenshotView.getLayoutParams(); - lp.width = LayoutParams.MATCH_PARENT; - lp.height = LayoutParams.MATCH_PARENT; - - screenshotView.setLayoutParams(lp); - screenshotView.setClipToOutline(true); - screenshotView.setOutlineProvider(new ViewOutlineProvider() { - @Override - public void getOutline(View view, Outline outline) { - outline.setRoundRect(0, 0, view.getWidth(), view.getHeight(), mCornerRadius); - } - }); - screenshotView.setImageBitmap(screenshot); - } - - private void setAsBannerView(Drawable banner, ImageView bannerView) { - LayoutParams lp = (LayoutParams) bannerView.getLayoutParams(); - lp.width = getResources() - .getDimensionPixelSize(R.dimen.recents_tv_banner_width); - lp.height = getResources() - .getDimensionPixelSize(R.dimen.recents_tv_banner_height); - bannerView.setLayoutParams(lp); - bannerView.setImageDrawable(banner); - } - - private void setAsIconView(Drawable icon, ImageView iconView) { - LayoutParams lp = (LayoutParams) iconView.getLayoutParams(); - lp.width = getResources() - .getDimensionPixelSize(R.dimen.recents_tv_fallback_icon_width); - lp.height = getResources() - .getDimensionPixelSize(R.dimen.recents_tv_fallback_icon_height); - - iconView.setLayoutParams(lp); - iconView.setImageDrawable(icon); - } - - public View getThumbnailView() { - return mThumbnailView; - } - - public View getInfoFieldView() { - return mInfoFieldView; - } - - public View getDismissIconView() { - return mDismissIconView; - } - - public static int getNumberOfVisibleTasks(Context context) { - Resources res = context.getResources(); - WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); - Display display = wm.getDefaultDisplay(); - Point size = new Point(); - display.getSize(size); - int screenWidth = size.x; - int cardWidth = res.getDimensionPixelSize(R.dimen.recents_tv_card_width); - int spacing = res.getDimensionPixelSize(R.dimen.recents_tv_gird_card_spacing); - return (int) (1.0 + Math.ceil(screenWidth / (cardWidth + spacing * 2.0))); - } -} diff --git a/packages/SystemUI/src/com/android/systemui/recents/tv/views/TaskStackHorizontalGridView.java b/packages/SystemUI/src/com/android/systemui/recents/tv/views/TaskStackHorizontalGridView.java deleted file mode 100644 index f9b8700c1d46..000000000000 --- a/packages/SystemUI/src/com/android/systemui/recents/tv/views/TaskStackHorizontalGridView.java +++ /dev/null @@ -1,169 +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.recents.tv.views; - -import android.animation.Animator; -import android.animation.AnimatorSet; -import android.content.Context; -import android.support.v17.leanback.widget.HorizontalGridView; -import android.util.AttributeSet; -import android.view.View; - -import com.android.systemui.R; -import com.android.systemui.recents.RecentsActivity; -import com.android.systemui.recents.events.EventBus; -import com.android.systemui.recents.events.ui.AllTaskViewsDismissedEvent; -import com.android.systemui.recents.model.Task; -import com.android.systemui.recents.model.TaskStack; -import com.android.systemui.recents.model.TaskStack.TaskStackCallbacks; -import com.android.systemui.recents.views.AnimationProps; - -/** - * Horizontal Grid View Implementation to show the Task Stack for TV. - */ -public class TaskStackHorizontalGridView extends HorizontalGridView implements TaskStackCallbacks { - private static final int ANIMATION_DELAY_MS = 50; - private static final int MSG_START_RECENT_ROW_FOCUS_ANIMATION = 100; - private TaskStack mStack; - private Task mFocusedTask; - private AnimatorSet mRecentsRowFocusAnimation; - - public TaskStackHorizontalGridView(Context context) { - this(context, null); - } - - public TaskStackHorizontalGridView(Context context, AttributeSet attrs) { - super(context, attrs); - } - - @Override - protected void onAttachedToWindow() { - EventBus.getDefault().register(this, RecentsActivity.EVENT_BUS_PRIORITY + 1); - setWindowAlignment(WINDOW_ALIGN_NO_EDGE); - setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES); - super.onAttachedToWindow(); - } - - @Override - protected void onDetachedFromWindow() { - super.onDetachedFromWindow(); - EventBus.getDefault().unregister(this); - } - - /** - * Initializes the grid view. - * @param stack - */ - public void init(TaskStack stack) { - // Set new stack - mStack = stack; - if (mStack != null) { - mStack.setCallbacks(this); - } - } - - /** - * @return Returns the task stack. - */ - public TaskStack getStack() { - return mStack; - } - - /** - * @return - The focused task. - */ - public Task getFocusedTask() { - if (findFocus() != null) { - mFocusedTask = ((TaskCardView)findFocus()).getTask(); - } - return mFocusedTask; - } - - /** - * @param task - * @return Child view for given task - */ - public TaskCardView getChildViewForTask(Task task) { - for (int i = 0; i < getChildCount(); i++) { - TaskCardView tv = (TaskCardView) getChildAt(i); - if (tv.getTask() == task) { - return tv; - } - } - return null; - } - - - /** - * Starts the Recents row's focus gain animation. - */ - public void startFocusGainAnimation() { - for (int i = 0; i < getChildCount(); i++) { - TaskCardView v = (TaskCardView) getChildAt(i); - if (v.hasFocus()) { - v.getViewFocusAnimator().changeSize(true); - } - v.getRecentsRowFocusAnimationHolder().startFocusGainAnimation(); - } - } - - /** - * Starts the Recents row's focus loss animation. - */ - public void startFocusLossAnimation() { - for (int i = 0; i < getChildCount(); i++) { - TaskCardView v = (TaskCardView) getChildAt(i); - if (v.hasFocus()) { - v.getViewFocusAnimator().changeSize(false); - } - v.getRecentsRowFocusAnimationHolder().startFocusLossAnimation(); - } - } - - @Override - public void onStackTaskAdded(TaskStack stack, Task newTask) { - ((TaskStackHorizontalViewAdapter) getAdapter()).addTaskAt(newTask, - stack.indexOfStackTask(newTask)); - } - - @Override - public void onStackTaskRemoved(TaskStack stack, Task removedTask, Task newFrontMostTask, - AnimationProps animation, boolean fromDockGesture) { - ((TaskStackHorizontalViewAdapter) getAdapter()).removeTask(removedTask); - if (mFocusedTask == removedTask) { - mFocusedTask = null; - } - // If there are no remaining tasks, then just close recents - if (mStack.getStackTaskCount() == 0) { - boolean shouldFinishActivity = (mStack.getStackTaskCount() == 0); - if (shouldFinishActivity) { - EventBus.getDefault().send(new AllTaskViewsDismissedEvent(fromDockGesture - ? R.string.recents_empty_message - : R.string.recents_empty_message_dismissed_all)); - } - } - } - - @Override - public void onStackTasksRemoved(TaskStack stack) { - // Do nothing - } - - @Override - public void onStackTasksUpdated(TaskStack stack) { - // Do nothing - } -} diff --git a/packages/SystemUI/src/com/android/systemui/recents/tv/views/TaskStackHorizontalViewAdapter.java b/packages/SystemUI/src/com/android/systemui/recents/tv/views/TaskStackHorizontalViewAdapter.java deleted file mode 100644 index 236d0778fadd..000000000000 --- a/packages/SystemUI/src/com/android/systemui/recents/tv/views/TaskStackHorizontalViewAdapter.java +++ /dev/null @@ -1,159 +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.recents.tv.views; - -import android.animation.Animator; -import android.support.v7.widget.RecyclerView; -import android.util.Log; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; - -import com.android.systemui.R; -import com.android.systemui.recents.Recents; -import com.android.systemui.recents.events.EventBus; -import com.android.systemui.recents.events.activity.LaunchTvTaskEvent; -import com.android.systemui.recents.events.ui.DeleteTaskDataEvent; -import com.android.systemui.recents.model.RecentsTaskLoader; -import com.android.systemui.recents.model.Task; -import com.android.systemui.recents.views.AnimationProps; - -import java.util.ArrayList; -import java.util.List; - -import static android.app.ActivityManager.StackId.INVALID_STACK_ID; - -public class TaskStackHorizontalViewAdapter extends - RecyclerView.Adapter<TaskStackHorizontalViewAdapter.ViewHolder> { - - //Full class name is 30 characters - private static final String TAG = "TaskStackViewAdapter"; - private List<Task> mTaskList; - private TaskStackHorizontalGridView mGridView; - - public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener{ - private TaskCardView mTaskCardView; - private Task mTask; - public ViewHolder(View v) { - super(v); - mTaskCardView = (TaskCardView) v; - } - - public void init(Task task) { - mTaskCardView.init(task); - mTask = task; - mTaskCardView.setOnClickListener(this); - } - - @Override - public void onClick(View v) { - try { - if (mTaskCardView.isInDismissState()) { - mTaskCardView.startDismissTaskAnimation( - getRemoveAtListener(getAdapterPosition(), mTaskCardView.getTask())); - } else { - EventBus.getDefault().send(new LaunchTvTaskEvent(mTaskCardView, mTask, - null, INVALID_STACK_ID)); - } - } catch (Exception e) { - Log.e(TAG, v.getContext() - .getString(R.string.recents_launch_error_message, mTask.title), e); - } - - } - - private Animator.AnimatorListener getRemoveAtListener(final int position, - final Task task) { - return new Animator.AnimatorListener() { - - @Override - public void onAnimationStart(Animator animation) { } - - @Override - public void onAnimationEnd(Animator animation) { - removeTask(task); - EventBus.getDefault().send(new DeleteTaskDataEvent(task)); - } - - @Override - public void onAnimationCancel(Animator animation) { } - - @Override - public void onAnimationRepeat(Animator animation) { } - }; - - } - } - - public TaskStackHorizontalViewAdapter(List tasks) { - mTaskList = new ArrayList<Task>(tasks); - } - - public void setNewStackTasks(List tasks) { - mTaskList.clear(); - mTaskList.addAll(tasks); - notifyDataSetChanged(); - } - - @Override - public TaskStackHorizontalViewAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, - int viewType) { - LayoutInflater inflater = LayoutInflater.from(parent.getContext()); - ViewHolder viewHolder = new ViewHolder( - inflater.inflate(R.layout.recents_tv_task_card_view, parent, false)); - return viewHolder; - } - - @Override - public void onBindViewHolder(ViewHolder holder, int position) { - Task task = mTaskList.get(position); - // Retrives from caches, loading only if necessary - Recents.getTaskLoader().loadTaskData(task); - holder.init(task); - } - - @Override - public int getItemCount() { - return mTaskList.size(); - } - - public void removeTask(Task task) { - int position = mTaskList.indexOf(task); - if (position >= 0) { - mTaskList.remove(position); - notifyItemRemoved(position); - if (mGridView != null) { - mGridView.getStack().removeTask(task, AnimationProps.IMMEDIATE, - false); - } - } - } - - public int getPositionOfTask(Task task) { - int position = mTaskList.indexOf(task); - return (position >= 0) ? position : 0; - } - - - public void setTaskStackHorizontalGridView(TaskStackHorizontalGridView gridView) { - mGridView = gridView; - } - - public void addTaskAt(Task task, int position) { - mTaskList.add(position, task); - notifyItemInserted(position); - } -} diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java index 6a66fca79f76..8882cab1b68b 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java @@ -16,6 +16,7 @@ package com.android.systemui.recents.views; +import static android.app.ActivityManager.StackId.ASSISTANT_STACK_ID; import static android.app.ActivityManager.StackId.DOCKED_STACK_ID; import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID; import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID; @@ -303,7 +304,7 @@ public class RecentsTransitionHelper { // TODO: Sometimes targetStackId is not initialized after reboot, so we also have to // check for INVALID_STACK_ID if (targetStackId == FULLSCREEN_WORKSPACE_STACK_ID || targetStackId == DOCKED_STACK_ID - || targetStackId == INVALID_STACK_ID) { + || targetStackId == ASSISTANT_STACK_ID || targetStackId == INVALID_STACK_ID) { if (taskView == null) { specs.add(composeOffscreenAnimationSpec(task, offscreenTaskRect)); } else { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java index dceeb7415c20..092408961eab 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java @@ -29,7 +29,6 @@ import android.os.BatteryManager; import android.os.BatteryStats; import android.os.Handler; import android.os.Message; -import android.os.PowerManager; import android.os.RemoteException; import android.os.ServiceManager; import android.os.UserHandle; @@ -95,12 +94,20 @@ public class KeyguardIndicationController { private final DevicePolicyManager mDevicePolicyManager; private boolean mDozing; + /** + * Creates a new KeyguardIndicationController and registers callbacks. + */ public KeyguardIndicationController(Context context, ViewGroup indicationArea, LockIcon lockIcon) { this(context, indicationArea, lockIcon, WakeLock.createPartial(context, "Doze:KeyguardIndication")); + + registerCallbacks(KeyguardUpdateMonitor.getInstance(context)); } + /** + * Creates a new KeyguardIndicationController for testing. Does *not* register callbacks. + */ @VisibleForTesting KeyguardIndicationController(Context context, ViewGroup indicationArea, LockIcon lockIcon, WakeLock wakeLock) { @@ -124,12 +131,15 @@ public class KeyguardIndicationController { mDevicePolicyManager = (DevicePolicyManager) context.getSystemService( Context.DEVICE_POLICY_SERVICE); - KeyguardUpdateMonitor.getInstance(context).registerCallback(getKeyguardCallback()); - context.registerReceiverAsUser(mTickReceiver, UserHandle.SYSTEM, + updateDisclosure(); + } + + private void registerCallbacks(KeyguardUpdateMonitor monitor) { + monitor.registerCallback(getKeyguardCallback()); + + mContext.registerReceiverAsUser(mTickReceiver, UserHandle.SYSTEM, new IntentFilter(Intent.ACTION_TIME_TICK), null, Dependency.get(Dependency.TIME_TICK_HANDLER)); - - updateDisclosure(); } /** @@ -331,7 +341,7 @@ public class KeyguardIndicationController { mStatusBarKeyguardViewManager = statusBarKeyguardViewManager; } - BroadcastReceiver mTickReceiver = new BroadcastReceiver() { + private final BroadcastReceiver mTickReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { mHandler.post(() -> { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java index 6cd3eae366e8..aa0fcbd28650 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java @@ -45,6 +45,7 @@ import com.android.systemui.statusbar.policy.NetworkController; public class CollapsedStatusBarFragment extends Fragment implements CommandQueue.Callbacks { public static final String TAG = "CollapsedStatusBarFragment"; + private static final String EXTRA_PANEL_STATE = "panel_state"; private PhoneStatusBarView mStatusBar; private KeyguardMonitor mKeyguardMonitor; private NetworkController mNetworkController; @@ -73,14 +74,23 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); mStatusBar = (PhoneStatusBarView) view; - mDarkIconManager = new DarkIconManager((LinearLayout) view.findViewById(R.id.statusIcons)); + if (savedInstanceState != null && savedInstanceState.containsKey(EXTRA_PANEL_STATE)) { + mStatusBar.go(savedInstanceState.getInt(EXTRA_PANEL_STATE)); + } + mDarkIconManager = new DarkIconManager(view.findViewById(R.id.statusIcons)); Dependency.get(StatusBarIconController.class).addIconGroup(mDarkIconManager); - mSystemIconArea = (LinearLayout) mStatusBar.findViewById(R.id.system_icon_area); + mSystemIconArea = mStatusBar.findViewById(R.id.system_icon_area); mSignalClusterView = reinflateSignalCluster(mStatusBar); Dependency.get(DarkIconDispatcher.class).addDarkReceiver(mSignalClusterView); } @Override + public void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + outState.putInt(EXTRA_PANEL_STATE, mStatusBar.getState()); + } + + @Override public void onResume() { super.onResume(); SysUiServiceProvider.getComponent(getContext(), CommandQueue.class).addCallbacks(this); @@ -101,8 +111,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue public void initNotificationIconArea(NotificationIconAreaController notificationIconAreaController) { - ViewGroup notificationIconArea = (ViewGroup) mStatusBar - .findViewById(R.id.notification_icon_area); + ViewGroup notificationIconArea = mStatusBar.findViewById(R.id.notification_icon_area); mNotificationIconAreaInner = notificationIconAreaController.getNotificationInnerAreaView(); if (mNotificationIconAreaInner.getParent() != null) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java index 99f8aafafdf5..2b52b48a6819 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java @@ -35,6 +35,7 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.res.Configuration; +import android.database.ContentObserver; import android.graphics.PixelFormat; import android.graphics.Rect; import android.inputmethodservice.InputMethodService; @@ -46,6 +47,7 @@ import android.os.Message; import android.os.PowerManager; import android.os.RemoteException; import android.os.UserHandle; +import android.provider.Settings; import android.support.annotation.VisibleForTesting; import android.telecom.TelecomManager; import android.text.TextUtils; @@ -104,6 +106,7 @@ public class NavigationBarFragment extends Fragment implements Callbacks { private int mNavigationIconHints = 0; private int mNavigationBarMode; private AccessibilityManager mAccessibilityManager; + private MagnificationContentObserver mMagnificationObserver; private int mDisabledFlags1; private StatusBar mStatusBar; @@ -135,6 +138,12 @@ public class NavigationBarFragment extends Fragment implements Callbacks { mAccessibilityManager = getContext().getSystemService(AccessibilityManager.class); mAccessibilityManager.addAccessibilityServicesStateChangeListener( this::updateAccessibilityServicesState); + mMagnificationObserver = new MagnificationContentObserver( + getContext().getMainThreadHandler()); + getContext().getContentResolver().registerContentObserver(Settings.Secure.getUriFor( + Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED), false, + mMagnificationObserver); + if (savedInstanceState != null) { mDisabledFlags1 = savedInstanceState.getInt(EXTRA_DISABLE_STATE, 0); } @@ -142,7 +151,7 @@ public class NavigationBarFragment extends Fragment implements Callbacks { try { WindowManagerGlobal.getWindowManagerService() - .watchRotation(mRotationWatcher); + .watchRotation(mRotationWatcher, getContext().getDisplay().getDisplayId()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -154,6 +163,7 @@ public class NavigationBarFragment extends Fragment implements Callbacks { mCommandQueue.removeCallbacks(this); mAccessibilityManager.removeAccessibilityServicesStateChangeListener( this::updateAccessibilityServicesState); + getContext().getContentResolver().unregisterContentObserver(mMagnificationObserver); try { WindowManagerGlobal.getWindowManagerService() .removeRotationWatcher(mRotationWatcher); @@ -387,6 +397,7 @@ public class NavigationBarFragment extends Fragment implements Callbacks { ButtonDispatcher accessibilityButton = mNavigationBarView.getAccessibilityButton(); accessibilityButton.setOnClickListener(this::onAccessibilityClick); accessibilityButton.setOnLongClickListener(this::onAccessibilityLongClick); + updateAccessibilityServicesState(); } private boolean onHomeTouch(View v, MotionEvent event) { @@ -550,10 +561,18 @@ public class NavigationBarFragment extends Fragment implements Callbacks { } private void updateAccessibilityServicesState() { + int requestingServices = 0; + try { + if (Settings.Secure.getInt(getContext().getContentResolver(), + Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED) == 1) { + requestingServices++; + } + } catch (Settings.SettingNotFoundException e) { + } + final List<AccessibilityServiceInfo> services = mAccessibilityManager.getEnabledAccessibilityServiceList( AccessibilityServiceInfo.FEEDBACK_ALL_MASK); - int requestingServices = 0; for (int i = services.size() - 1; i >= 0; --i) { AccessibilityServiceInfo info = services.get(i); if ((info.flags & AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON) != 0) { @@ -600,6 +619,18 @@ public class NavigationBarFragment extends Fragment implements Callbacks { mNavigationBarView.getBarTransitions().finishAnimations(); } + private class MagnificationContentObserver extends ContentObserver { + + public MagnificationContentObserver(Handler handler) { + super(handler); + } + + @Override + public void onChange(boolean selfChange) { + NavigationBarFragment.this.updateAccessibilityServicesState(); + } + } + private final Stub mRotationWatcher = new Stub() { @Override public void onRotationChanged(int rotation) throws RemoteException { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java index 23d3816b3ffe..cefe97200ca9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java @@ -46,6 +46,10 @@ public abstract class PanelBar extends FrameLayout { mState = state; } + public int getState() { + return mState; + } + public PanelBar(Context context, AttributeSet attrs) { super(context, attrs); } diff --git a/packages/SystemUI/src/com/android/systemui/tuner/BatteryPreference.java b/packages/SystemUI/src/com/android/systemui/tuner/BatteryPreference.java index 1df12acde4a5..1a51dabe209a 100644 --- a/packages/SystemUI/src/com/android/systemui/tuner/BatteryPreference.java +++ b/packages/SystemUI/src/com/android/systemui/tuner/BatteryPreference.java @@ -25,7 +25,7 @@ import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.systemui.Dependency; import com.android.systemui.statusbar.phone.StatusBarIconController; -import static com.android.systemui.BatteryMeterView.SHOW_PERCENT_SETTING; +import static android.provider.Settings.System.SHOW_BATTERY_PERCENT; public class BatteryPreference extends DropDownPreference implements TunerService.Tunable { @@ -49,7 +49,7 @@ public class BatteryPreference extends DropDownPreference implements TunerServic public void onAttached() { super.onAttached(); mHasPercentage = Settings.System.getInt(getContext().getContentResolver(), - SHOW_PERCENT_SETTING, 0) != 0; + SHOW_BATTERY_PERCENT, 0) != 0; Dependency.get(TunerService.class).addTunable(this, StatusBarIconController.ICON_BLACKLIST); } @@ -84,7 +84,7 @@ public class BatteryPreference extends DropDownPreference implements TunerServic protected boolean persistString(String value) { final boolean v = PERCENT.equals(value); MetricsLogger.action(getContext(), MetricsEvent.TUNER_BATTERY_PERCENTAGE, v); - Settings.System.putInt(getContext().getContentResolver(), SHOW_PERCENT_SETTING, v ? 1 : 0); + Settings.System.putInt(getContext().getContentResolver(), SHOW_BATTERY_PERCENT, v ? 1 : 0); if (DISABLED.equals(value)) { mBlacklist.add(mBattery); } else { diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java index 6a92b2f91c5c..369ce6939cdf 100644 --- a/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java +++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java @@ -1,228 +1,96 @@ /* - * Copyright (C) 2015 The Android Open Source Project + * Copyright (C) 2017 The Android Open Source Project * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the specific language governing + * permissions and limitations under the License. */ + package com.android.systemui.tuner; import android.app.ActivityManager; import android.content.BroadcastReceiver; import android.content.ComponentName; -import android.content.ContentResolver; import android.content.Context; import android.content.DialogInterface; import android.content.DialogInterface.OnClickListener; import android.content.Intent; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; -import android.content.pm.UserInfo; -import android.database.ContentObserver; -import android.net.Uri; -import android.os.Handler; -import android.os.Looper; import android.os.UserHandle; -import android.os.UserManager; import android.provider.Settings; -import android.provider.Settings.Secure; -import android.text.TextUtils; -import android.util.ArrayMap; -import android.util.ArraySet; -import static com.android.systemui.BatteryMeterView.SHOW_PERCENT_SETTING; +import static android.provider.Settings.System.SHOW_BATTERY_PERCENT; import com.android.systemui.DemoMode; import com.android.systemui.Dependency; import com.android.systemui.R; -import com.android.systemui.SysUiServiceProvider; -import com.android.systemui.SystemUI; -import com.android.systemui.SystemUIApplication; -import com.android.systemui.settings.CurrentUserTracker; -import com.android.systemui.statusbar.phone.StatusBarIconController; import com.android.systemui.statusbar.phone.SystemUIDialog; -import com.android.systemui.util.leak.LeakDetector; - -import java.util.HashMap; -import java.util.HashSet; -import java.util.Set; - -public class TunerService { +public abstract class TunerService { public static final String ACTION_CLEAR = "com.android.systemui.action.CLEAR_TUNER"; - private static final String TUNER_VERSION = "sysui_tuner_version"; - - private static final int CURRENT_TUNER_VERSION = 1; - - private final Observer mObserver = new Observer(); - // Map of Uris we listen on to their settings keys. - private final ArrayMap<Uri, String> mListeningUris = new ArrayMap<>(); - // Map of settings keys to the listener. - private final HashMap<String, Set<Tunable>> mTunableLookup = new HashMap<>(); - // Set of all tunables, used for leak detection. - private final HashSet<Tunable> mTunables = LeakDetector.ENABLED ? new HashSet<>() : null; - private final Context mContext; - - private ContentResolver mContentResolver; - private int mCurrentUser; - private CurrentUserTracker mUserTracker; - - public TunerService(Context context) { - mContext = context; - mContentResolver = mContext.getContentResolver(); - - for (UserInfo user : UserManager.get(mContext).getUsers()) { - mCurrentUser = user.getUserHandle().getIdentifier(); - if (getValue(TUNER_VERSION, 0) != CURRENT_TUNER_VERSION) { - upgradeTuner(getValue(TUNER_VERSION, 0), CURRENT_TUNER_VERSION); - } - } - - mCurrentUser = ActivityManager.getCurrentUser(); - mUserTracker = new CurrentUserTracker(mContext) { - @Override - public void onUserSwitched(int newUserId) { - mCurrentUser = newUserId; - reloadAll(); - reregisterAll(); - } - }; - mUserTracker.startTracking(); - } - - public void destroy() { - mUserTracker.stopTracking(); - } - - private void upgradeTuner(int oldVersion, int newVersion) { - if (oldVersion < 1) { - String blacklistStr = getValue(StatusBarIconController.ICON_BLACKLIST); - if (blacklistStr != null) { - ArraySet<String> iconBlacklist = - StatusBarIconController.getIconBlacklist(blacklistStr); + public abstract void clearAll(); + public abstract void destroy(); - iconBlacklist.add("rotate"); - iconBlacklist.add("headset"); - - Settings.Secure.putStringForUser(mContentResolver, - StatusBarIconController.ICON_BLACKLIST, - TextUtils.join(",", iconBlacklist), mCurrentUser); - } - } - setValue(TUNER_VERSION, newVersion); - } + public abstract String getValue(String setting); + public abstract int getValue(String setting, int def); + public abstract String getValue(String setting, String def); - public String getValue(String setting) { - return Settings.Secure.getStringForUser(mContentResolver, setting, mCurrentUser); - } + public abstract void setValue(String setting, String value); + public abstract void setValue(String setting, int value); - public void setValue(String setting, String value) { - Settings.Secure.putStringForUser(mContentResolver, setting, value, mCurrentUser); - } + public abstract void addTunable(Tunable tunable, String... keys); + public abstract void removeTunable(Tunable tunable); - public int getValue(String setting, int def) { - return Settings.Secure.getIntForUser(mContentResolver, setting, def, mCurrentUser); - } - - public String getValue(String setting, String def) { - String ret = Secure.getStringForUser(mContentResolver, setting, mCurrentUser); - if (ret == null) return def; - return ret; - } - - public void setValue(String setting, int value) { - Settings.Secure.putIntForUser(mContentResolver, setting, value, mCurrentUser); - } - - public void addTunable(Tunable tunable, String... keys) { - for (String key : keys) { - addTunable(tunable, key); - } + public interface Tunable { + void onTuningChanged(String key, String newValue); } - private void addTunable(Tunable tunable, String key) { - if (!mTunableLookup.containsKey(key)) { - mTunableLookup.put(key, new ArraySet<Tunable>()); - } - mTunableLookup.get(key).add(tunable); - if (LeakDetector.ENABLED) { - mTunables.add(tunable); - Dependency.get(LeakDetector.class).trackCollection(mTunables, "TunerService.mTunables"); - } - Uri uri = Settings.Secure.getUriFor(key); - if (!mListeningUris.containsKey(uri)) { - mListeningUris.put(uri, key); - mContentResolver.registerContentObserver(uri, false, mObserver, mCurrentUser); + private static Context userContext(Context context) { + try { + return context.createPackageContextAsUser(context.getPackageName(), 0, + new UserHandle(ActivityManager.getCurrentUser())); + } catch (NameNotFoundException e) { + return context; } - // Send the first state. - String value = Settings.Secure.getStringForUser(mContentResolver, key, mCurrentUser); - tunable.onTuningChanged(key, value); } - public void removeTunable(Tunable tunable) { - for (Set<Tunable> list : mTunableLookup.values()) { - list.remove(tunable); - } - if (LeakDetector.ENABLED) { - mTunables.remove(tunable); - } - } + public static final void setTunerEnabled(Context context, boolean enabled) { + userContext(context).getPackageManager().setComponentEnabledSetting( + new ComponentName(context, TunerActivity.class), + enabled ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED + : PackageManager.COMPONENT_ENABLED_STATE_DISABLED, + PackageManager.DONT_KILL_APP); - protected void reregisterAll() { - if (mListeningUris.size() == 0) { - return; - } - mContentResolver.unregisterContentObserver(mObserver); - for (Uri uri : mListeningUris.keySet()) { - mContentResolver.registerContentObserver(uri, false, mObserver, mCurrentUser); - } + userContext(context).getPackageManager().setComponentEnabledSetting( + new ComponentName(context, TunerActivity.ACTIVITY_ALIAS_NAME), + enabled ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED + : PackageManager.COMPONENT_ENABLED_STATE_DISABLED, + PackageManager.DONT_KILL_APP); } - public void reloadSetting(Uri uri) { - String key = mListeningUris.get(uri); - Set<Tunable> tunables = mTunableLookup.get(key); - if (tunables == null) { - return; - } - String value = Settings.Secure.getStringForUser(mContentResolver, key, mCurrentUser); - for (Tunable tunable : tunables) { - tunable.onTuningChanged(key, value); - } + public static final boolean isTunerEnabled(Context context) { + return userContext(context).getPackageManager().getComponentEnabledSetting( + new ComponentName(context, TunerActivity.class)) + == PackageManager.COMPONENT_ENABLED_STATE_ENABLED; } - private void reloadAll() { - for (String key : mTunableLookup.keySet()) { - String value = Settings.Secure.getStringForUser(mContentResolver, key, - mCurrentUser); - for (Tunable tunable : mTunableLookup.get(key)) { - tunable.onTuningChanged(key, value); + public static class ClearReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + if (ACTION_CLEAR.equals(intent.getAction())) { + Dependency.get(TunerService.class).clearAll(); } } } - public void clearAll() { - // A couple special cases. - Settings.Global.putString(mContentResolver, DemoMode.DEMO_MODE_ALLOWED, null); - Settings.System.putString(mContentResolver, - SHOW_PERCENT_SETTING, null); - Intent intent = new Intent(DemoMode.ACTION_DEMO); - intent.putExtra(DemoMode.EXTRA_COMMAND, DemoMode.COMMAND_EXIT); - mContext.sendBroadcast(intent); - - for (String key : mTunableLookup.keySet()) { - Settings.Secure.putString(mContentResolver, key, null); - } - } - public static final void showResetRequest(final Context context, final Runnable onDisabled) { SystemUIDialog dialog = new SystemUIDialog(context); dialog.setShowForAllUsers(true); @@ -247,59 +115,4 @@ public class TunerService { }); dialog.show(); } - - public static final void setTunerEnabled(Context context, boolean enabled) { - userContext(context).getPackageManager().setComponentEnabledSetting( - new ComponentName(context, TunerActivity.class), - enabled ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED - : PackageManager.COMPONENT_ENABLED_STATE_DISABLED, - PackageManager.DONT_KILL_APP); - - userContext(context).getPackageManager().setComponentEnabledSetting( - new ComponentName(context, TunerActivity.ACTIVITY_ALIAS_NAME), - enabled ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED - : PackageManager.COMPONENT_ENABLED_STATE_DISABLED, - PackageManager.DONT_KILL_APP); - } - - public static final boolean isTunerEnabled(Context context) { - return userContext(context).getPackageManager().getComponentEnabledSetting( - new ComponentName(context, TunerActivity.class)) - == PackageManager.COMPONENT_ENABLED_STATE_ENABLED; - } - - private static Context userContext(Context context) { - try { - return context.createPackageContextAsUser(context.getPackageName(), 0, - new UserHandle(ActivityManager.getCurrentUser())); - } catch (NameNotFoundException e) { - return context; - } - } - - private class Observer extends ContentObserver { - public Observer() { - super(new Handler(Looper.getMainLooper())); - } - - @Override - public void onChange(boolean selfChange, Uri uri, int userId) { - if (userId == ActivityManager.getCurrentUser()) { - reloadSetting(uri); - } - } - } - - public interface Tunable { - void onTuningChanged(String key, String newValue); - } - - public static class ClearReceiver extends BroadcastReceiver { - @Override - public void onReceive(Context context, Intent intent) { - if (ACTION_CLEAR.equals(intent.getAction())) { - Dependency.get(TunerService.class).clearAll(); - } - } - } } diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java new file mode 100644 index 000000000000..8e584bc362eb --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java @@ -0,0 +1,242 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ +package com.android.systemui.tuner; + +import android.app.ActivityManager; +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.content.ContentResolver; +import android.content.Context; +import android.content.DialogInterface; +import android.content.DialogInterface.OnClickListener; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.content.pm.UserInfo; +import android.database.ContentObserver; +import android.net.Uri; +import android.os.Handler; +import android.os.Looper; +import android.os.UserHandle; +import android.os.UserManager; +import android.provider.Settings; +import android.provider.Settings.Secure; +import android.text.TextUtils; +import android.util.ArrayMap; +import android.util.ArraySet; + +import com.android.systemui.DemoMode; +import com.android.systemui.Dependency; +import com.android.systemui.R; +import com.android.systemui.SysUiServiceProvider; +import com.android.systemui.SystemUI; +import com.android.systemui.SystemUIApplication; +import com.android.systemui.settings.CurrentUserTracker; +import com.android.systemui.statusbar.phone.StatusBarIconController; +import com.android.systemui.statusbar.phone.SystemUIDialog; +import com.android.systemui.util.leak.LeakDetector; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Set; + + +public class TunerServiceImpl extends TunerService { + + private static final String TUNER_VERSION = "sysui_tuner_version"; + + private static final int CURRENT_TUNER_VERSION = 1; + + private final Observer mObserver = new Observer(); + // Map of Uris we listen on to their settings keys. + private final ArrayMap<Uri, String> mListeningUris = new ArrayMap<>(); + // Map of settings keys to the listener. + private final HashMap<String, Set<Tunable>> mTunableLookup = new HashMap<>(); + // Set of all tunables, used for leak detection. + private final HashSet<Tunable> mTunables = LeakDetector.ENABLED ? new HashSet<>() : null; + private final Context mContext; + + private ContentResolver mContentResolver; + private int mCurrentUser; + private CurrentUserTracker mUserTracker; + + public TunerServiceImpl(Context context) { + mContext = context; + mContentResolver = mContext.getContentResolver(); + + for (UserInfo user : UserManager.get(mContext).getUsers()) { + mCurrentUser = user.getUserHandle().getIdentifier(); + if (getValue(TUNER_VERSION, 0) != CURRENT_TUNER_VERSION) { + upgradeTuner(getValue(TUNER_VERSION, 0), CURRENT_TUNER_VERSION); + } + } + + mCurrentUser = ActivityManager.getCurrentUser(); + mUserTracker = new CurrentUserTracker(mContext) { + @Override + public void onUserSwitched(int newUserId) { + mCurrentUser = newUserId; + reloadAll(); + reregisterAll(); + } + }; + mUserTracker.startTracking(); + } + + @Override + public void destroy() { + mUserTracker.stopTracking(); + } + + private void upgradeTuner(int oldVersion, int newVersion) { + if (oldVersion < 1) { + String blacklistStr = getValue(StatusBarIconController.ICON_BLACKLIST); + if (blacklistStr != null) { + ArraySet<String> iconBlacklist = + StatusBarIconController.getIconBlacklist(blacklistStr); + + iconBlacklist.add("rotate"); + iconBlacklist.add("headset"); + + Settings.Secure.putStringForUser(mContentResolver, + StatusBarIconController.ICON_BLACKLIST, + TextUtils.join(",", iconBlacklist), mCurrentUser); + } + } + setValue(TUNER_VERSION, newVersion); + } + + @Override + public String getValue(String setting) { + return Settings.Secure.getStringForUser(mContentResolver, setting, mCurrentUser); + } + + @Override + public void setValue(String setting, String value) { + Settings.Secure.putStringForUser(mContentResolver, setting, value, mCurrentUser); + } + + @Override + public int getValue(String setting, int def) { + return Settings.Secure.getIntForUser(mContentResolver, setting, def, mCurrentUser); + } + + @Override + public String getValue(String setting, String def) { + String ret = Secure.getStringForUser(mContentResolver, setting, mCurrentUser); + if (ret == null) return def; + return ret; + } + + @Override + public void setValue(String setting, int value) { + Settings.Secure.putIntForUser(mContentResolver, setting, value, mCurrentUser); + } + + @Override + public void addTunable(Tunable tunable, String... keys) { + for (String key : keys) { + addTunable(tunable, key); + } + } + + private void addTunable(Tunable tunable, String key) { + if (!mTunableLookup.containsKey(key)) { + mTunableLookup.put(key, new ArraySet<Tunable>()); + } + mTunableLookup.get(key).add(tunable); + if (LeakDetector.ENABLED) { + mTunables.add(tunable); + Dependency.get(LeakDetector.class).trackCollection(mTunables, "TunerService.mTunables"); + } + Uri uri = Settings.Secure.getUriFor(key); + if (!mListeningUris.containsKey(uri)) { + mListeningUris.put(uri, key); + mContentResolver.registerContentObserver(uri, false, mObserver, mCurrentUser); + } + // Send the first state. + String value = Settings.Secure.getStringForUser(mContentResolver, key, mCurrentUser); + tunable.onTuningChanged(key, value); + } + + @Override + public void removeTunable(Tunable tunable) { + for (Set<Tunable> list : mTunableLookup.values()) { + list.remove(tunable); + } + if (LeakDetector.ENABLED) { + mTunables.remove(tunable); + } + } + + protected void reregisterAll() { + if (mListeningUris.size() == 0) { + return; + } + mContentResolver.unregisterContentObserver(mObserver); + for (Uri uri : mListeningUris.keySet()) { + mContentResolver.registerContentObserver(uri, false, mObserver, mCurrentUser); + } + } + + private void reloadSetting(Uri uri) { + String key = mListeningUris.get(uri); + Set<Tunable> tunables = mTunableLookup.get(key); + if (tunables == null) { + return; + } + String value = Settings.Secure.getStringForUser(mContentResolver, key, mCurrentUser); + for (Tunable tunable : tunables) { + tunable.onTuningChanged(key, value); + } + } + + private void reloadAll() { + for (String key : mTunableLookup.keySet()) { + String value = Settings.Secure.getStringForUser(mContentResolver, key, + mCurrentUser); + for (Tunable tunable : mTunableLookup.get(key)) { + tunable.onTuningChanged(key, value); + } + } + } + + @Override + public void clearAll() { + // A couple special cases. + Settings.Global.putString(mContentResolver, DemoMode.DEMO_MODE_ALLOWED, null); + Intent intent = new Intent(DemoMode.ACTION_DEMO); + intent.putExtra(DemoMode.EXTRA_COMMAND, DemoMode.COMMAND_EXIT); + mContext.sendBroadcast(intent); + + for (String key : mTunableLookup.keySet()) { + Settings.Secure.putString(mContentResolver, key, null); + } + } + + private class Observer extends ContentObserver { + public Observer() { + super(new Handler(Looper.getMainLooper())); + } + + @Override + public void onChange(boolean selfChange, Uri uri, int userId) { + if (userId == ActivityManager.getCurrentUser()) { + reloadSetting(uri); + } + } + } +} diff --git a/packages/SystemUI/tests/Android.mk b/packages/SystemUI/tests/Android.mk index 760d875143c0..576299fe0057 100644 --- a/packages/SystemUI/tests/Android.mk +++ b/packages/SystemUI/tests/Android.mk @@ -49,7 +49,8 @@ LOCAL_STATIC_JAVA_LIBRARIES := \ 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 27955ecbca72..c297ae840bb9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/DependencyTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/DependencyTest.java @@ -39,21 +39,21 @@ public class DependencyTest extends SysuiTestCase { @Test public void testClassDependency() { FlashlightController f = mock(FlashlightController.class); - injectTestDependency(FlashlightController.class, f); + mDependency.injectTestDependency(FlashlightController.class, f); Assert.assertEquals(f, Dependency.get(FlashlightController.class)); } @Test public void testStringDependency() { Looper l = Looper.getMainLooper(); - injectTestDependency(Dependency.BG_LOOPER, l); + mDependency.injectTestDependency(Dependency.BG_LOOPER, l); assertEquals(l, Dependency.get(Dependency.BG_LOOPER)); } @Test public void testDump() { Dumpable d = mock(Dumpable.class); - injectTestDependency(DUMPABLE, d); + mDependency.injectTestDependency(DUMPABLE, d); Dependency.get(DUMPABLE); mDependency.dump(null, mock(PrintWriter.class), null); verify(d).dump(eq(null), any(), eq(null)); @@ -62,7 +62,7 @@ public class DependencyTest extends SysuiTestCase { @Test public void testConfigurationChanged() { ConfigurationChangedReceiver d = mock(ConfigurationChangedReceiver.class); - injectTestDependency(CONFIGURATION_CHANGED_RECEIVER, d); + mDependency.injectTestDependency(CONFIGURATION_CHANGED_RECEIVER, d); Dependency.get(CONFIGURATION_CHANGED_RECEIVER); mDependency.onConfigurationChanged(null); verify(d).onConfigurationChanged(eq(null)); diff --git a/packages/SystemUI/tests/src/com/android/systemui/SysuiBaseFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/SysuiBaseFragmentTest.java new file mode 100644 index 000000000000..15cebc70e8bc --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/SysuiBaseFragmentTest.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package com.android.systemui; + +import android.app.Fragment; +import android.support.test.InstrumentationRegistry; +import android.testing.BaseFragmentTest; + +import com.android.systemui.utils.leaks.LeakCheckedTest; +import com.android.systemui.utils.leaks.LeakCheckedTest.SysuiLeakCheck; + +import org.junit.Before; +import org.junit.Rule; + +public abstract class SysuiBaseFragmentTest extends BaseFragmentTest { + + public static final Class<?>[] ALL_SUPPORTED_CLASSES = LeakCheckedTest.ALL_SUPPORTED_CLASSES; + + @Rule + public final SysuiLeakCheck mLeakCheck = new SysuiLeakCheck(); + + protected final TestableDependency mDependency = new TestableDependency(mContext); + protected SysuiTestableContext mSysuiContext; + + public SysuiBaseFragmentTest(Class<? extends Fragment> cls) { + super(cls); + } + + @Before + public void SysuiSetup() { + System.setProperty("dexmaker.share_classloader", "true"); + SystemUIFactory.createFromConfig(mContext); + // TODO: Figure out another way to give reference to a SysuiTestableContext. + mSysuiContext = (SysuiTestableContext) mContext; + } + + @Override + protected SysuiTestableContext getContext() { + return new SysuiTestableContext(InstrumentationRegistry.getContext(), mLeakCheck); + } + + public void injectLeakCheckedDependencies(Class<?>... cls) { + for (Class<?> c : cls) { + injectLeakCheckedDependency(c); + } + } + + public <T> void injectLeakCheckedDependency(Class<T> c) { + mDependency.injectTestDependency(c, mLeakCheck.getLeakChecker(c)); + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java b/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java index c0e7e8061407..aadae0f22c17 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java +++ b/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java @@ -15,49 +15,38 @@ */ package com.android.systemui; -import static org.mockito.Mockito.mock; - import android.content.Context; import android.os.Handler; import android.os.Looper; import android.os.MessageQueue; import android.support.test.InstrumentationRegistry; -import android.util.ArrayMap; - -import com.android.systemui.Dependency.DependencyKey; -import com.android.systemui.utils.TestableContext; -import com.android.systemui.utils.leaks.Tracker; +import android.testing.LeakCheck; -import org.junit.After; import org.junit.Before; +import org.junit.Rule; /** * Base class that does System UI specific setup. */ public abstract class SysuiTestCase { - private Throwable mException; private Handler mHandler; - protected TestableContext mContext; - protected TestDependency mDependency; + @Rule + public SysuiTestableContext mContext = new SysuiTestableContext( + InstrumentationRegistry.getContext(), getLeakCheck()); + public TestableDependency mDependency = new TestableDependency(mContext); @Before public void SysuiSetup() throws Exception { - mException = null; System.setProperty("dexmaker.share_classloader", "true"); - mContext = new TestableContext(InstrumentationRegistry.getTargetContext(), this); SystemUIFactory.createFromConfig(mContext); - mDependency = new TestDependency(); - mDependency.mContext = mContext; - mDependency.start(); } - @After - public void cleanup() throws Exception { - mContext.getSettingsProvider().clearOverrides(this); + protected LeakCheck getLeakCheck() { + return null; } - protected Context getContext() { + public Context getContext() { return mContext; } @@ -84,48 +73,11 @@ public abstract class SysuiTestCase { } } - // Used for leak tracking, returns null to indicate no leak tracking by default. - public Tracker getTracker(String tag) { - return null; - } - - public <T> T injectMockDependency(Class<T> cls) { - final T mock = mock(cls); - mDependency.injectTestDependency(cls, mock); - return mock; - } - - public <T> void injectTestDependency(Class<T> cls, T obj) { - mDependency.injectTestDependency(cls, obj); - } - - public <T> void injectTestDependency(DependencyKey<T> key, T obj) { - mDependency.injectTestDependency(key, obj); - } - public static final class EmptyRunnable implements Runnable { public void run() { } } - public static class TestDependency extends Dependency { - private final ArrayMap<Object, Object> mObjs = new ArrayMap<>(); - - private <T> void injectTestDependency(DependencyKey<T> key, T obj) { - mObjs.put(key, obj); - } - - private <T> void injectTestDependency(Class<T> key, T obj) { - mObjs.put(key, obj); - } - - @Override - protected <T> T createDependency(Object key) { - if (mObjs.containsKey(key)) return (T) mObjs.get(key); - return super.createDependency(key); - } - } - public static final class Idler implements MessageQueue.IdleHandler { private final Runnable mCallback; private boolean mIdle; diff --git a/packages/SystemUI/tests/src/com/android/systemui/SysuiTestableContext.java b/packages/SystemUI/tests/src/com/android/systemui/SysuiTestableContext.java new file mode 100644 index 000000000000..b94a2fbb8f81 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/SysuiTestableContext.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package com.android.systemui; + +import android.content.Context; +import android.testing.LeakCheck; +import android.testing.TestableContext; +import android.util.ArrayMap; + +public class SysuiTestableContext extends TestableContext implements SysUiServiceProvider { + + private ArrayMap<Class<?>, Object> mComponents; + + public SysuiTestableContext(Context base) { + super(base); + } + + public SysuiTestableContext(Context base, LeakCheck check) { + super(base, check); + } + + @SuppressWarnings("unchecked") + public <T> T getComponent(Class<T> interfaceType) { + return (T) (mComponents != null ? mComponents.get(interfaceType) : null); + } + + public <T, C extends T> void putComponent(Class<T> interfaceType, C component) { + if (mComponents == null) mComponents = new ArrayMap<>(); + mComponents.put(interfaceType, component); + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/TestableDependency.java b/packages/SystemUI/tests/src/com/android/systemui/TestableDependency.java new file mode 100644 index 000000000000..53a799497fd8 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/TestableDependency.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package com.android.systemui; + +import static org.mockito.Mockito.mock; + +import android.content.Context; +import android.util.ArrayMap; + +public class TestableDependency extends Dependency { + private final ArrayMap<Object, Object> mObjs = new ArrayMap<>(); + + public TestableDependency(Context context) { + mContext = context; + if (SystemUIFactory.getInstance() == null) { + SystemUIFactory.createFromConfig(context); + } + start(); + } + + public <T> T injectMockDependency(Class<T> cls) { + final T mock = mock(cls); + injectTestDependency(cls, mock); + return mock; + } + + public <T> void injectTestDependency(DependencyKey<T> key, T obj) { + mObjs.put(key, obj); + } + + public <T> void injectTestDependency(Class<T> key, T obj) { + mObjs.put(key, obj); + } + + @Override + protected <T> T createDependency(Object key) { + if (mObjs.containsKey(key)) return (T) mObjs.get(key); + return super.createDependency(key); + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeConfigurationTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeConfigurationTest.java index 5477afa86ffe..048936b3f5f0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeConfigurationTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeConfigurationTest.java @@ -47,7 +47,7 @@ public class DozeConfigurationTest extends SysuiTestCase { return; } - mContext.getSettingsProvider().acquireOverridesBuilder(this) + mContext.getSettingsProvider().acquireOverridesBuilder() .addSetting("secure", Settings.Secure.DOZE_ALWAYS_ON, null) .build(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java index ba396718fc78..cdbde5e8b0e1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java @@ -39,19 +39,20 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.support.test.filters.SmallTest; +import android.testing.AndroidTestingRunner; import android.view.Display; -import com.android.systemui.SysUIRunner; -import com.android.systemui.UiThreadTest; import com.android.internal.hardware.AmbientDisplayConfiguration; import com.android.systemui.util.wakelock.WakeLockFake; +import android.testing.UiThreadTest; + import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @SmallTest -@RunWith(SysUIRunner.class) +@RunWith(AndroidTestingRunner.class) @UiThreadTest public class DozeMachineTest { diff --git a/packages/SystemUI/tests/src/com/android/systemui/notification/PropertyAnimatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/notification/PropertyAnimatorTest.java index 53053fa6e2b2..8484beda5fc7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/notification/PropertyAnimatorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/notification/PropertyAnimatorTest.java @@ -14,22 +14,25 @@ package com.android.systemui.notification; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; + import android.animation.AnimatorListenerAdapter; import android.animation.ValueAnimator; -import android.support.test.runner.AndroidJUnit4; import android.test.suitebuilder.annotation.SmallTest; - -import com.android.systemui.Interpolators; -import com.android.systemui.R; - +import android.testing.AndroidTestingRunner; +import android.testing.UiThreadTest; import android.util.FloatProperty; import android.util.Property; import android.view.View; import android.view.animation.Interpolator; -import com.android.systemui.SysUIRunner; +import com.android.systemui.Interpolators; +import com.android.systemui.R; import com.android.systemui.SysuiTestCase; -import com.android.systemui.UiThreadTest; import com.android.systemui.statusbar.notification.PropertyAnimator; import com.android.systemui.statusbar.stack.AnimationFilter; import com.android.systemui.statusbar.stack.AnimationProperties; @@ -39,18 +42,8 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; -import static org.mockito.Matchers.anyObject; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - @SmallTest -@RunWith(SysUIRunner.class) +@RunWith(AndroidTestingRunner.class) @UiThreadTest public class PropertyAnimatorTest extends SysuiTestCase { diff --git a/packages/SystemUI/tests/src/com/android/systemui/plugins/PluginInstanceManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/plugins/PluginInstanceManagerTest.java index 658966cdde17..4f0815d0f283 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/plugins/PluginInstanceManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/plugins/PluginInstanceManagerTest.java @@ -72,7 +72,7 @@ public class PluginInstanceManagerTest extends SysuiTestCase { private PackageManager mMockPm; private PluginListener mMockListener; private PluginInstanceManager mPluginInstanceManager; - private PluginManager mMockManager; + private PluginManagerImpl mMockManager; private VersionInfo mMockVersionInfo; @Before @@ -82,7 +82,7 @@ public class PluginInstanceManagerTest extends SysuiTestCase { mContextWrapper = new MyContextWrapper(getContext()); mMockPm = mock(PackageManager.class); mMockListener = mock(PluginListener.class); - mMockManager = mock(PluginManager.class); + mMockManager = mock(PluginManagerImpl.class); when(mMockManager.getClassLoader(any(), any())) .thenReturn(getClass().getClassLoader()); mMockVersionInfo = mock(VersionInfo.class); diff --git a/packages/SystemUI/tests/src/com/android/systemui/plugins/PluginManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/plugins/PluginManagerTest.java index 053e5cf2d4c0..a3d5d5fc3871 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/plugins/PluginManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/plugins/PluginManagerTest.java @@ -34,7 +34,7 @@ import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; import com.android.systemui.SysuiTestCase; import com.android.systemui.plugins.annotations.ProvidesInterface; import com.android.systemui.plugins.PluginInstanceManager.PluginInfo; -import com.android.systemui.plugins.PluginManager.PluginInstanceManagerFactory; +import com.android.systemui.plugins.PluginManagerImpl.PluginInstanceManagerFactory; import org.junit.Before; import org.junit.Test; @@ -50,7 +50,7 @@ public class PluginManagerTest extends SysuiTestCase { private PluginInstanceManagerFactory mMockFactory; private PluginInstanceManager mMockPluginInstance; - private PluginManager mPluginManager; + private PluginManagerImpl mPluginManager; private PluginListener mMockListener; private UncaughtExceptionHandler mRealExceptionHandler; @@ -66,7 +66,8 @@ public class PluginManagerTest extends SysuiTestCase { when(mMockFactory.createPluginInstanceManager(Mockito.any(), Mockito.any(), Mockito.any(), Mockito.anyBoolean(), Mockito.any(), Mockito.any(), Mockito.any())) .thenReturn(mMockPluginInstance); - mPluginManager = new PluginManager(getContext(), mMockFactory, true, mMockExceptionHandler); + mPluginManager = new PluginManagerImpl(getContext(), mMockFactory, true, + mMockExceptionHandler); resetExceptionHandler(); mMockListener = mock(PluginListener.class); } @@ -98,7 +99,7 @@ public class PluginManagerTest extends SysuiTestCase { @Test public void testNonDebuggable() { - mPluginManager = new PluginManager(getContext(), mMockFactory, false, + mPluginManager = new PluginManagerImpl(getContext(), mMockFactory, false, mMockExceptionHandler); resetExceptionHandler(); @@ -148,7 +149,7 @@ public class PluginManagerTest extends SysuiTestCase { ComponentName testComponent = new ComponentName(getContext().getPackageName(), PluginManagerTest.class.getName()); - Intent intent = new Intent(PluginManager.DISABLE_PLUGIN); + Intent intent = new Intent(PluginManagerImpl.DISABLE_PLUGIN); intent.setData(Uri.parse("package://" + testComponent.flattenToString())); mPluginManager.onReceive(mContext, intent); verify(nm).cancel(eq(testComponent.getClassName()), eq(SystemMessage.NOTE_PLUGIN)); diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java index 7153340920d1..deb31dae6840 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java @@ -21,14 +21,16 @@ import android.os.Looper; import com.android.keyguard.CarrierText; import com.android.systemui.Dependency; -import com.android.systemui.FragmentTestCase; import com.android.systemui.R; -import com.android.systemui.SysUIRunner; + +import android.testing.AndroidTestingRunner; + +import com.android.systemui.SysuiBaseFragmentTest; import com.android.systemui.statusbar.phone.StatusBarIconController; import com.android.systemui.statusbar.policy.UserSwitcherController; -import com.android.systemui.util.LayoutInflaterBuilder; -import com.android.systemui.utils.TestableLooper; -import com.android.systemui.utils.TestableLooper.RunWithLooper; +import android.testing.LayoutInflaterBuilder; +import android.testing.TestableLooper; +import android.testing.TestableLooper.RunWithLooper; import org.junit.Before; import org.junit.Test; @@ -38,9 +40,9 @@ import android.content.Context; import android.view.View; import android.widget.FrameLayout; -@RunWith(SysUIRunner.class) +@RunWith(AndroidTestingRunner.class) @RunWithLooper(setAsMainLooper = true) -public class QSFragmentTest extends FragmentTestCase { +public class QSFragmentTest extends SysuiBaseFragmentTest { public QSFragmentTest() { super(QSFragment.class); @@ -56,8 +58,9 @@ public class QSFragmentTest extends FragmentTestCase { .replace(CarrierText.class, View.class) .build()); - injectTestDependency(Dependency.BG_LOOPER, TestableLooper.get(this).getLooper()); - injectMockDependency(UserSwitcherController.class); + mDependency.injectTestDependency(Dependency.BG_LOOPER, + TestableLooper.get(this).getLooper()); + mDependency.injectMockDependency(UserSwitcherController.class); injectLeakCheckedDependencies(ALL_SUPPORTED_CLASSES); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java index e38c30f5b020..1ff373c95c14 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java @@ -33,8 +33,8 @@ import com.android.systemui.Dependency; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.statusbar.policy.SecurityController; -import com.android.systemui.util.LayoutInflaterBuilder; -import com.android.systemui.utils.TestableImageView; +import android.testing.LayoutInflaterBuilder; +import android.testing.TestableImageView; import org.junit.Before; import org.junit.Test; @@ -57,8 +57,8 @@ public class QSSecurityFooterTest extends SysuiTestCase { @Before public void setUp() { - injectTestDependency(SecurityController.class, mSecurityController); - injectTestDependency(Dependency.BG_LOOPER, Looper.getMainLooper()); + mDependency.injectTestDependency(SecurityController.class, mSecurityController); + mDependency.injectTestDependency(Dependency.BG_LOOPER, Looper.getMainLooper()); mContext.addMockSystemService(Context.LAYOUT_INFLATER_SERVICE, new LayoutInflaterBuilder(mContext) .replace("ImageView", TestableImageView.class) diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java index d1e17f4b5499..5ae107a6a431 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java @@ -21,13 +21,13 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import com.android.systemui.Dependency; -import com.android.systemui.SysUIRunner; +import android.testing.AndroidTestingRunner; import com.android.systemui.SysuiTestCase; import com.android.systemui.plugins.qs.QSTile; import com.android.systemui.plugins.qs.QSTile.State; import com.android.systemui.qs.QSTileHost; -import com.android.systemui.utils.TestableLooper; -import com.android.systemui.utils.TestableLooper.RunWithLooper; +import android.testing.TestableLooper; +import android.testing.TestableLooper.RunWithLooper; import org.junit.Before; import org.junit.Test; @@ -37,7 +37,7 @@ import android.os.Message; import android.test.suitebuilder.annotation.SmallTest; @SmallTest -@RunWith(SysUIRunner.class) +@RunWith(AndroidTestingRunner.class) @RunWithLooper public class TileQueryHelperTest extends SysuiTestCase { private TestableLooper mBGLooper; @@ -46,7 +46,7 @@ public class TileQueryHelperTest extends SysuiTestCase { @Before public void setup() { mBGLooper = TestableLooper.get(this); - injectTestDependency(Dependency.BG_LOOPER, mBGLooper.getLooper()); + mDependency.injectTestDependency(Dependency.BG_LOOPER, mBGLooper.getLooper()); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java index 6d7b50fc1df9..ddd661558be5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java @@ -25,12 +25,12 @@ import android.os.Looper; import android.service.quicksettings.Tile; import android.test.suitebuilder.annotation.SmallTest; -import com.android.systemui.SysUIRunner; +import android.testing.AndroidTestingRunner; import com.android.systemui.SysuiTestCase; import com.android.systemui.qs.QSTileHost; import com.android.systemui.statusbar.phone.StatusBarIconController; -import com.android.systemui.utils.TestableLooper; -import com.android.systemui.utils.TestableLooper.RunWithLooper; +import android.testing.TestableLooper; +import android.testing.TestableLooper.RunWithLooper; import org.junit.After; import org.junit.Before; @@ -42,7 +42,7 @@ import org.mockito.Mockito; import java.util.ArrayList; @SmallTest -@RunWith(SysUIRunner.class) +@RunWith(AndroidTestingRunner.class) @RunWithLooper(setAsMainLooper = true) public class TileServicesTest extends SysuiTestCase { private static int NUM_FAKES = TileServices.DEFAULT_MAX_BOUND * 2; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java index 6424a0a4c9e1..bdd05c7e6384 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java @@ -16,8 +16,6 @@ package com.android.systemui.statusbar; -import static android.support.test.internal.runner.junit4.statement.UiThreadStatement.runOnUiThread; - import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.mock; @@ -34,12 +32,11 @@ import android.hardware.fingerprint.FingerprintManager; import android.os.Looper; import android.support.test.InstrumentationRegistry; import android.support.test.filters.SmallTest; -import android.support.test.internal.runner.junit4.statement.UiThreadStatement; import android.support.test.runner.AndroidJUnit4; import android.view.View; import android.view.ViewGroup; -import com.android.keyguard.KeyguardUpdateMonitor; +import com.android.keyguard.KeyguardUpdateMonitorCallback; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.statusbar.phone.KeyguardIndicationTextView; @@ -124,7 +121,7 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { when(mDevicePolicyManager.isDeviceManaged()).thenReturn(false); createController(); - final KeyguardUpdateMonitor monitor = KeyguardUpdateMonitor.getInstance(mContext); + final KeyguardUpdateMonitorCallback monitor = mController.getKeyguardCallback(); reset(mDisclosure); when(mDevicePolicyManager.isDeviceManaged()).thenReturn(true); @@ -186,12 +183,10 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { }); mInstrumentation.waitForIdleSync(); - boolean[] held = new boolean[2]; + Boolean[] held = new Boolean[1]; mInstrumentation.runOnMainSync(() -> { held[0] = mWakeLock.isHeld(); - held[1] = true; }); - assertFalse("wake lock still held", held[0]); - assertTrue("held was not written yet", held[1]); + assertFalse("WakeLock expected: RELEASED, was: HELD", held[0]); } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInfoTest.java index 8520bdb114aa..5b9270d4e4ed 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInfoTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInfoTest.java @@ -33,41 +33,35 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.app.INotificationManager; -import android.app.Notification; import android.app.NotificationChannel; import android.app.NotificationChannelGroup; import android.app.NotificationManager; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; -import android.content.pm.ParceledListSlice; import android.graphics.drawable.Drawable; import android.service.notification.StatusBarNotification; -import android.support.test.InstrumentationRegistry; -import android.support.test.runner.AndroidJUnit4; import android.test.suitebuilder.annotation.SmallTest; -import android.util.Log; +import android.testing.AndroidTestingRunner; +import android.testing.UiThreadTest; import android.view.LayoutInflater; import android.view.View; import android.widget.ImageView; import android.widget.Switch; import android.widget.TextView; -import com.android.internal.util.CharSequences; + import com.android.systemui.R; -import com.android.systemui.SysUIRunner; import com.android.systemui.SysuiTestCase; -import com.android.systemui.UiThreadTest; import org.junit.Before; -import org.junit.runner.RunWith; import org.junit.Test; -import org.mockito.Mockito; -import java.util.Arrays; +import org.junit.runner.RunWith; + import java.util.Collections; import java.util.concurrent.CountDownLatch; @SmallTest -@RunWith(SysUIRunner.class) +@RunWith(AndroidTestingRunner.class) @UiThreadTest public class NotificationInfoTest extends SysuiTestCase { private static final String TEST_PACKAGE_NAME = "test_package"; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationMenuRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationMenuRowTest.java index b8be4fa57c0f..c2c633639e81 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationMenuRowTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationMenuRowTest.java @@ -14,17 +14,17 @@ package com.android.systemui.statusbar; -import com.android.systemui.SysUIRunner; -import com.android.systemui.utils.TestableLooper; -import com.android.systemui.utils.TestableLooper.RunWithLooper; -import com.android.systemui.utils.ViewUtils; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; +import android.testing.TestableLooper.RunWithLooper; +import android.testing.ViewUtils; import com.android.systemui.utils.leaks.LeakCheckedTest; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -@RunWith(SysUIRunner.class) +@RunWith(AndroidTestingRunner.class) @RunWithLooper(setAsMainLooper = true) public class NotificationMenuRowTest extends LeakCheckedTest { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java index 3ccb1606876a..e41a827098c6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java @@ -22,7 +22,7 @@ import static org.mockito.Mockito.verify; import com.android.internal.app.NightDisplayController; import com.android.systemui.Prefs; import com.android.systemui.Prefs.Key; -import com.android.systemui.SysUIRunner; +import android.testing.AndroidTestingRunner; import com.android.systemui.SysuiTestCase; import com.android.systemui.qs.QSTileHost; @@ -32,7 +32,7 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mockito; -@RunWith(SysUIRunner.class) +@RunWith(AndroidTestingRunner.class) public class AutoTileManagerTest extends SysuiTestCase { private QSTileHost mQsTileHost; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragmentTest.java index f55115e005b2..a9acda305663 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragmentTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragmentTest.java @@ -23,24 +23,22 @@ import android.app.StatusBarManager; import android.view.View; import android.view.ViewPropertyAnimator; -import com.android.systemui.FragmentTestCase; import com.android.systemui.R; -import com.android.systemui.SysUIRunner; +import android.testing.AndroidTestingRunner; + +import com.android.systemui.SysuiBaseFragmentTest; import com.android.systemui.statusbar.CommandQueue; -import com.android.systemui.statusbar.policy.KeyguardMonitor; -import com.android.systemui.statusbar.policy.NetworkController; -import com.android.systemui.statusbar.policy.SecurityController; import com.android.systemui.tuner.TunerService; -import com.android.systemui.utils.TestableLooper.RunWithLooper; +import android.testing.TestableLooper.RunWithLooper; import org.junit.Before; import org.junit.runner.RunWith; import org.junit.Test; import org.mockito.Mockito; -@RunWith(SysUIRunner.class) +@RunWith(AndroidTestingRunner.class) @RunWithLooper(setAsMainLooper = true) -public class CollapsedStatusBarFragmentTest extends FragmentTestCase { +public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { private NotificationIconAreaController mMockNotificiationAreaController; private View mNotificationAreaInner; @@ -51,9 +49,9 @@ public class CollapsedStatusBarFragmentTest extends FragmentTestCase { @Before public void setup() { - mContext.putComponent(CommandQueue.class, mock(CommandQueue.class)); - mContext.putComponent(StatusBar.class, mock(StatusBar.class)); - mContext.putComponent(TunerService.class, mock(TunerService.class)); + mSysuiContext.putComponent(CommandQueue.class, mock(CommandQueue.class)); + mSysuiContext.putComponent(StatusBar.class, mock(StatusBar.class)); + mSysuiContext.putComponent(TunerService.class, mock(TunerService.class)); injectLeakCheckedDependencies(ALL_SUPPORTED_CLASSES); mMockNotificiationAreaController = mock(NotificationIconAreaController.class); mNotificationAreaInner = mock(View.class); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarFragmentTest.java index 1fa98461d02d..e7cdcb541ee4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarFragmentTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarFragmentTest.java @@ -19,24 +19,25 @@ import static org.mockito.Mockito.when; import android.content.Context; import android.os.Looper; +import android.testing.AndroidTestingRunner; import android.view.Display; import android.view.WindowManager; import com.android.systemui.Dependency; -import com.android.systemui.FragmentTestCase; -import com.android.systemui.SysUIRunner; + +import com.android.systemui.SysuiBaseFragmentTest; import com.android.systemui.recents.Recents; import com.android.systemui.stackdivider.Divider; import com.android.systemui.statusbar.CommandQueue; -import com.android.systemui.utils.TestableLooper.RunWithLooper; +import android.testing.TestableLooper.RunWithLooper; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -@RunWith(SysUIRunner.class) +@RunWith(AndroidTestingRunner.class) @RunWithLooper(setAsMainLooper = true) -public class NavigationBarFragmentTest extends FragmentTestCase { +public class NavigationBarFragmentTest extends SysuiBaseFragmentTest { public NavigationBarFragmentTest() { super(NavigationBarFragment.class); @@ -44,11 +45,11 @@ public class NavigationBarFragmentTest extends FragmentTestCase { @Before public void setup() { - injectTestDependency(Dependency.BG_LOOPER, Looper.getMainLooper()); - mContext.putComponent(CommandQueue.class, mock(CommandQueue.class)); - mContext.putComponent(StatusBar.class, mock(StatusBar.class)); - mContext.putComponent(Recents.class, mock(Recents.class)); - mContext.putComponent(Divider.class, mock(Divider.class)); + mDependency.injectTestDependency(Dependency.BG_LOOPER, Looper.getMainLooper()); + mSysuiContext.putComponent(CommandQueue.class, mock(CommandQueue.class)); + mSysuiContext.putComponent(StatusBar.class, mock(StatusBar.class)); + mSysuiContext.putComponent(Recents.class, mock(Recents.class)); + mSysuiContext.putComponent(Divider.class, mock(Divider.class)); injectLeakCheckedDependencies(ALL_SUPPORTED_CLASSES); WindowManager windowManager = mock(WindowManager.class); Display defaultDisplay = mContext.getSystemService(WindowManager.class).getDefaultDisplay(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ExtensionControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ExtensionControllerTest.java index e3a5ef04bcc8..3e79a0404026 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ExtensionControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ExtensionControllerTest.java @@ -42,8 +42,8 @@ public class ExtensionControllerTest extends SysuiTestCase { @Before public void setup() { - mPluginManager = injectMockDependency(PluginManager.class); - mTunerService = injectMockDependency(TunerService.class); + mPluginManager = mDependency.injectMockDependency(PluginManager.class); + mTunerService = mDependency.injectMockDependency(TunerService.class); mExtensionController = Dependency.get(ExtensionController.class); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java index 8cbf95b548eb..efa232bb33af 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java @@ -18,7 +18,7 @@ import android.test.suitebuilder.annotation.SmallTest; import com.android.settingslib.Utils; import com.android.systemui.statusbar.policy.NetworkController.IconState; -import com.android.systemui.utils.FakeSettingsProvider.SettingOverrider; +import android.testing.TestableSettings.SettingOverrider; import org.junit.Test; import org.junit.runner.RunWith; @@ -92,9 +92,9 @@ public class NetworkControllerWifiTest extends NetworkControllerBaseTest { attr); // Must set the Settings value before instantiating the NetworkControllerImpl due to bugs in - // FakeSettingsProvider. + // TestableSettings. SettingOverrider settingsOverrider = - mContext.getSettingsProvider().acquireOverridesBuilder(this) + mContext.getSettingsProvider().acquireOverridesBuilder() .addSetting("global", Settings.Global.NETWORK_SCORING_UI_ENABLED, "1") .build(); super.setUp(); // re-instantiate NetworkControllImpl now that setting has been updated diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/FakeSettingsProvider.java b/packages/SystemUI/tests/src/com/android/systemui/utils/FakeSettingsProvider.java deleted file mode 100644 index f40fe4cb2450..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/utils/FakeSettingsProvider.java +++ /dev/null @@ -1,298 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file - * except in compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the - * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ - -package com.android.systemui.utils; - -import android.content.ContentProviderClient; -import android.content.ContentResolver; -import android.os.Bundle; -import android.os.RemoteException; -import android.provider.Settings; -import android.support.annotation.VisibleForTesting; -import android.test.mock.MockContentProvider; -import android.util.ArrayMap; -import android.util.ArraySet; -import android.util.Log; - -import com.android.systemui.SysuiTestCase; -import com.android.systemui.utils.FakeSettingsProvider.SettingOverrider.Builder; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.Set; - -/** - * Allows calls to android.provider.Settings to be tested easier. A SettingOverride - * can be acquired and a set of specific settings can be set to a value (and not changed - * in the system when set), so that they can be tested without breaking the test device. - * <p> - * To use, in the before method acquire the override add all settings that will affect if - * your test passes or not. - * - * <pre class="prettyprint"> - * {@literal - * mSettingOverride = mTestableContext.getSettingsProvider().acquireOverridesBuilder() - * .addSetting("secure", Secure.USER_SETUP_COMPLETE, "0") - * .build(); - * } - * </pre> - * - * Then in the after free up the settings. - * - * <pre class="prettyprint"> - * {@literal - * mSettingOverride.release(); - * } - * </pre> - */ -public class FakeSettingsProvider extends MockContentProvider { - - private static final String TAG = "FakeSettingsProvider"; - private static final boolean DEBUG = false; - - // Number of times to try to acquire a setting if in use. - private static final int MAX_TRIES = 10; - // Time to wait for each setting. WAIT_TIMEOUT * MAX_TRIES will be the maximum wait time - // for a setting. - private static final long WAIT_TIMEOUT = 1000; - - private final Map<String, SettingOverrider> mOverrideMap = new ArrayMap<>(); - private final Map<SysuiTestCase, List<SettingOverrider>> mOwners = new ArrayMap<>(); - - private static FakeSettingsProvider sInstance; - private final ContentProviderClient mSettings; - private final ContentResolver mResolver; - - private FakeSettingsProvider(ContentProviderClient settings, ContentResolver resolver) { - mSettings = settings; - mResolver = resolver; - } - - public Builder acquireOverridesBuilder(SysuiTestCase test) { - return new Builder(this, test); - } - - public void clearOverrides(SysuiTestCase test) { - List<SettingOverrider> overrides = mOwners.remove(test); - if (overrides != null) { - overrides.forEach(override -> override.ensureReleased()); - } - } - - public Bundle call(String method, String arg, Bundle extras) { - // Methods are "GET_system", "GET_global", "PUT_secure", etc. - final String[] commands = method.split("_", 2); - final String op = commands[0]; - final String table = commands[1]; - - synchronized (mOverrideMap) { - SettingOverrider overrider = mOverrideMap.get(key(table, arg)); - if (overrider == null) { - // Fall through to real settings. - try { - if (DEBUG) Log.d(TAG, "Falling through to real settings " + method); - // TODO: Add our own version of caching to handle this. - Bundle call = mSettings.call(method, arg, extras); - call.remove(Settings.CALL_METHOD_TRACK_GENERATION_KEY); - return call; - } catch (RemoteException e) { - throw new RuntimeException(e); - } - } - String value; - Bundle out = new Bundle(); - switch (op) { - case "GET": - value = overrider.get(table, arg); - if (value != null) { - out.putString(Settings.NameValueTable.VALUE, value); - } - break; - case "PUT": - value = extras.getString(Settings.NameValueTable.VALUE, null); - if (value != null) { - overrider.put(table, arg, value); - } else { - overrider.remove(table, arg); - } - break; - default: - throw new UnsupportedOperationException("Unknown command " + method); - } - return out; - } - } - - private void acquireSettings(SettingOverrider overridder, Set<String> keys, - SysuiTestCase owner) throws AcquireTimeoutException { - synchronized (mOwners) { - List<SettingOverrider> list = mOwners.get(owner); - if (list == null) { - list = new ArrayList<>(); - mOwners.put(owner, list); - } - list.add(overridder); - } - synchronized (mOverrideMap) { - for (int i = 0; i < MAX_TRIES; i++) { - if (checkKeys(keys, false)) break; - try { - if (DEBUG) Log.d(TAG, "Waiting for contention to finish"); - mOverrideMap.wait(WAIT_TIMEOUT); - } catch (InterruptedException e) { - } - } - checkKeys(keys, true); - for (String key : keys) { - if (DEBUG) Log.d(TAG, "Acquiring " + key); - mOverrideMap.put(key, overridder); - } - } - } - - private void releaseSettings(Set<String> keys) { - synchronized (mOverrideMap) { - for (String key : keys) { - if (DEBUG) Log.d(TAG, "Releasing " + key); - mOverrideMap.remove(key); - } - if (DEBUG) Log.d(TAG, "Notifying"); - mOverrideMap.notify(); - } - } - - @VisibleForTesting - public Object getLock() { - return mOverrideMap; - } - - private boolean checkKeys(Set<String> keys, boolean shouldThrow) - throws AcquireTimeoutException { - for (String key : keys) { - if (mOverrideMap.containsKey(key)) { - if (shouldThrow) { - throw new AcquireTimeoutException("Could not acquire " + key); - } - return false; - } - } - return true; - } - - public static class SettingOverrider { - private final Set<String> mValidKeys; - private final Map<String, String> mValueMap = new ArrayMap<>(); - private final FakeSettingsProvider mProvider; - private boolean mReleased; - - private SettingOverrider(Set<String> keys, FakeSettingsProvider provider) { - mValidKeys = new ArraySet<>(keys); - mProvider = provider; - } - - private void ensureReleased() { - if (!mReleased) { - release(); - } - } - - public void release() { - mProvider.releaseSettings(mValidKeys); - mReleased = true; - } - - private void putDirect(String key, String value) { - mValueMap.put(key, value); - } - - public void put(String table, String key, String value) { - if (!mValidKeys.contains(key(table, key))) { - throw new IllegalArgumentException("Key " + table + " " + key - + " not acquired for this overrider"); - } - mValueMap.put(key(table, key), value); - } - - public void remove(String table, String key) { - if (!mValidKeys.contains(key(table, key))) { - throw new IllegalArgumentException("Key " + table + " " + key - + " not acquired for this overrider"); - } - mValueMap.remove(key(table, key)); - } - - public String get(String table, String key) { - if (!mValidKeys.contains(key(table, key))) { - throw new IllegalArgumentException("Key " + table + " " + key - + " not acquired for this overrider"); - } - Log.d(TAG, "Get " + table + " " + key + " " + mValueMap.get(key(table, key))); - return mValueMap.get(key(table, key)); - } - - public static class Builder { - private final FakeSettingsProvider mProvider; - private final SysuiTestCase mOwner; - private Set<String> mKeys = new ArraySet<>(); - private Map<String, String> mValues = new ArrayMap<>(); - - private Builder(FakeSettingsProvider provider, SysuiTestCase test) { - mProvider = provider; - mOwner = test; - } - - public Builder addSetting(String table, String key) { - mKeys.add(key(table, key)); - return this; - } - - public Builder addSetting(String table, String key, String value) { - addSetting(table, key); - mValues.put(key(table, key), value); - return this; - } - - public SettingOverrider build() throws AcquireTimeoutException { - SettingOverrider overrider = new SettingOverrider(mKeys, mProvider); - mProvider.acquireSettings(overrider, mKeys, mOwner); - mValues.forEach((key, value) -> overrider.putDirect(key, value)); - return overrider; - } - } - } - - public static class AcquireTimeoutException extends Exception { - public AcquireTimeoutException(String str) { - super(str); - } - } - - private static String key(String table, String key) { - return table + "_" + key; - } - - /** - * Since the settings provider is cached inside android.provider.Settings, this must - * be gotten statically to ensure there is only one instance referenced. - * @param settings - */ - public static FakeSettingsProvider getFakeSettingsProvider(ContentProviderClient settings, - ContentResolver resolver) { - if (sInstance == null) { - sInstance = new FakeSettingsProvider(settings, resolver); - } - return sInstance; - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/BaseLeakChecker.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/BaseLeakChecker.java index b118fdcb64ac..d94ecc09f263 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/BaseLeakChecker.java +++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/BaseLeakChecker.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016 The Android Open Source Project + * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of the License at @@ -14,6 +14,9 @@ package com.android.systemui.utils.leaks; +import android.testing.LeakCheck; +import android.testing.LeakCheck.Tracker; + import com.android.systemui.Dumpable; import com.android.systemui.statusbar.policy.CallbackController; @@ -24,7 +27,7 @@ public class BaseLeakChecker<T> implements CallbackController<T>, Dumpable { private final Tracker mTracker; - public BaseLeakChecker(LeakCheckedTest test, String tag) { + public BaseLeakChecker(LeakCheck test, String tag) { mTracker = test.getTracker(tag); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeBatteryController.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeBatteryController.java index fa07d337cd3d..a843cca498a0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeBatteryController.java +++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeBatteryController.java @@ -15,6 +15,7 @@ package com.android.systemui.utils.leaks; import android.os.Bundle; +import android.testing.LeakCheck; import com.android.systemui.statusbar.policy.BatteryController; import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback; @@ -24,7 +25,7 @@ import java.io.PrintWriter; public class FakeBatteryController extends BaseLeakChecker<BatteryStateChangeCallback> implements BatteryController { - public FakeBatteryController(LeakCheckedTest test) { + public FakeBatteryController(LeakCheck test) { super(test, "battery"); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeBluetoothController.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeBluetoothController.java index 6074a011fd20..0ba031908d90 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeBluetoothController.java +++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeBluetoothController.java @@ -14,6 +14,8 @@ package com.android.systemui.utils.leaks; +import android.testing.LeakCheck; + import com.android.settingslib.bluetooth.CachedBluetoothDevice; import com.android.systemui.statusbar.policy.BluetoothController; import com.android.systemui.statusbar.policy.BluetoothController.Callback; @@ -23,7 +25,7 @@ import java.util.Collection; public class FakeBluetoothController extends BaseLeakChecker<Callback> implements BluetoothController { - public FakeBluetoothController(LeakCheckedTest test) { + public FakeBluetoothController(LeakCheck test) { super(test, "bluetooth"); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeCastController.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeCastController.java index 08211f857cb8..51149abe792d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeCastController.java +++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeCastController.java @@ -14,13 +14,15 @@ package com.android.systemui.utils.leaks; +import android.testing.LeakCheck; + import com.android.systemui.statusbar.policy.CastController; import com.android.systemui.statusbar.policy.CastController.Callback; import java.util.Set; public class FakeCastController extends BaseLeakChecker<Callback> implements CastController { - public FakeCastController(LeakCheckedTest test) { + public FakeCastController(LeakCheck test) { super(test, "cast"); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeDataSaverController.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeDataSaverController.java index 857a78510c1e..886722e46376 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeDataSaverController.java +++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeDataSaverController.java @@ -14,12 +14,14 @@ package com.android.systemui.utils.leaks; +import android.testing.LeakCheck; + import com.android.systemui.statusbar.policy.DataSaverController; import com.android.systemui.statusbar.policy.DataSaverController.Listener; public class FakeDataSaverController extends BaseLeakChecker<Listener> implements DataSaverController { - public FakeDataSaverController(LeakCheckedTest test) { + public FakeDataSaverController(LeakCheck test) { super(test, "datasaver"); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeExtensionController.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeExtensionController.java index c0f578373fe4..b9d188a3871a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeExtensionController.java +++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeExtensionController.java @@ -14,7 +14,8 @@ package com.android.systemui.utils.leaks; -import static org.mockito.Mockito.mock; +import android.testing.LeakCheck; +import android.testing.LeakCheck.Tracker; import com.android.systemui.statusbar.policy.ExtensionController; @@ -25,7 +26,7 @@ public class FakeExtensionController implements ExtensionController { private final Tracker mTracker; - public FakeExtensionController(LeakCheckedTest test) { + public FakeExtensionController(LeakCheck test) { mTracker = test.getTracker("extension"); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeFlashlightController.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeFlashlightController.java index 630abd75af97..f6fd2cb8f3b1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeFlashlightController.java +++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeFlashlightController.java @@ -14,12 +14,14 @@ package com.android.systemui.utils.leaks; +import android.testing.LeakCheck; + import com.android.systemui.statusbar.policy.FlashlightController; import com.android.systemui.statusbar.policy.FlashlightController.FlashlightListener; public class FakeFlashlightController extends BaseLeakChecker<FlashlightListener> implements FlashlightController { - public FakeFlashlightController(LeakCheckedTest test) { + public FakeFlashlightController(LeakCheck test) { super(test, "flashlight"); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeHotspotController.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeHotspotController.java index 781960d82e59..69e236172fef 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeHotspotController.java +++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeHotspotController.java @@ -14,12 +14,14 @@ package com.android.systemui.utils.leaks; +import android.testing.LeakCheck; + import com.android.systemui.statusbar.policy.HotspotController; import com.android.systemui.statusbar.policy.HotspotController.Callback; public class FakeHotspotController extends BaseLeakChecker<Callback> implements HotspotController { - public FakeHotspotController(LeakCheckedTest test) { + public FakeHotspotController(LeakCheck test) { super(test, "hotspot"); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeKeyguardMonitor.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeKeyguardMonitor.java index 21871fc4cb75..51e35cc3915e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeKeyguardMonitor.java +++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeKeyguardMonitor.java @@ -14,13 +14,15 @@ package com.android.systemui.utils.leaks; +import android.testing.LeakCheck; + import com.android.systemui.statusbar.policy.KeyguardMonitor; public class FakeKeyguardMonitor implements KeyguardMonitor { private final BaseLeakChecker<Callback> mCallbackController; - public FakeKeyguardMonitor(LeakCheckedTest test) { + public FakeKeyguardMonitor(LeakCheck test) { mCallbackController = new BaseLeakChecker<Callback>(test, "keyguard"); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeLocationController.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeLocationController.java index eab436cd8750..29d7f1ca85bc 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeLocationController.java +++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeLocationController.java @@ -14,12 +14,14 @@ package com.android.systemui.utils.leaks; +import android.testing.LeakCheck; + import com.android.systemui.statusbar.policy.LocationController; import com.android.systemui.statusbar.policy.LocationController.LocationSettingsChangeCallback; public class FakeLocationController extends BaseLeakChecker<LocationSettingsChangeCallback> implements LocationController { - public FakeLocationController(LeakCheckedTest test) { + public FakeLocationController(LeakCheck test) { super(test, "location"); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeManagedProfileController.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeManagedProfileController.java index 0ec0d778a472..18b07cf25fbc 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeManagedProfileController.java +++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeManagedProfileController.java @@ -14,12 +14,14 @@ package com.android.systemui.utils.leaks; +import android.testing.LeakCheck; + import com.android.systemui.statusbar.phone.ManagedProfileController; import com.android.systemui.statusbar.phone.ManagedProfileController.Callback; public class FakeManagedProfileController extends BaseLeakChecker<Callback> implements ManagedProfileController { - public FakeManagedProfileController(LeakCheckedTest test) { + public FakeManagedProfileController(LeakCheck test) { super(test, "profile"); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeNetworkController.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeNetworkController.java index 47ed5ca05312..64fe8dd4a98f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeNetworkController.java +++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeNetworkController.java @@ -15,25 +15,23 @@ package com.android.systemui.utils.leaks; import android.os.Bundle; +import android.testing.LeakCheck; import com.android.settingslib.net.DataUsageController; import com.android.systemui.statusbar.policy.DataSaverController; import com.android.systemui.statusbar.policy.NetworkController; import com.android.systemui.statusbar.policy.NetworkController.SignalCallback; -import java.io.FileDescriptor; -import java.io.PrintWriter; - public class FakeNetworkController extends BaseLeakChecker<SignalCallback> implements NetworkController { private final FakeDataSaverController mDataSaverController; private final BaseLeakChecker<EmergencyListener> mEmergencyChecker; - public FakeNetworkController(LeakCheckedTest test) { + public FakeNetworkController(LeakCheck test) { super(test, "network"); mDataSaverController = new FakeDataSaverController(test); - mEmergencyChecker = new BaseLeakChecker<EmergencyListener>(test, "emergency"); + mEmergencyChecker = new BaseLeakChecker<>(test, "emergency"); } @Override diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeNextAlarmController.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeNextAlarmController.java index 707fc4b4880d..5ae8e22c06ee 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeNextAlarmController.java +++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeNextAlarmController.java @@ -14,13 +14,15 @@ package com.android.systemui.utils.leaks; +import android.testing.LeakCheck; + import com.android.systemui.statusbar.policy.NextAlarmController; import com.android.systemui.statusbar.policy.NextAlarmController.NextAlarmChangeCallback; public class FakeNextAlarmController extends BaseLeakChecker<NextAlarmChangeCallback> implements NextAlarmController { - public FakeNextAlarmController(LeakCheckedTest test) { + public FakeNextAlarmController(LeakCheck test) { super(test, "alarm"); } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakePluginManager.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakePluginManager.java index 59a93618cdb1..0a83a896dfaf 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakePluginManager.java +++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakePluginManager.java @@ -15,17 +15,17 @@ package com.android.systemui.utils.leaks; import android.content.Context; +import android.testing.LeakCheck; import com.android.systemui.plugins.Plugin; import com.android.systemui.plugins.PluginListener; import com.android.systemui.plugins.PluginManager; -public class FakePluginManager extends PluginManager { +public class FakePluginManager implements PluginManager { private final BaseLeakChecker<PluginListener> mLeakChecker; - public FakePluginManager(Context context, LeakCheckedTest test) { - super(context); + public FakePluginManager(LeakCheck test) { mLeakChecker = new BaseLeakChecker<>(test, "Plugin"); } @@ -36,11 +36,38 @@ public class FakePluginManager extends PluginManager { } @Override + public <T extends Plugin> void addPluginListener(PluginListener<T> listener, Class<?> cls) { + mLeakChecker.addCallback(listener); + } + + @Override + public <T extends Plugin> void addPluginListener(PluginListener<T> listener, Class<?> cls, + boolean allowMultiple) { + mLeakChecker.addCallback(listener); + } + + @Override + public <T extends Plugin> void addPluginListener(String action, PluginListener<T> listener, + Class<?> cls) { + mLeakChecker.addCallback(listener); + } + + @Override public void removePluginListener(PluginListener<?> listener) { mLeakChecker.removeCallback(listener); } @Override + public <T> boolean dependsOn(Plugin p, Class<T> cls) { + return false; + } + + @Override + public <T extends Plugin> T getOneShotPlugin(Class<T> cls) { + return null; + } + + @Override public <T extends Plugin> T getOneShotPlugin(String action, Class<?> cls) { return null; } diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeRotationLockController.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeRotationLockController.java index 00e2404e931f..d60fe78e9d6e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeRotationLockController.java +++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeRotationLockController.java @@ -14,12 +14,14 @@ package com.android.systemui.utils.leaks; +import android.testing.LeakCheck; + import com.android.systemui.statusbar.policy.RotationLockController; import com.android.systemui.statusbar.policy.RotationLockController.RotationLockControllerCallback; public class FakeRotationLockController extends BaseLeakChecker<RotationLockControllerCallback> implements RotationLockController { - public FakeRotationLockController(LeakCheckedTest test) { + public FakeRotationLockController(LeakCheck test) { super(test, "rotation"); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeSecurityController.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeSecurityController.java index 2d53c77cd163..157b8a04720b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeSecurityController.java +++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeSecurityController.java @@ -14,12 +14,14 @@ package com.android.systemui.utils.leaks; +import android.testing.LeakCheck; + import com.android.systemui.statusbar.policy.SecurityController; import com.android.systemui.statusbar.policy.SecurityController.SecurityControllerCallback; public class FakeSecurityController extends BaseLeakChecker<SecurityControllerCallback> implements SecurityController { - public FakeSecurityController(LeakCheckedTest test) { + public FakeSecurityController(LeakCheck test) { super(test, "security"); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java index b13535f75aee..6b501af95097 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java +++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java @@ -14,6 +14,8 @@ package com.android.systemui.utils.leaks; +import android.testing.LeakCheck; + import com.android.internal.statusbar.StatusBarIcon; import com.android.systemui.statusbar.phone.StatusBarIconController; import com.android.systemui.statusbar.phone.StatusBarIconController.IconManager; @@ -21,7 +23,7 @@ import com.android.systemui.statusbar.phone.StatusBarIconController.IconManager; public class FakeStatusBarIconController extends BaseLeakChecker<IconManager> implements StatusBarIconController { - public FakeStatusBarIconController(LeakCheckedTest test) { + public FakeStatusBarIconController(LeakCheck test) { super(test, "StatusBarGroup"); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeTunerService.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeTunerService.java index b841ce908ca2..8db82e2a52fc 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeTunerService.java +++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeTunerService.java @@ -15,6 +15,7 @@ package com.android.systemui.utils.leaks; import android.content.Context; +import android.testing.LeakCheck; import com.android.systemui.tuner.TunerService; @@ -22,10 +23,8 @@ public class FakeTunerService extends TunerService { private final BaseLeakChecker<Tunable> mBaseLeakChecker; - public FakeTunerService(Context context, LeakCheckedTest test) { - super(context); + public FakeTunerService(LeakCheck test) { mBaseLeakChecker = new BaseLeakChecker<>(test, "tunable"); - destroy(); } @Override @@ -40,4 +39,39 @@ public class FakeTunerService extends TunerService { public void removeTunable(Tunable tunable) { mBaseLeakChecker.removeCallback(tunable); } + + @Override + public void clearAll() { + + } + + @Override + public void destroy() { + + } + + @Override + public String getValue(String setting) { + return null; + } + + @Override + public int getValue(String setting, int def) { + return def; + } + + @Override + public String getValue(String setting, String def) { + return def; + } + + @Override + public void setValue(String setting, String value) { + + } + + @Override + public void setValue(String setting, int value) { + + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeUserInfoController.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeUserInfoController.java index 578b31089d75..f7ef653aebf2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeUserInfoController.java +++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeUserInfoController.java @@ -14,12 +14,14 @@ package com.android.systemui.utils.leaks; +import android.testing.LeakCheck; + import com.android.systemui.statusbar.policy.UserInfoController; import com.android.systemui.statusbar.policy.UserInfoController.OnUserInfoChangedListener; public class FakeUserInfoController extends BaseLeakChecker<OnUserInfoChangedListener> implements UserInfoController { - public FakeUserInfoController(LeakCheckedTest test) { + public FakeUserInfoController(LeakCheck test) { super(test, "user_info"); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeZenModeController.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeZenModeController.java index 7581363c78f0..fb9bf7a15f60 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeZenModeController.java +++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeZenModeController.java @@ -18,12 +18,13 @@ import android.content.ComponentName; import android.net.Uri; import android.service.notification.ZenModeConfig; import android.service.notification.ZenModeConfig.ZenRule; +import android.testing.LeakCheck; import com.android.systemui.statusbar.policy.ZenModeController; import com.android.systemui.statusbar.policy.ZenModeController.Callback; public class FakeZenModeController extends BaseLeakChecker<Callback> implements ZenModeController { - public FakeZenModeController(LeakCheckedTest test) { + public FakeZenModeController(LeakCheck test) { super(test, "zen"); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/LeakCheckedTest.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/LeakCheckedTest.java index 6c5152496692..94af77332f46 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/LeakCheckedTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/LeakCheckedTest.java @@ -15,8 +15,8 @@ package com.android.systemui.utils.leaks; import static org.mockito.Matchers.any; -import static org.mockito.Mockito.doAnswer; +import android.testing.LeakCheck; import android.util.ArrayMap; import com.android.systemui.SysuiTestCase; @@ -25,7 +25,6 @@ import com.android.systemui.statusbar.phone.ManagedProfileController; import com.android.systemui.statusbar.phone.StatusBarIconController; import com.android.systemui.statusbar.policy.BatteryController; import com.android.systemui.statusbar.policy.BluetoothController; -import com.android.systemui.statusbar.policy.CallbackController; import com.android.systemui.statusbar.policy.CastController; import com.android.systemui.statusbar.policy.FlashlightController; import com.android.systemui.statusbar.policy.HotspotController; @@ -41,12 +40,7 @@ import com.android.systemui.tuner.TunerService; import org.junit.Assert; import org.junit.Rule; -import org.junit.rules.TestWatcher; -import org.junit.runner.Description; -import org.mockito.invocation.InvocationOnMock; -import org.mockito.stubbing.Answer; -import java.util.HashMap; import java.util.Map; /** @@ -56,10 +50,7 @@ import java.util.Map; public abstract class LeakCheckedTest extends SysuiTestCase { private static final String TAG = "LeakCheckedTest"; - private final Map<String, Tracker> mTrackers = new HashMap<>(); - private final Map<Class, Object> mLeakCheckers = new ArrayMap<>(); - - public static final Class<?>[] ALL_SUPPORTED_CLASSES = new Class[] { + public static final Class<?>[] ALL_SUPPORTED_CLASSES = new Class[]{ BluetoothController.class, LocationController.class, RotationLockController.class, @@ -80,71 +71,11 @@ public abstract class LeakCheckedTest extends SysuiTestCase { }; @Rule - public TestWatcher successWatcher = new TestWatcher() { - @Override - protected void succeeded(Description description) { - verify(); - } - }; - - public <T> T getLeakChecker(Class<T> cls) { - Object obj = mLeakCheckers.get(cls); - if (obj == null) { - // Lazy create checkers so we only have the ones we need. - if (cls == BluetoothController.class) { - obj = new FakeBluetoothController(this); - } else if (cls == LocationController.class) { - obj = new FakeLocationController(this); - } else if (cls == RotationLockController.class) { - obj = new FakeRotationLockController(this); - } else if (cls == ZenModeController.class) { - obj = new FakeZenModeController(this); - } else if (cls == CastController.class) { - obj = new FakeCastController(this); - } else if (cls == HotspotController.class) { - obj = new FakeHotspotController(this); - } else if (cls == FlashlightController.class) { - obj = new FakeFlashlightController(this); - } else if (cls == UserInfoController.class) { - obj = new FakeUserInfoController(this); - } else if (cls == KeyguardMonitor.class) { - obj = new FakeKeyguardMonitor(this); - } else if (cls == BatteryController.class) { - obj = new FakeBatteryController(this); - } else if (cls == SecurityController.class) { - obj = new FakeSecurityController(this); - } else if (cls == ManagedProfileController.class) { - obj = new FakeManagedProfileController(this); - } else if (cls == NextAlarmController.class) { - obj = new FakeNextAlarmController(this); - } else if (cls == NetworkController.class) { - obj = new FakeNetworkController(this); - } else if (cls == PluginManager.class) { - obj = new FakePluginManager(mContext, this); - } else if (cls == TunerService.class) { - obj = new FakeTunerService(mContext, this); - } else if (cls == StatusBarIconController.class) { - obj = new FakeStatusBarIconController(this); - } else { - Assert.fail(cls.getName() + " is not supported by LeakCheckedTest yet"); - } - mLeakCheckers.put(cls, obj); - } - return (T) obj; - } + public SysuiLeakCheck mLeakCheck = new SysuiLeakCheck(); @Override - public Tracker getTracker(String tag) { - Tracker t = mTrackers.get(tag); - if (t == null) { - t = new Tracker(); - mTrackers.put(tag, t); - } - return t; - } - - public void verify() { - mTrackers.values().forEach(Tracker::verify); + public LeakCheck getLeakCheck() { + return mLeakCheck; } public void injectLeakCheckedDependencies(Class<?>... cls) { @@ -154,26 +85,61 @@ public abstract class LeakCheckedTest extends SysuiTestCase { } public <T> void injectLeakCheckedDependency(Class<T> c) { - injectTestDependency(c, getLeakChecker(c)); + mDependency.injectTestDependency(c, mLeakCheck.getLeakChecker(c)); } - public <T extends CallbackController> T addListening(T mock, Class<T> cls, String tag) { - doAnswer(new Answer<Void>() { - @Override - public Void answer(InvocationOnMock invocation) throws Throwable { - getTracker(tag).getLeakInfo(invocation.getArguments()[0]) - .addAllocation(new Throwable()); - return null; - } - }).when(mock).addCallback(any()); - doAnswer(new Answer<Void>() { - @Override - public Void answer(InvocationOnMock invocation) throws Throwable { - getTracker(tag).getLeakInfo(invocation.getArguments()[0]).clearAllocations(); - return null; + public static class SysuiLeakCheck extends LeakCheck { + + private final Map<Class, Object> mLeakCheckers = new ArrayMap<>(); + + public SysuiLeakCheck() { + super(); + } + + public <T> T getLeakChecker(Class<T> cls) { + Object obj = mLeakCheckers.get(cls); + if (obj == null) { + // Lazy create checkers so we only have the ones we need. + if (cls == BluetoothController.class) { + obj = new FakeBluetoothController(this); + } else if (cls == LocationController.class) { + obj = new FakeLocationController(this); + } else if (cls == RotationLockController.class) { + obj = new FakeRotationLockController(this); + } else if (cls == ZenModeController.class) { + obj = new FakeZenModeController(this); + } else if (cls == CastController.class) { + obj = new FakeCastController(this); + } else if (cls == HotspotController.class) { + obj = new FakeHotspotController(this); + } else if (cls == FlashlightController.class) { + obj = new FakeFlashlightController(this); + } else if (cls == UserInfoController.class) { + obj = new FakeUserInfoController(this); + } else if (cls == KeyguardMonitor.class) { + obj = new FakeKeyguardMonitor(this); + } else if (cls == BatteryController.class) { + obj = new FakeBatteryController(this); + } else if (cls == SecurityController.class) { + obj = new FakeSecurityController(this); + } else if (cls == ManagedProfileController.class) { + obj = new FakeManagedProfileController(this); + } else if (cls == NextAlarmController.class) { + obj = new FakeNextAlarmController(this); + } else if (cls == NetworkController.class) { + obj = new FakeNetworkController(this); + } else if (cls == PluginManager.class) { + obj = new FakePluginManager(this); + } else if (cls == TunerService.class) { + obj = new FakeTunerService(this); + } else if (cls == StatusBarIconController.class) { + obj = new FakeStatusBarIconController(this); + } else { + Assert.fail(cls.getName() + " is not supported by LeakCheckedTest yet"); + } + mLeakCheckers.put(cls, obj); } - }).when(mock).removeCallback(any()); - mLeakCheckers.put(cls, mock); - return mock; + return (T) obj; + } } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/LeakInfo.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/LeakInfo.java deleted file mode 100644 index 1d016fbe092b..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/LeakInfo.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file - * except in compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the - * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ - -package com.android.systemui.utils.leaks; - -import android.util.Log; - -import org.junit.Assert; - -import java.io.PrintWriter; -import java.io.StringWriter; -import java.util.ArrayList; -import java.util.List; - -public class LeakInfo { - private static final String TAG = "LeakInfo"; - private List<Throwable> mThrowables = new ArrayList<>(); - - LeakInfo() { - } - - public void addAllocation(Throwable t) { - // TODO: Drop off the first element in the stack trace here to have a cleaner stack. - mThrowables.add(t); - } - - public void clearAllocations() { - mThrowables.clear(); - } - - void verify() { - if (mThrowables.size() == 0) return; - Log.e(TAG, "Listener or binding not properly released"); - for (Throwable t : mThrowables) { - Log.e(TAG, "Allocation found", t); - } - StringWriter writer = new StringWriter(); - mThrowables.get(0).printStackTrace(new PrintWriter(writer)); - Assert.fail("Listener or binding not properly released\n" - + writer.toString()); - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/Tracker.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/Tracker.java deleted file mode 100644 index 26ffd10b8362..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/Tracker.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file - * except in compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the - * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ - -package com.android.systemui.utils.leaks; - -import android.util.ArrayMap; - -import com.android.systemui.utils.leaks.LeakInfo; - -import java.util.Map; - -public class Tracker { - private Map<Object, LeakInfo> mObjects = new ArrayMap<>(); - - public LeakInfo getLeakInfo(Object object) { - LeakInfo leakInfo = mObjects.get(object); - if (leakInfo == null) { - leakInfo = new LeakInfo(); - mObjects.put(object, leakInfo); - } - return leakInfo; - } - - void verify() { - mObjects.values().forEach(LeakInfo::verify); - } -} diff --git a/proto/src/metrics_constants.proto b/proto/src/metrics_constants.proto index 42446d13d09f..3800f297a8cc 100644 --- a/proto/src/metrics_constants.proto +++ b/proto/src/metrics_constants.proto @@ -44,6 +44,18 @@ message MetricsEvent { // The view or control was updated. TYPE_UPDATE = 6; + + // Type for APP_TRANSITION event: The transition started a new activity for which it's process + // wasn't running. + TYPE_TRANSITION_COLD_LAUNCH = 7; + + // Type for APP_TRANSITION event: The transition started a new activity for which it's process + // was already running. + TYPE_TRANSITION_WARM_LAUNCH = 8; + + // Type for APP_TRANSITION event: The transition brought an already existing activity to the + // front. + TYPE_TRANSITION_HOT_LAUNCH = 9; } // Known visual elements: views or controls. @@ -3544,6 +3556,58 @@ message MetricsEvent { // internal platform metrics use. RESERVED_FOR_LOGBUILDER_PID = 865; + // ACTION: Settings > Connected devices > Bluetooth -> Available devices + ACTION_SETTINGS_BLUETOOTH_PAIR = 866; + + // ACTION: Settings > Connected devices > Bluetooth -> Paired devices + ACTION_SETTINGS_BLUETOOTH_CONNECT = 867; + + // ACTION: Settings > Connected devices > Bluetooth -> Connected device + ACTION_SETTINGS_BLUETOOTH_DISCONNECT = 868; + + // ACTION: Settings > Connected devices > Bluetooth -> Error dialog + ACTION_SETTINGS_BLUETOOTH_CONNECT_ERROR = 869; + + // ACTION: Settings > Connected devices > Bluetooth master switch Toggle + ACTION_SETTINGS_MASTER_SWITCH_BLUETOOTH_TOGGLE = 870; + + // The name of the activity being launched in an app transition event. + APP_TRANSITION_ACTIVITY_NAME = 871; + + // ACTION: Settings > App detail > Uninstall + ACTION_SETTINGS_UNINSTALL_APP = 872; + + // ACTION: Settings > App detail > Uninstall Device admin app + ACTION_SETTINGS_UNINSTALL_DEVICE_ADMIN = 873; + + // ACTION: Settings > App detail > Disable app + ACTION_SETTINGS_DISABLE_APP = 874; + + // ACTION: Settings > App detail > Enable app + ACTION_SETTINGS_ENABLE_APP = 875; + + // ACTION: Settings > App detail > Clear data + ACTION_SETTINGS_CLEAR_APP_DATA = 876; + + // ACTION: Settings > App detail > Clear cache + ACTION_SETTINGS_CLEAR_APP_CACHE = 877; + + // ACTION: Clicking on any search result in Settings. + ACTION_CLICK_SETTINGS_SEARCH_INLINE_RESULT = 878; + + // FIELD: Settings inline search result name + FIELD_SETTINGS_SEARCH_INLINE_RESULT_NAME = 879; + + // 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 b4630efe80e8..c532efb79345 100644 --- a/rs/jni/android_renderscript_RenderScript.cpp +++ b/rs/jni/android_renderscript_RenderScript.cpp @@ -48,9 +48,6 @@ static constexpr bool kLogApi = false; using namespace android; -template <typename... T> -void UNUSED(T... t) {} - #define PER_ARRAY_TYPE(flag, fnc, readonly, ...) { \ jint len = 0; \ void *ptr = nullptr; \ diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java index fd93865ea97e..9e4d89cbc9c5 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java @@ -77,9 +77,6 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo */ static final int FLAG_FEATURE_INJECT_MOTION_EVENTS = 0x00000010; - static final int FEATURES_AFFECTING_MOTION_EVENTS = FLAG_FEATURE_INJECT_MOTION_EVENTS - | FLAG_FEATURE_AUTOCLICK | FLAG_FEATURE_TOUCH_EXPLORATION - | FLAG_FEATURE_SCREEN_MAGNIFIER; /** * Flag for enabling the feature to control the screen magnifier. If * {@link #FLAG_FEATURE_SCREEN_MAGNIFIER} is set this flag is ignored @@ -90,6 +87,16 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo */ static final int FLAG_FEATURE_CONTROL_SCREEN_MAGNIFIER = 0x00000020; + /** + * Flag for enabling the feature to trigger the screen magnifier + * from another on-device interaction. + */ + static final int FLAG_FEATURE_TRIGGERED_SCREEN_MAGNIFIER = 0x00000040; + + static final int FEATURES_AFFECTING_MOTION_EVENTS = FLAG_FEATURE_INJECT_MOTION_EVENTS + | FLAG_FEATURE_AUTOCLICK | FLAG_FEATURE_TOUCH_EXPLORATION + | FLAG_FEATURE_SCREEN_MAGNIFIER | FLAG_FEATURE_TRIGGERED_SCREEN_MAGNIFIER; + private final Runnable mProcessBatchedEventsRunnable = new Runnable() { @Override public void run() { @@ -379,6 +386,12 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo } } + void notifyAccessibilityButtonClicked() { + if (mMagnificationGestureHandler != null) { + mMagnificationGestureHandler.notifyShortcutTriggered(); + } + } + private void enableFeatures() { resetStreamState(); @@ -393,11 +406,14 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo } if ((mEnabledFeatures & FLAG_FEATURE_CONTROL_SCREEN_MAGNIFIER) != 0 - || (mEnabledFeatures & FLAG_FEATURE_SCREEN_MAGNIFIER) != 0) { + || ((mEnabledFeatures & FLAG_FEATURE_SCREEN_MAGNIFIER) != 0) + || ((mEnabledFeatures & FLAG_FEATURE_TRIGGERED_SCREEN_MAGNIFIER) != 0)) { final boolean detectControlGestures = (mEnabledFeatures & FLAG_FEATURE_SCREEN_MAGNIFIER) != 0; + final boolean triggerable = (mEnabledFeatures + & FLAG_FEATURE_TRIGGERED_SCREEN_MAGNIFIER) != 0; mMagnificationGestureHandler = new MagnificationGestureHandler( - mContext, mAms, detectControlGestures); + mContext, mAms, detectControlGestures, triggerable); addFirstEventHandler(mMagnificationGestureHandler); } diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index e0d78063ffb8..397938ac3160 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -50,6 +50,7 @@ import android.graphics.Point; import android.graphics.Rect; import android.graphics.Region; import android.hardware.display.DisplayManager; +import android.hardware.fingerprint.IFingerprintService; import android.hardware.input.InputManager; import android.net.Uri; import android.os.Binder; @@ -69,7 +70,6 @@ import android.os.UserHandle; import android.os.UserManager; import android.os.UserManagerInternal; import android.provider.Settings; -import android.hardware.fingerprint.IFingerprintService; import android.provider.SettingsStringUtil.ComponentNameSet; import android.provider.SettingsStringUtil.SettingStringHelper; import android.text.TextUtils; @@ -100,7 +100,9 @@ import android.view.accessibility.IAccessibilityManagerClient; import com.android.internal.R; import com.android.internal.annotations.GuardedBy; import com.android.internal.content.PackageMonitor; +import com.android.internal.os.HandlerCaller; import com.android.internal.os.SomeArgs; +import com.android.internal.util.IntPair; import com.android.server.LocalServices; import com.android.server.policy.AccessibilityShortcutController; import com.android.server.statusbar.StatusBarManagerInternal; @@ -120,6 +122,7 @@ import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.CopyOnWriteArrayList; +import java.util.function.Consumer; /** * This class is instantiated by the system as a system level service and can be @@ -413,10 +416,12 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { removeUser(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0)); } else if (Intent.ACTION_USER_PRESENT.equals(action)) { // We will update when the automation service dies. - UserState userState = getCurrentUserStateLocked(); - if (!userState.isUiAutomationSuppressingOtherServices()) { - if (readConfigurationForUserStateLocked(userState)) { - onUserStateChangedLocked(userState); + synchronized (mLock) { + UserState userState = getCurrentUserStateLocked(); + if (!userState.isUiAutomationSuppressingOtherServices()) { + if (readConfigurationForUserStateLocked(userState)) { + onUserStateChangedLocked(userState); + } } } } else if (Intent.ACTION_SETTING_RESTORED.equals(action)) { @@ -434,7 +439,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { } @Override - public int addClient(IAccessibilityManagerClient client, int userId) { + public long addClient(IAccessibilityManagerClient client, int userId) { synchronized (mLock) { // We treat calls from a profile as if made by its parent as profiles // share the accessibility state of the parent. The call below @@ -450,7 +455,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { if (DEBUG) { Slog.i(LOG_TAG, "Added global client for pid:" + Binder.getCallingPid()); } - return userState.getClientState(); + return IntPair.of( + userState.getClientState(), userState.mLastSentRelevantEventTypes); } else { userState.mUserClients.register(client); // If this client is not for the current user we do not @@ -460,7 +466,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { Slog.i(LOG_TAG, "Added user client for pid:" + Binder.getCallingPid() + " and userId:" + mCurrentUserId); } - return (resolvedUserId == mCurrentUserId) ? userState.getClientState() : 0; + return IntPair.of( + (resolvedUserId == mCurrentUserId) ? userState.getClientState() : 0, + userState.mLastSentRelevantEventTypes); } } } @@ -773,6 +781,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { userState.mIsTouchExplorationEnabled = false; userState.mIsEnhancedWebAccessibilityEnabled = false; userState.mIsDisplayMagnificationEnabled = false; + userState.mIsNavBarMagnificationEnabled = false; userState.mIsAutoclickEnabled = false; userState.mEnabledServices.clear(); } @@ -823,6 +832,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { userState.mIsTouchExplorationEnabled = touchExplorationEnabled; userState.mIsEnhancedWebAccessibilityEnabled = false; userState.mIsDisplayMagnificationEnabled = false; + userState.mIsNavBarMagnificationEnabled = false; userState.mIsAutoclickEnabled = false; userState.mEnabledServices.clear(); userState.mEnabledServices.add(service); @@ -1144,11 +1154,16 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { private void notifyAccessibilityButtonClickedLocked() { final UserState state = getCurrentUserStateLocked(); - for (int i = state.mBoundServices.size() - 1; i >= 0; i--) { - final Service service = state.mBoundServices.get(i); - // TODO(b/34720082): Only notify a single user-defined service - if (service.mRequestAccessibilityButton) { - service.notifyAccessibilityButtonClickedLocked(); + if (state.mIsNavBarMagnificationEnabled) { + mMainHandler.obtainMessage( + MainHandler.MSG_SEND_ACCESSIBILITY_BUTTON_TO_INPUT_FILTER).sendToTarget(); + } else { + for (int i = state.mBoundServices.size() - 1; i >= 0; i--) { + final Service service = state.mBoundServices.get(i); + // TODO(b/34720082): Only notify a single user-defined service + if (service.mRequestAccessibilityButton) { + service.notifyAccessibilityButtonClickedLocked(); + } } } } @@ -1323,6 +1338,35 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { scheduleNotifyClientsOfServicesStateChange(userState); } + private void updateRelevantEventsLocked(UserState userState) { + int relevantEventTypes = AccessibilityCache.CACHE_CRITICAL_EVENTS_MASK; + for (Service service : userState.mBoundServices) { + relevantEventTypes |= service.mEventTypes; + } + int finalRelevantEventTypes = relevantEventTypes; + + if (userState.mLastSentRelevantEventTypes != finalRelevantEventTypes) { + userState.mLastSentRelevantEventTypes = finalRelevantEventTypes; + mMainHandler.obtainMessage(MainHandler.MSG_SEND_RELEVANT_EVENTS_CHANGED_TO_CLIENTS, + userState.mUserId, finalRelevantEventTypes); + mMainHandler.post(() -> { + broadcastToClients(userState, (client) -> { + try { + client.setRelevantEventTypes(finalRelevantEventTypes); + } catch (RemoteException re) { + /* ignore */ + } + }); + }); + } + } + + private void broadcastToClients( + UserState userState, Consumer<IAccessibilityManagerClient> clientAction) { + mGlobalClients.broadcast(clientAction); + userState.mUserClients.broadcast(clientAction); + } + /** * Determines if given event can be dispatched to a service based on the package of the * event source. Specifically, a service is notified if it is interested in events from the @@ -1511,6 +1555,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { if (userState.mIsDisplayMagnificationEnabled) { flags |= AccessibilityInputFilter.FLAG_FEATURE_SCREEN_MAGNIFIER; } + if (userState.mIsNavBarMagnificationEnabled) { + flags |= AccessibilityInputFilter.FLAG_FEATURE_TRIGGERED_SCREEN_MAGNIFIER; + } if (userHasMagnificationServicesLocked(userState)) { flags |= AccessibilityInputFilter.FLAG_FEATURE_CONTROL_SCREEN_MAGNIFIER; } @@ -1633,6 +1680,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { scheduleUpdateFingerprintGestureHandling(userState); scheduleUpdateInputFilter(userState); scheduleUpdateClientsIfNeededLocked(userState); + updateRelevantEventsLocked(userState); } private void updateAccessibilityFocusBehaviorLocked(UserState userState) { @@ -1743,7 +1791,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { somethingChanged |= readTouchExplorationEnabledSettingLocked(userState); somethingChanged |= readHighTextContrastEnabledSettingLocked(userState); somethingChanged |= readEnhancedWebAccessibilityEnabledChangedLocked(userState); - somethingChanged |= readDisplayMagnificationEnabledSettingLocked(userState); + somethingChanged |= readMagnificationEnabledSettingsLocked(userState); somethingChanged |= readAutoclickEnabledSettingLocked(userState); somethingChanged |= readAccessibilityShortcutSettingLocked(userState); return somethingChanged; @@ -1772,13 +1820,19 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { return false; } - private boolean readDisplayMagnificationEnabledSettingLocked(UserState userState) { + private boolean readMagnificationEnabledSettingsLocked(UserState userState) { final boolean displayMagnificationEnabled = Settings.Secure.getIntForUser( mContext.getContentResolver(), Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED, 0, userState.mUserId) == 1; - if (displayMagnificationEnabled != userState.mIsDisplayMagnificationEnabled) { + final boolean navBarMagnificationEnabled = Settings.Secure.getIntForUser( + mContext.getContentResolver(), + Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED, + 0, userState.mUserId) == 1; + if ((displayMagnificationEnabled != userState.mIsDisplayMagnificationEnabled) + || (navBarMagnificationEnabled != userState.mIsNavBarMagnificationEnabled)) { userState.mIsDisplayMagnificationEnabled = displayMagnificationEnabled; + userState.mIsNavBarMagnificationEnabled = navBarMagnificationEnabled; return true; } return false; @@ -1980,8 +2034,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { return; } - if (userState.mIsDisplayMagnificationEnabled || - userHasListeningMagnificationServicesLocked(userState)) { + if (userState.mIsDisplayMagnificationEnabled || userState.mIsNavBarMagnificationEnabled + || userHasListeningMagnificationServicesLocked(userState)) { // Initialize the magnification controller if necessary getMagnificationController(); mMagnificationController.register(); @@ -2203,6 +2257,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { pw.append(", touchExplorationEnabled=" + userState.mIsTouchExplorationEnabled); pw.append(", displayMagnificationEnabled=" + userState.mIsDisplayMagnificationEnabled); + pw.append(", navBarMagnificationEnabled=" + + userState.mIsNavBarMagnificationEnabled); pw.append(", autoclickEnabled=" + userState.mIsAutoclickEnabled); if (userState.mUiAutomationService != null) { pw.append(", "); @@ -2281,6 +2337,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { public static final int MSG_CLEAR_ACCESSIBILITY_FOCUS = 9; 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); @@ -2351,6 +2409,30 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { case MSG_UPDATE_FINGERPRINT: { updateFingerprintGestureHandling((UserState) msg.obj); } break; + + case MSG_SEND_RELEVANT_EVENTS_CHANGED_TO_CLIENTS: { + final int userId = msg.arg1; + final int relevantEventTypes = msg.arg2; + final UserState userState; + synchronized (mLock) { + userState = getUserStateLocked(userId); + } + broadcastToClients(userState, (client) -> { + try { + client.setRelevantEventTypes(relevantEventTypes); + } catch (RemoteException re) { + /* ignore */ + } + }); + } break; + + case MSG_SEND_ACCESSIBILITY_BUTTON_TO_INPUT_FILTER: { + synchronized (mLock) { + if (mHasInputFilter && mInputFilter != null) { + mInputFilter.notifyAccessibilityButtonClicked(); + } + } + } } } @@ -2380,19 +2462,13 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { private void sendStateToClients(int clientState, RemoteCallbackList<IAccessibilityManagerClient> clients) { - try { - final int userClientCount = clients.beginBroadcast(); - for (int i = 0; i < userClientCount; i++) { - IAccessibilityManagerClient client = clients.getBroadcastItem(i); - try { - client.setState(clientState); - } catch (RemoteException re) { - /* ignore */ - } + clients.broadcast((client) -> { + try { + client.setState(clientState); + } catch (RemoteException re) { + /* ignore */ } - } finally { - clients.finishBroadcast(); - } + }); } private void notifyClientsOfServicesStateChange( @@ -4710,6 +4786,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { public final CopyOnWriteArrayList<Service> mBoundServices = new CopyOnWriteArrayList<>(); + public int mLastSentRelevantEventTypes = AccessibilityEvent.TYPES_ALL_MASK; + public final Map<ComponentName, Service> mComponentNameToServiceMap = new HashMap<>(); @@ -4737,6 +4815,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { public boolean mIsTextHighContrastEnabled; public boolean mIsEnhancedWebAccessibilityEnabled; public boolean mIsDisplayMagnificationEnabled; + public boolean mIsNavBarMagnificationEnabled; public boolean mIsAutoclickEnabled; public boolean mIsPerformGesturesEnabled; public boolean mIsFilterKeyEventsEnabled; @@ -4805,6 +4884,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { mIsTouchExplorationEnabled = false; mIsEnhancedWebAccessibilityEnabled = false; mIsDisplayMagnificationEnabled = false; + mIsNavBarMagnificationEnabled = false; mIsAutoclickEnabled = false; mSoftKeyboardShowMode = 0; @@ -4837,6 +4917,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { private final Uri mDisplayMagnificationEnabledUri = Settings.Secure.getUriFor( Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED); + private final Uri mNavBarMagnificationEnabledUri = Settings.Secure.getUriFor( + Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED); + private final Uri mAutoclickEnabledUri = Settings.Secure.getUriFor( Settings.Secure.ACCESSIBILITY_AUTOCLICK_ENABLED); @@ -4876,6 +4959,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { false, this, UserHandle.USER_ALL); contentResolver.registerContentObserver(mDisplayMagnificationEnabledUri, false, this, UserHandle.USER_ALL); + contentResolver.registerContentObserver(mNavBarMagnificationEnabledUri, + false, this, UserHandle.USER_ALL); contentResolver.registerContentObserver(mAutoclickEnabledUri, false, this, UserHandle.USER_ALL); contentResolver.registerContentObserver(mEnabledAccessibilityServicesUri, @@ -4915,8 +5000,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { if (readTouchExplorationEnabledSettingLocked(userState)) { onUserStateChangedLocked(userState); } - } else if (mDisplayMagnificationEnabledUri.equals(uri)) { - if (readDisplayMagnificationEnabledSettingLocked(userState)) { + } else if (mDisplayMagnificationEnabledUri.equals(uri) + || mNavBarMagnificationEnabledUri.equals(uri)) { + if (readMagnificationEnabledSettingsLocked(userState)) { onUserStateChangedLocked(userState); } } else if (mAutoclickEnabledUri.equals(uri)) { diff --git a/services/accessibility/java/com/android/server/accessibility/MagnificationController.java b/services/accessibility/java/com/android/server/accessibility/MagnificationController.java index f65046ce4350..caa74b9512d1 100644 --- a/services/accessibility/java/com/android/server/accessibility/MagnificationController.java +++ b/services/accessibility/java/com/android/server/accessibility/MagnificationController.java @@ -685,6 +685,12 @@ class MagnificationController implements Handler.Callback { } } + void setForceShowMagnifiableBounds(boolean show) { + if (mRegistered) { + mWindowManager.setForceShowMagnifiableBounds(show); + } + } + private void getMagnifiedFrameInContentCoordsLocked(Rect outFrame) { final float scale = getSentScale(); final float offsetX = getSentOffsetX(); diff --git a/services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java index f6e5340ed7ee..7e82edaae3e5 100644 --- a/services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java +++ b/services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java @@ -16,7 +16,10 @@ package com.android.server.accessibility; +import android.content.BroadcastReceiver; import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; import android.os.Handler; import android.os.Message; import android.util.MathUtils; @@ -57,22 +60,30 @@ import android.view.accessibility.AccessibilityEvent; * be the same but when the finger goes up the screen will stay magnified. * In other words, the initial magnified state is sticky. * - * 3. Pinching with any number of additional fingers when viewport dragging + * 3. Magnification can optionally be "triggered" by some external shortcut + * affordance. When this occurs via {@link #notifyShortcutTriggered()} a + * subsequent tap in a magnifiable region will engage permanent screen + * magnification as described in #1. Alternatively, a subsequent long-press + * or drag will engage magnification with viewport dragging as described in + * #2. Once magnified, all following behaviors apply whether magnification + * was engaged via a triple-tap or by a triggered shortcut. + * + * 4. Pinching with any number of additional fingers when viewport dragging * is enabled, i.e. the user triple tapped and holds, would adjust the * magnification scale which will become the current default magnification * scale. The next time the user magnifies the same magnification scale * would be used. * - * 4. When in a permanent magnified state the user can use two or more fingers + * 5. When in a permanent magnified state the user can use two or more fingers * to pan the viewport. Note that in this mode the content is panned as * opposed to the viewport dragging mode in which the viewport is moved. * - * 5. When in a permanent magnified state the user can use two or more + * 6. When in a permanent magnified state the user can use two or more * fingers to change the magnification scale which will become the current * default magnification scale. The next time the user magnifies the same * magnification scale would be used. * - * 6. The magnification scale will be persisted in settings and in the cloud. + * 7. The magnification scale will be persisted in settings and in the cloud. */ class MagnificationGestureHandler implements EventStreamTransformation { private static final String LOG_TAG = "MagnificationEventHandler"; @@ -94,8 +105,10 @@ class MagnificationGestureHandler implements EventStreamTransformation { private final MagnifiedContentInteractionStateHandler mMagnifiedContentInteractionStateHandler; private final StateViewportDraggingHandler mStateViewportDraggingHandler; + private final ScreenStateReceiver mScreenStateReceiver; - private final boolean mDetectControlGestures; + private final boolean mDetectTripleTap; + private final boolean mTriggerable; private EventStreamTransformation mNext; @@ -104,19 +117,39 @@ class MagnificationGestureHandler implements EventStreamTransformation { private boolean mTranslationEnabledBeforePan; + private boolean mShortcutTriggered; + private PointerCoords[] mTempPointerCoords; private PointerProperties[] mTempPointerProperties; private long mDelegatingStateDownTime; + /** + * @param context Context for resolving various magnification-related resources + * @param ams AccessibilityManagerService used to obtain a {@link MagnificationController} + * @param detectTripleTap {@code true} if this detector should detect and respond to triple-tap + * gestures for engaging and disengaging magnification, + * {@code false} if it should ignore such gestures + * @param triggerable {@code true} if this detector should be "triggerable" by some external + * shortcut invoking {@link #notifyShortcutTriggered}, {@code + * false} if it should ignore such triggers. + */ public MagnificationGestureHandler(Context context, AccessibilityManagerService ams, - boolean detectControlGestures) { + boolean detectTripleTap, boolean triggerable) { mMagnificationController = ams.getMagnificationController(); mDetectingStateHandler = new DetectingStateHandler(context); mStateViewportDraggingHandler = new StateViewportDraggingHandler(); mMagnifiedContentInteractionStateHandler = new MagnifiedContentInteractionStateHandler(context); - mDetectControlGestures = detectControlGestures; + mDetectTripleTap = detectTripleTap; + mTriggerable = triggerable; + + if (triggerable) { + mScreenStateReceiver = new ScreenStateReceiver(context, this); + mScreenStateReceiver.register(); + } else { + mScreenStateReceiver = null; + } transitionToState(STATE_DETECTING); } @@ -129,7 +162,7 @@ class MagnificationGestureHandler implements EventStreamTransformation { } return; } - if (!mDetectControlGestures) { + if (!mDetectTripleTap && !mTriggerable) { if (mNext != null) { dispatchTransformedEvent(event, rawEvent, policyFlags); } @@ -151,7 +184,7 @@ class MagnificationGestureHandler implements EventStreamTransformation { break; case STATE_MAGNIFIED_INTERACTION: { // mMagnifiedContentInteractionStateHandler handles events only - // if this is the current state since it uses ScaleGestureDetecotr + // if this is the current state since it uses ScaleGestureDetector // and a GestureDetector which need well formed event stream. } break; @@ -193,11 +226,34 @@ class MagnificationGestureHandler implements EventStreamTransformation { @Override public void onDestroy() { + if (mScreenStateReceiver != null) { + mScreenStateReceiver.unregister(); + } clear(); } + void notifyShortcutTriggered() { + if (mTriggerable) { + if (mMagnificationController.resetIfNeeded(true)) { + clear(); + } else { + setMagnificationShortcutTriggered(!mShortcutTriggered); + } + } + } + + private void setMagnificationShortcutTriggered(boolean state) { + if (mShortcutTriggered == state) { + return; + } + + mShortcutTriggered = state; + mMagnificationController.setForceShowMagnifiableBounds(state); + } + private void clear() { mCurrentState = STATE_DETECTING; + setMagnificationShortcutTriggered(false); mDetectingStateHandler.clear(); mStateViewportDraggingHandler.clear(); mMagnifiedContentInteractionStateHandler.clear(); @@ -575,31 +631,51 @@ class MagnificationGestureHandler implements EventStreamTransformation { mHandler.removeMessages(MESSAGE_TRANSITION_TO_DELEGATING_STATE); if (!mMagnificationController.magnificationRegionContains( event.getX(), event.getY())) { - transitionToDelegatingStateAndClear(); + transitionToDelegatingState(!mShortcutTriggered); return; } - if (mTapCount == ACTION_TAP_COUNT - 1 && mLastDownEvent != null - && GestureUtils.isMultiTap(mLastDownEvent, event, - mMultiTapTimeSlop, mMultiTapDistanceSlop, 0)) { + if (mShortcutTriggered) { Message message = mHandler.obtainMessage(MESSAGE_ON_ACTION_TAP_AND_HOLD, policyFlags, 0, event); mHandler.sendMessageDelayed(message, ViewConfiguration.getLongPressTimeout()); - } else if (mTapCount < ACTION_TAP_COUNT) { + return; + } + if (mDetectTripleTap) { + if ((mTapCount == ACTION_TAP_COUNT - 1) && (mLastDownEvent != null) + && GestureUtils.isMultiTap(mLastDownEvent, event, mMultiTapTimeSlop, + mMultiTapDistanceSlop, 0)) { + Message message = mHandler.obtainMessage(MESSAGE_ON_ACTION_TAP_AND_HOLD, + policyFlags, 0, event); + mHandler.sendMessageDelayed(message, + ViewConfiguration.getLongPressTimeout()); + } else if (mTapCount < ACTION_TAP_COUNT) { + Message message = mHandler.obtainMessage( + MESSAGE_TRANSITION_TO_DELEGATING_STATE); + mHandler.sendMessageDelayed(message, mMultiTapTimeSlop); + } + clearLastDownEvent(); + mLastDownEvent = MotionEvent.obtain(event); + } else if (mMagnificationController.isMagnifying()) { + // If magnified, consume an ACTION_DOWN until mMultiTapTimeSlop or + // mTapDistanceSlop is reached to ensure MAGNIFIED_INTERACTION is reachable. Message message = mHandler.obtainMessage( MESSAGE_TRANSITION_TO_DELEGATING_STATE); mHandler.sendMessageDelayed(message, mMultiTapTimeSlop); + return; + } else { + transitionToDelegatingState(true); + return; } - clearLastDownEvent(); - mLastDownEvent = MotionEvent.obtain(event); } break; case MotionEvent.ACTION_POINTER_DOWN: { if (mMagnificationController.isMagnifying()) { + mHandler.removeMessages(MESSAGE_TRANSITION_TO_DELEGATING_STATE); transitionToState(STATE_MAGNIFIED_INTERACTION); clear(); } else { - transitionToDelegatingStateAndClear(); + transitionToDelegatingState(true); } } break; @@ -608,29 +684,34 @@ class MagnificationGestureHandler implements EventStreamTransformation { final double distance = GestureUtils.computeDistance(mLastDownEvent, event, 0); if (Math.abs(distance) > mTapDistanceSlop) { - transitionToDelegatingStateAndClear(); + transitionToDelegatingState(true); } } } break; case MotionEvent.ACTION_UP: { - if (mLastDownEvent == null) { - return; - } - mHandler.removeMessages(MESSAGE_ON_ACTION_TAP_AND_HOLD); if (!mMagnificationController.magnificationRegionContains( event.getX(), event.getY())) { - transitionToDelegatingStateAndClear(); + transitionToDelegatingState(!mShortcutTriggered); return; } + if (mShortcutTriggered) { + clear(); + onActionTap(event, policyFlags); + return; + } + if (mLastDownEvent == null) { + return; + } + mHandler.removeMessages(MESSAGE_ON_ACTION_TAP_AND_HOLD); if (!GestureUtils.isTap(mLastDownEvent, event, mTapTimeSlop, mTapDistanceSlop, 0)) { - transitionToDelegatingStateAndClear(); + transitionToDelegatingState(true); return; } - if (mLastTapUpEvent != null && !GestureUtils.isMultiTap(mLastTapUpEvent, - event, mMultiTapTimeSlop, mMultiTapDistanceSlop, 0)) { - transitionToDelegatingStateAndClear(); + if (mLastTapUpEvent != null && !GestureUtils.isMultiTap( + mLastTapUpEvent, event, mMultiTapTimeSlop, mMultiTapDistanceSlop, 0)) { + transitionToDelegatingState(true); return; } mTapCount++; @@ -655,6 +736,7 @@ class MagnificationGestureHandler implements EventStreamTransformation { @Override public void clear() { + setMagnificationShortcutTriggered(false); mHandler.removeMessages(MESSAGE_ON_ACTION_TAP_AND_HOLD); mHandler.removeMessages(MESSAGE_TRANSITION_TO_DELEGATING_STATE); clearTapDetectionState(); @@ -714,10 +796,12 @@ class MagnificationGestureHandler implements EventStreamTransformation { } } - private void transitionToDelegatingStateAndClear() { + private void transitionToDelegatingState(boolean andClear) { transitionToState(STATE_DELEGATING); sendDelayedMotionEvents(); - clear(); + if (andClear) { + clear(); + } } private void onActionTap(MotionEvent up, int policyFlags) { @@ -820,4 +904,30 @@ class MagnificationGestureHandler implements EventStreamTransformation { mPolicyFlags = 0; } } + + /** + * BroadcastReceiver used to cancel the magnification shortcut when the screen turns off + */ + private static class ScreenStateReceiver extends BroadcastReceiver { + private final Context mContext; + private final MagnificationGestureHandler mGestureHandler; + + public ScreenStateReceiver(Context context, MagnificationGestureHandler gestureHandler) { + mContext = context; + mGestureHandler = gestureHandler; + } + + public void register() { + mContext.registerReceiver(this, new IntentFilter(Intent.ACTION_SCREEN_OFF)); + } + + public void unregister() { + mContext.unregisterReceiver(this); + } + + @Override + public void onReceive(Context context, Intent intent) { + mGestureHandler.setMagnificationShortcutTriggered(false); + } + } } diff --git a/services/autofill/java/com/android/server/autofill/AutoFillManagerService.java b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java index b90a2a2881dc..af1193d7edea 100644 --- a/services/autofill/java/com/android/server/autofill/AutoFillManagerService.java +++ b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java @@ -67,13 +67,13 @@ import java.util.List; * Entry point service for autofill management. * * <p>This service provides the {@link IAutoFillManager} implementation and keeps a list of - * {@link AutoFillManagerServiceImpl} per user; the real work is done by - * {@link AutoFillManagerServiceImpl} itself. + * {@link AutofillManagerServiceImpl} per user; the real work is done by + * {@link AutofillManagerServiceImpl} itself. */ // TODO(b/33197203): Handle removing of packages -public final class AutoFillManagerService extends SystemService { +public final class AutofillManagerService extends SystemService { - private static final String TAG = "AutoFillManagerService"; + private static final String TAG = "AutofillManagerService"; static final String RECEIVER_BUNDLE_EXTRA_SESSIONS = "sessions"; @@ -83,7 +83,7 @@ public final class AutoFillManagerService extends SystemService { private final Object mLock = new Object(); /** - * Cache of {@link AutoFillManagerServiceImpl} per user id. + * Cache of {@link AutofillManagerServiceImpl} per user id. * <p> * It has to be mapped by user id because the same current user could have simultaneous sessions * associated to different user profiles (for example, in a multi-window environment or when @@ -97,7 +97,7 @@ public final class AutoFillManagerService extends SystemService { */ // TODO(b/33197203): Update the above comment @GuardedBy("mLock") - private SparseArray<AutoFillManagerServiceImpl> mServicesCache = new SparseArray<>(); + private SparseArray<AutofillManagerServiceImpl> mServicesCache = new SparseArray<>(); // TODO(b/33197203): set a different max (or disable it) on low-memory devices. private final LocalLog mRequestsHistory = new LocalLog(20); @@ -115,7 +115,7 @@ public final class AutoFillManagerService extends SystemService { } }; - public AutoFillManagerService(Context context) { + public AutofillManagerService(Context context) { super(context); mContext = context; mUi = new AutoFillUI(mContext); @@ -157,10 +157,11 @@ public final class AutoFillManagerService extends SystemService { * * @return service instance. */ - @NonNull AutoFillManagerServiceImpl getServiceForUserLocked(int userId) { - AutoFillManagerServiceImpl service = mServicesCache.get(userId); + @NonNull + AutofillManagerServiceImpl getServiceForUserLocked(int userId) { + AutofillManagerServiceImpl service = mServicesCache.get(userId); if (service == null) { - service = new AutoFillManagerServiceImpl(mContext, mLock, + service = new AutofillManagerServiceImpl(mContext, mLock, mRequestsHistory, userId, mUi); mServicesCache.put(userId, service); } @@ -174,7 +175,7 @@ public final class AutoFillManagerService extends SystemService { final IBinder activityToken = getTopActivityForUser(); if (activityToken != null) { synchronized (mLock) { - final AutoFillManagerServiceImpl service = mServicesCache.get(userId); + final AutofillManagerServiceImpl service = mServicesCache.get(userId); if (service == null) { Log.w(TAG, "handleSaveForUser(): no cached service for userId " + userId); return; @@ -258,7 +259,7 @@ public final class AutoFillManagerService extends SystemService { * Removes a cached service for a given user. */ private void removeCachedServiceLocked(int userId) { - final AutoFillManagerServiceImpl service = mServicesCache.get(userId); + final AutofillManagerServiceImpl service = mServicesCache.get(userId); if (service != null) { mServicesCache.delete(userId); service.destroyLocked(); @@ -269,7 +270,7 @@ public final class AutoFillManagerService extends SystemService { * Updates a cached service for a given user. */ private void updateCachedServiceLocked(int userId) { - AutoFillManagerServiceImpl service = mServicesCache.get(userId); + AutofillManagerServiceImpl service = mServicesCache.get(userId); if (service != null) { service.updateLocked(); } @@ -299,7 +300,7 @@ public final class AutoFillManagerService extends SystemService { @Override public void setAuthenticationResult(Bundle data, IBinder activityToken, int userId) { synchronized (mLock) { - final AutoFillManagerServiceImpl service = getServiceForUserLocked(userId); + final AutofillManagerServiceImpl service = getServiceForUserLocked(userId); service.setAuthenticationResultLocked(data, activityToken); } } @@ -307,7 +308,7 @@ public final class AutoFillManagerService extends SystemService { @Override public void setHasCallback(IBinder activityToken, int userId, boolean hasIt) { synchronized (mLock) { - final AutoFillManagerServiceImpl service = getServiceForUserLocked(userId); + final AutofillManagerServiceImpl service = getServiceForUserLocked(userId); service.setHasCallback(activityToken, hasIt); } } @@ -315,13 +316,13 @@ public final class AutoFillManagerService extends SystemService { @Override public void startSession(IBinder activityToken, IBinder windowToken, IBinder appCallback, AutofillId autofillId, Rect bounds, AutofillValue value, int userId, - boolean hasCallback) { + boolean hasCallback, int flags) { // TODO(b/33197203): make sure it's called by resumed / focused activity synchronized (mLock) { - final AutoFillManagerServiceImpl service = getServiceForUserLocked(userId); + final AutofillManagerServiceImpl service = getServiceForUserLocked(userId); service.startSessionLocked(activityToken, windowToken, appCallback, - autofillId, bounds, value, hasCallback); + autofillId, bounds, value, hasCallback, flags); } } @@ -329,7 +330,7 @@ public final class AutoFillManagerService extends SystemService { public void updateSession(IBinder activityToken, AutofillId id, Rect bounds, AutofillValue value, int flags, int userId) { synchronized (mLock) { - final AutoFillManagerServiceImpl service = mServicesCache.get( + final AutofillManagerServiceImpl service = mServicesCache.get( UserHandle.getCallingUserId()); if (service != null) { service.updateSessionLocked(activityToken, id, bounds, value, flags); @@ -340,7 +341,7 @@ public final class AutoFillManagerService extends SystemService { @Override public void finishSession(IBinder activityToken, int userId) { synchronized (mLock) { - final AutoFillManagerServiceImpl service = mServicesCache.get( + final AutofillManagerServiceImpl service = mServicesCache.get( UserHandle.getCallingUserId()); if (service != null) { service.finishSessionLocked(activityToken); @@ -349,6 +350,17 @@ public final class AutoFillManagerService extends SystemService { } @Override + public void cancelSession(IBinder activityToken, int userId) { + synchronized (mLock) { + final AutofillManagerServiceImpl service = mServicesCache.get( + UserHandle.getCallingUserId()); + if (service != null) { + service.cancelSessionLocked(activityToken); + } + } + } + + @Override public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { if (mContext.checkCallingPermission( Manifest.permission.DUMP) != PackageManager.PERMISSION_GRANTED) { @@ -366,7 +378,7 @@ public final class AutoFillManagerService extends SystemService { pw.println(size); for (int i = 0; i < size; i++) { pw.print("\nService at index "); pw.println(i); - final AutoFillManagerServiceImpl impl = mServicesCache.valueAt(i); + final AutofillManagerServiceImpl impl = mServicesCache.valueAt(i); impl.dumpLocked(" ", pw); } } @@ -379,7 +391,7 @@ public final class AutoFillManagerService extends SystemService { @Override public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err, String[] args, ShellCallback callback, ResultReceiver resultReceiver) { - (new AutoFillManagerServiceShellCommand(AutoFillManagerService.this)).exec( + (new AutofillManagerServiceShellCommand(AutofillManagerService.this)).exec( this, in, out, err, args, callback, resultReceiver); } } diff --git a/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java index e6916239a22e..3e5ad82d0129 100644 --- a/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceImpl.java +++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java @@ -19,8 +19,8 @@ package com.android.server.autofill; import static android.service.autofill.AutofillService.EXTRA_ACTIVITY_TOKEN; import static android.service.voice.VoiceInteractionSession.KEY_RECEIVER_EXTRAS; import static android.service.voice.VoiceInteractionSession.KEY_STRUCTURE; -import static android.view.autofill.AutofillManager.FLAG_FOCUS_GAINED; -import static android.view.autofill.AutofillManager.FLAG_FOCUS_LOST; +import static android.view.autofill.AutofillManager.FLAG_VIEW_ENTERED; +import static android.view.autofill.AutofillManager.FLAG_VIEW_EXITED; import static android.view.autofill.AutofillManager.FLAG_START_SESSION; import static android.view.autofill.AutofillManager.FLAG_VALUE_CHANGED; @@ -60,6 +60,7 @@ import android.service.autofill.SaveInfo; import android.text.TextUtils; import android.util.ArrayMap; import android.util.LocalLog; +import android.util.Log; import android.util.PrintWriterPrinter; import android.util.Slog; import android.view.autofill.AutofillId; @@ -77,13 +78,13 @@ import java.util.Map; import java.util.Map.Entry; /** - * Bridge between the {@code system_server}'s {@link AutoFillManagerService} and the + * Bridge between the {@code system_server}'s {@link AutofillManagerService} and the * app's {@link IAutoFillService} implementation. * */ -final class AutoFillManagerServiceImpl { +final class AutofillManagerServiceImpl { - private static final String TAG = "AutoFillManagerServiceImpl"; + private static final String TAG = "AutofillManagerServiceImpl"; private static final int MSG_SERVICE_SAVE = 1; @@ -169,11 +170,11 @@ final class AutoFillManagerServiceImpl { structure.sanitizeForParceling(true); // TODO(b/33197203): Need to pipe the bundle - session.mRemoteFillService.onFillRequest(structure, null); + session.mRemoteFillService.onFillRequest(structure, null, session.mFlags); } }; - AutoFillManagerServiceImpl(Context context, Object lock, LocalLog requestsHistory, + AutofillManagerServiceImpl(Context context, Object lock, LocalLog requestsHistory, int userId, AutoFillUI ui) { mContext = context; mLock = lock; @@ -240,7 +241,7 @@ final class AutoFillManagerServiceImpl { } /** - * Used by {@link AutoFillManagerServiceShellCommand} to request save for the current top app. + * Used by {@link AutofillManagerServiceShellCommand} to request save for the current top app. */ void requestSaveForUserLocked(IBinder activityToken) { if (!hasService()) { @@ -284,7 +285,8 @@ final class AutoFillManagerServiceImpl { } 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 class AutoFillManagerServiceImpl { final String historyItem = "s=" + mInfo.getServiceInfo().packageName + " u=" + mUserId + " a=" + activityToken - + " i=" + autofillId + " b=" + bounds + " hc=" + hasCallback; + + " i=" + autofillId + " b=" + bounds + " hc=" + hasCallback + " f=" + flags; mRequestsHistory.log(historyItem); // TODO(b/33197203): Handle partitioning @@ -303,7 +305,7 @@ final class AutoFillManagerServiceImpl { } final Session newSession = createSessionByTokenLocked(activityToken, - windowToken, appCallbackToken, hasCallback); + windowToken, appCallbackToken, hasCallback, flags); newSession.updateLocked(autofillId, bounds, value, FLAG_START_SESSION); } @@ -321,10 +323,24 @@ final class AutoFillManagerServiceImpl { session.showSaveLocked(); } + void cancelSessionLocked(IBinder activityToken) { + if (!hasService()) { + return; + } + + final Session session = mSessions.get(activityToken); + if (session == null) { + Slog.w(TAG, "cancelSessionLocked(): no session for " + activityToken); + return; + } + + session.destroyLocked(); + } + 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); /* @@ -457,7 +473,7 @@ final class AutoFillManagerServiceImpl { @Override public String toString() { - return "AutoFillManagerServiceImpl: [userId=" + mUserId + return "AutofillManagerServiceImpl: [userId=" + mUserId + ", component=" + (mInfo != null ? mInfo.getServiceInfo().getComponentName() : null) + "]"; } @@ -610,17 +626,23 @@ final class AutoFillManagerServiceImpl { private AssistStructure mStructure; /** - * Whether the client has an {@link android.view.autofill.AutoFillManager.AutofillCallback}. + * Whether the client has an {@link android.view.autofill.AutofillManager.AutofillCallback}. */ 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 { @@ -788,35 +810,101 @@ final class AutoFillManagerServiceImpl { 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 AutofillId[] requiredIds = saveInfo.getRequiredIds(); + if (requiredIds == null || requiredIds.length == 0) { + Slog.w(TAG, "showSaveLocked(): no required ids on saveInfo"); return; } - final int size = saveInfo.getSavableIds().size(); - for (int i = 0; i < size; i++) { - final AutofillId id = saveInfo.getSavableIds().valueAt(i); + 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(); } /** @@ -899,13 +987,17 @@ final class AutoFillManagerServiceImpl { viewState.mAutofillValue = value; // Update the chooser UI - getUiForShowing().filterFillUi(value.coerceToString()); + if (value.isText()) { + getUiForShowing().filterFillUi(value.getTextValue().toString()); + } else { + getUiForShowing().filterFillUi(null); + } } return; } - if ((flags & FLAG_FOCUS_GAINED) != 0) { + if ((flags & FLAG_VIEW_ENTERED) != 0) { // Remove the UI if the ViewState has changed. if (mCurrentViewState != viewState) { mUi.hideFillUi(mCurrentViewState != null ? mCurrentViewState.mId : null); @@ -923,7 +1015,7 @@ final class AutoFillManagerServiceImpl { return; } - if ((flags & FLAG_FOCUS_LOST) != 0) { + if ((flags & FLAG_VIEW_EXITED) != 0) { if (mCurrentViewState == viewState) { mUi.hideFillUi(viewState.mId); mCurrentViewState = null; @@ -937,13 +1029,9 @@ final class AutoFillManagerServiceImpl { @Override public void onFillReady(ViewState viewState, FillResponse response, Rect bounds, AutofillId filledId, @Nullable AutofillValue value) { - String filterText = ""; - if (value != null) { - // TODO(b/33197203): Handle other AutofillValue types - final CharSequence text = value.getTextValue(); - if (text != null) { - filterText = text.toString(); - } + String filterText = null; + if (value != null && value.isText()) { + filterText = value.getTextValue().toString(); } getUiForShowing().showFillUi(filledId, response, bounds, filterText); @@ -1001,7 +1089,7 @@ final class AutoFillManagerServiceImpl { } CharSequence getServiceName() { - return AutoFillManagerServiceImpl.this.getServiceName(); + return AutofillManagerServiceImpl.this.getServiceName(); } private Intent createAuthFillInIntent(AssistStructure structure) { @@ -1020,6 +1108,7 @@ final class AutoFillManagerServiceImpl { void dumpLocked(String prefix, PrintWriter pw) { pw.print(prefix); pw.print("mActivityToken: "); pw.println(mActivityToken); + pw.print(prefix); pw.print("mFlags: "); pw.println(mFlags); pw.print(prefix); pw.print("mCurrentResponse: "); pw.println(mCurrentResponse); pw.print(prefix); pw.print("mAutoFilledDataset: "); pw.println(mAutoFilledDataset); pw.print(prefix); pw.print("mCurrentViewStates: "); pw.println(mCurrentViewState); diff --git a/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceShellCommand.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceShellCommand.java index 76d9aea73636..80560f18038e 100644 --- a/services/autofill/java/com/android/server/autofill/AutoFillManagerServiceShellCommand.java +++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceShellCommand.java @@ -16,7 +16,7 @@ package com.android.server.autofill; -import static com.android.server.autofill.AutoFillManagerService.RECEIVER_BUNDLE_EXTRA_SESSIONS; +import static com.android.server.autofill.AutofillManagerService.RECEIVER_BUNDLE_EXTRA_SESSIONS; import android.app.ActivityManager; import android.os.Bundle; @@ -30,11 +30,11 @@ import java.util.ArrayList; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; -public final class AutoFillManagerServiceShellCommand extends ShellCommand { +public final class AutofillManagerServiceShellCommand extends ShellCommand { - private final AutoFillManagerService mService; + private final AutofillManagerService mService; - public AutoFillManagerServiceShellCommand(AutoFillManagerService service) { + public AutofillManagerServiceShellCommand(AutofillManagerService service) { mService = service; } diff --git a/services/autofill/java/com/android/server/autofill/RemoteFillService.java b/services/autofill/java/com/android/server/autofill/RemoteFillService.java index 76385b1696ee..b1cc89b5de44 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 @@ final class RemoteFillService implements DeathRecipient { mCallbacks.onServiceDied(this); } - public void onFillRequest(@NonNull AssistStructure structure, @Nullable Bundle extras) { + public void onFillRequest(@NonNull AssistStructure structure, @Nullable Bundle extras, + int flags) { cancelScheduledUnbind(); - final PendingFillRequest request = new PendingFillRequest(structure, extras, this); + final PendingFillRequest request = new PendingFillRequest(structure, extras, this, flags); mHandler.obtainMessageO(MyHandler.MSG_ON_PENDING_REQUEST, request).sendToTarget(); } @@ -182,7 +183,7 @@ final class RemoteFillService implements DeathRecipient { return; } if (!isBound()) { - if (mPendingRequest != null && mPendingRequest != pendingRequest) { + if (mPendingRequest != null) { mPendingRequest.cancel(); } mPendingRequest = pendingRequest; @@ -240,7 +241,7 @@ final class RemoteFillService implements DeathRecipient { } mBinding = false; if (isBound()) { - // TODO(b/33197203, b/35395043): synchronize access instead + // TODO(b/33197203): synchronize access instead? // Need to double check if it's null, since it could be set on onServiceDisconnected() if (mAutoFillService != null) { try { @@ -322,7 +323,7 @@ final class RemoteFillService implements DeathRecipient { } try { - // TODO(b/33197203, b/35395043): synchronize access instead + // TODO(b/33197203): synchronize access instead? // Need to double check if it's null, since it could be set on // onServiceDisconnected() if (mAutoFillService != null) { @@ -337,9 +338,10 @@ final class RemoteFillService implements DeathRecipient { Slog.w(LOG_TAG, "Exception calling onConnected(): " + e); } - if (mPendingRequest != null) { - handlePendingRequest(mPendingRequest); + PendingRequest pendingRequest = mPendingRequest; + mPendingRequest = null; + handlePendingRequest(pendingRequest); } mServiceDied = false; @@ -417,11 +419,13 @@ final class RemoteFillService implements DeathRecipient { 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 @@ -468,7 +472,7 @@ final class RemoteFillService implements DeathRecipient { 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 3eefa7cb6506..c7e59a39aeef 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 @@ public final class AutoFillUI { } 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 4a380c5f19f7..a7d9fe96d0da 100644 --- a/services/autofill/java/com/android/server/autofill/ui/FillUi.java +++ b/services/autofill/java/com/android/server/autofill/ui/FillUi.java @@ -44,6 +44,8 @@ import java.util.ArrayList; final class FillUi { private static final String TAG = "FillUi"; + private static final int VISIBLE_OPTIONS_MAX_COUNT = 3; + interface Callback { void onResponsePicked(@NonNull FillResponse response); void onDatasetPicked(@NonNull Dataset dataset); @@ -56,6 +58,8 @@ final class FillUi { private final @NonNull Callback mCallback; + private final @NonNull ListView mListView; + private final @Nullable ArrayAdapter<ViewItem> mAdapter; private @Nullable String mFilterText; @@ -73,6 +77,9 @@ final class FillUi { mCallback = callback; if (response.getAuthentication() != null) { + mListView = null; + mAdapter = null; + final View content; try { content = response.getPresentation().apply(context, null); @@ -80,7 +87,6 @@ final class FillUi { callback.onCanceled(); Slog.e(TAG, "Error inflating remote views", e); mWindow = null; - mAdapter = null; return; } final int widthMeasureSpec = MeasureSpec.makeMeasureSpec(MeasureSpec.UNSPECIFIED, 0); @@ -89,10 +95,9 @@ final class FillUi { content.setOnClickListener(v -> mCallback.onResponsePicked(response)); mContentWidth = content.getMeasuredWidth(); mContentHeight = content.getMeasuredHeight(); - mAdapter = null; mWindow = new AnchoredWindow(windowToken, content); - mWindow.update(mContentWidth, mContentHeight, mAnchorBounds); + mWindow.show(mContentWidth, mContentHeight, mAnchorBounds); } else { final int datasetCount = response.getDatasets().size(); final ArrayList<ViewItem> items = new ArrayList<>(datasetCount); @@ -108,8 +113,13 @@ final class FillUi { Slog.e(TAG, "Error inflating remote views", e); continue; } - items.add(new ViewItem(dataset, value.coerceToString() - .toLowerCase(), view)); + + String valueText = null; + if (value.isText()) { + valueText = value.getTextValue().toString().toLowerCase(); + } + + items.add(new ViewItem(dataset, valueText, view)); } } @@ -121,16 +131,22 @@ final class FillUi { }; final LayoutInflater inflater = LayoutInflater.from(context); - final ListView listView = (ListView) inflater.inflate( + mListView = (ListView) inflater.inflate( com.android.internal.R.layout.autofill_dataset_picker, null); - listView.setAdapter(mAdapter); - listView.setOnItemClickListener((adapter, view, position, id) -> { + mListView.setAdapter(mAdapter); + mListView.setOnItemClickListener((adapter, view, position, id) -> { final ViewItem vi = mAdapter.getItem(position); mCallback.onDatasetPicked(vi.getDataset()); }); - filter(filterText); - mWindow = new AnchoredWindow(windowToken, listView); + if (filterText == null) { + mFilterText = null; + } else { + mFilterText = filterText.toLowerCase(); + } + + applyNewFilterText(); + mWindow = new AnchoredWindow(windowToken, mListView); } } @@ -138,36 +154,54 @@ final class FillUi { throwIfDestroyed(); if (!mAnchorBounds.equals(anchorBounds)) { mAnchorBounds.set(anchorBounds); - mWindow.update(mContentWidth, mContentHeight, anchorBounds); + mWindow.show(mContentWidth, mContentHeight, anchorBounds); } } - 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; } if (count <= 0) { - mCallback.onCanceled(); + mWindow.hide(); } else { if (updateContentSize()) { - mWindow.update(mContentWidth, mContentHeight, mAnchorBounds); + mWindow.show(mContentWidth, mContentHeight, mAnchorBounds); + } + if (mAdapter.getCount() > VISIBLE_OPTIONS_MAX_COUNT) { + mListView.setVerticalScrollBarEnabled(true); + mListView.onVisibilityAggregated(true); + } else { + mListView.setVerticalScrollBarEnabled(false); } } }); } + 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.destroy(); + mWindow.hide(); mDestroyed = true; } @@ -193,7 +227,7 @@ final class FillUi { final int widthMeasureSpec = MeasureSpec.makeMeasureSpec(MeasureSpec.UNSPECIFIED, 0); final int heightMeasureSpec = MeasureSpec.makeMeasureSpec(MeasureSpec.UNSPECIFIED, 0); - final int itemCount = mAdapter.getCount(); + final int itemCount = Math.min(mAdapter.getCount(), VISIBLE_OPTIONS_MAX_COUNT); for (int i = 0; i < itemCount; i++) { View view = mAdapter.getItem(i).getView(); view.measure(widthMeasureSpec, heightMeasureSpec); @@ -224,7 +258,7 @@ final class FillUi { ViewItem(Dataset dataset, String value, View view) { mDataset = dataset; - mValue = value.toLowerCase(); + mValue = value; mView = view; } @@ -266,7 +300,7 @@ final class FillUi { /** * Hides the window. */ - void destroy() { + void hide() { if (mContentView.isAttachedToWindow()) { mContentView.setOnTouchListener(null); mWm.removeView(mContentView); @@ -283,7 +317,7 @@ final class FillUi { return false; } - public void update(int desiredWidth, int desiredHeight, Rect anchorBounds) { + public void show(int desiredWidth, int desiredHeight, Rect anchorBounds) { final WindowManager.LayoutParams params = new WindowManager.LayoutParams(); params.setTitle("FillUi"); params.token = mActivityToken; @@ -293,7 +327,6 @@ final class FillUi { | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN; - params.format = PixelFormat.TRANSLUCENT; mWm.getDefaultDisplay().getRealSize(mTempPoint); final int screenWidth = mTempPoint.x; diff --git a/services/core/java/com/android/server/AppOpsService.java b/services/core/java/com/android/server/AppOpsService.java index de11f36499c4..a5e357ca91f8 100644 --- a/services/core/java/com/android/server/AppOpsService.java +++ b/services/core/java/com/android/server/AppOpsService.java @@ -235,6 +235,7 @@ public class AppOpsService extends IAppOpsService.Stub { } public AppOpsService(File storagePath, Handler handler) { + LockGuard.installLock(this, LockGuard.INDEX_APP_OPS); mFile = new AtomicFile(storagePath); mHandler = handler; readState(); diff --git a/services/core/java/com/android/server/FontManagerService.java b/services/core/java/com/android/server/FontManagerService.java index 593c3220fdb5..55a945a6fce1 100644 --- a/services/core/java/com/android/server/FontManagerService.java +++ b/services/core/java/com/android/server/FontManagerService.java @@ -71,11 +71,8 @@ public class FontManagerService extends IFontManager.Stub { return null; } - final int size = config.getFamilies().size(); - for (int i = 0; i < size; ++i) { - FontConfig.Family family = config.getFamilies().get(i); - for (int j = 0; j < family.getFonts().size(); ++j) { - FontConfig.Font font = family.getFonts().get(j); + for (FontConfig.Family family : config.getFamilies()) { + for (FontConfig.Font font : family.getFonts()) { File fontFile = new File(font.getFontName()); try { font.setFd(ParcelFileDescriptor.open( diff --git a/services/core/java/com/android/server/LockGuard.java b/services/core/java/com/android/server/LockGuard.java index 3a381ae8bf7c..b7449173be07 100644 --- a/services/core/java/com/android/server/LockGuard.java +++ b/services/core/java/com/android/server/LockGuard.java @@ -53,10 +53,29 @@ import java.io.PrintWriter; * <li>A guarded synchronized block takes 50ns when disabled. * <li>A guarded synchronized block takes 460ns per lock checked when enabled. * </ul> + * <p> + * This class also supports a second simpler mode of operation where well-known + * locks are explicitly registered and checked via indexes. */ public class LockGuard { private static final String TAG = "LockGuard"; + public static final boolean ENABLED = false; + + /** + * Well-known locks ordered by fixed index. Locks with a specific index + * should never be acquired while holding a lock of a lower index. + */ + public static final int INDEX_APP_OPS = 0; + public static final int INDEX_POWER = 1; + public static final int INDEX_USER = 2; + public static final int INDEX_PACKAGES = 3; + public static final int INDEX_STORAGE = 4; + public static final int INDEX_WINDOW = 5; + public static final int INDEX_ACTIVITY = 6; + + private static Object[] sKnownFixed = new Object[INDEX_ACTIVITY + 1]; + private static ArrayMap<Object, LockInfo> sKnown = new ArrayMap<>(0, true); private static class LockInfo { @@ -119,11 +138,41 @@ public class LockGuard { } /** + * Yell if any lower-level locks are being held by the calling thread that + * is about to acquire the given lock. + */ + public static void guard(int index) { + for (int i = 0; i < index; i++) { + final Object lock = sKnownFixed[i]; + if (lock != null && Thread.holdsLock(lock)) { + Slog.w(TAG, "Calling thread " + Thread.currentThread().getName() + " is holding " + + lockToString(i) + " while trying to acquire " + + lockToString(index), new Throwable()); + } + } + } + + /** * Report the given lock with a well-known label. */ - public static void installLock(Object lock, String label) { + public static Object installLock(Object lock, String label) { final LockInfo info = findOrCreateLockInfo(lock); info.label = label; + return lock; + } + + /** + * Report the given lock with a well-known index. + */ + public static Object installLock(Object lock, int index) { + sKnownFixed[index] = lock; + return lock; + } + + public static Object installNewLock(int index) { + final Object lock = new Object(); + installLock(lock, index); + return lock; } private static String lockToString(Object lock) { @@ -135,6 +184,19 @@ public class LockGuard { } } + private static String lockToString(int index) { + switch (index) { + case INDEX_APP_OPS: return "APP_OPS"; + case INDEX_POWER: return "POWER"; + case INDEX_USER: return "USER"; + case INDEX_PACKAGES: return "PACKAGES"; + case INDEX_STORAGE: return "STORAGE"; + case INDEX_WINDOW: return "WINDOW"; + case INDEX_ACTIVITY: return "ACTIVITY"; + default: return Integer.toString(index); + } + } + public static void dump(FileDescriptor fd, PrintWriter pw, String[] args) { for (int i = 0; i < sKnown.size(); i++) { final Object lock = sKnown.keyAt(i); diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java index 32136bbf6a95..3667ecdec472 100644 --- a/services/core/java/com/android/server/StorageManagerService.java +++ b/services/core/java/com/android/server/StorageManagerService.java @@ -308,7 +308,7 @@ class StorageManagerService extends IStorageManager.Stub * <em>Never</em> hold the lock while performing downcalls into vold, since * unsolicited events can suddenly appear to update data structures. */ - private final Object mLock = new Object(); + private final Object mLock = LockGuard.installNewLock(LockGuard.INDEX_STORAGE); /** Set of users that we know are unlocked. */ @GuardedBy("mLock") @@ -3275,8 +3275,13 @@ class StorageManagerService extends IStorageManager.Stub if (uid != Binder.getCallingUid()) { mContext.enforceCallingPermission(android.Manifest.permission.STORAGE_INTERNAL, TAG); } - // TODO: wire up to cache quota once merged - return 64 * TrafficStats.MB_IN_BYTES; + final long token = Binder.clearCallingIdentity(); + final StorageStatsManager stats = mContext.getSystemService(StorageStatsManager.class); + try { + return stats.getCacheQuotaBytes(volumeUuid, uid); + } finally { + Binder.restoreCallingIdentity(token); + } } @Override diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java index 0d5a3e00618b..accae0da8a8e 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 class AccountManagerService public Account[] getAccountsAsUser(String type, int userId, String opPackageName) { int callingUid = Binder.getCallingUid(); mAppOpsManager.checkPackage(callingUid, opPackageName); - return getAccountsAsUser(type, userId, opPackageName /* callingPackage */, -1, + return getAccountsAsUserForPackage(type, userId, opPackageName /* callingPackage */, -1, opPackageName, false /* includeUserManagedNotVisible */); } @NonNull - private Account[] getAccountsAsUser( + private Account[] getAccountsAsUserForPackage( String type, int userId, String callingPackage, @@ -4061,7 +4061,7 @@ public class AccountManagerService return getAccountsInternal( accounts, callingUid, - callingPackage, + opPackageName, visibleAccountTypes, includeUserManagedNotVisible); } finally { @@ -4178,7 +4178,7 @@ public class AccountManagerService throw new SecurityException("getAccountsForPackage() called from unauthorized uid " + callingUid + " with uid=" + uid); } - return getAccountsAsUser(null, UserHandle.getCallingUserId(), packageName, uid, + return getAccountsAsUserForPackage(null, UserHandle.getCallingUserId(), packageName, uid, opPackageName, true /* includeUserManagedNotVisible */); } @@ -4197,11 +4197,10 @@ public class AccountManagerService return EMPTY_ACCOUNT_ARRAY; } if (!UserHandle.isSameApp(callingUid, Process.SYSTEM_UID) - && !isAccountManagedByCaller(type, callingUid, userId)) { - return EMPTY_ACCOUNT_ARRAY; + && (type != null && !isAccountManagedByCaller(type, callingUid, userId))) { + return EMPTY_ACCOUNT_ARRAY; } - - return getAccountsAsUser(type, userId, + return getAccountsAsUserForPackage(type, userId, packageName, packageUid, opPackageName, true /* includeUserManagedNotVisible */); } @@ -4363,7 +4362,7 @@ public class AccountManagerService * security policy. * * In particular we want to make sure that the Authenticator doesn't try to trick users - * into launching aribtrary intents on the device via by tricking to click authenticator + * into launching arbitrary intents on the device via by tricking to click authenticator * supplied entries in the system Settings app. */ protected void checkKeyIntent( @@ -4375,12 +4374,9 @@ public class AccountManagerService ResolveInfo resolveInfo = pm.resolveActivityAsUser(intent, 0, mAccounts.userId); ActivityInfo targetActivityInfo = resolveInfo.activityInfo; int targetUid = targetActivityInfo.applicationInfo.uid; - if (!GrantCredentialsPermissionActivity.class.getName().equals( - targetActivityInfo.getClass().getName()) - && !CantAddAccountActivity.class - .equals(targetActivityInfo.getClass().getName()) - && PackageManager.SIGNATURE_MATCH != pm.checkSignatures(authUid, - targetUid)) { + if (!isExportedSystemActivity(targetActivityInfo) + && (PackageManager.SIGNATURE_MATCH != pm.checkSignatures(authUid, + targetUid))) { String pkgName = targetActivityInfo.packageName; String activityName = targetActivityInfo.name; String tmpl = "KEY_INTENT resolved to an Activity (%s) in a package (%s) that " @@ -4393,6 +4389,13 @@ public class AccountManagerService } } + private boolean isExportedSystemActivity(ActivityInfo activityInfo) { + String className = activityInfo.name; + return "android".equals(activityInfo.packageName) && + (GrantCredentialsPermissionActivity.class.getName().equals(className) + || CantAddAccountActivity.class.getName().equals(className)); + } + private void close() { synchronized (mSessions) { if (mSessions.remove(toString()) == null) { @@ -5371,10 +5374,13 @@ public class AccountManagerService @NonNull private Account[] filterAccounts(UserAccounts accounts, Account[] unfiltered, int callingUid, String callingPackage, boolean includeManagedNotVisible) { - // filter based on visibility. + String visibilityFilterPackage = callingPackage; + if (visibilityFilterPackage == null) { + visibilityFilterPackage = getPackageNameForUid(callingUid); + } Map<Account, Integer> firstPass = new LinkedHashMap<>(); for (Account account : unfiltered) { - int visibility = resolveAccountVisibility(account, callingPackage, accounts); + int visibility = resolveAccountVisibility(account, visibilityFilterPackage, accounts); if ((visibility == AccountManager.VISIBILITY_VISIBLE || visibility == AccountManager.VISIBILITY_USER_MANAGED_VISIBLE) || (includeManagedNotVisible @@ -5394,7 +5400,7 @@ public class AccountManagerService @NonNull private Map<Account, Integer> filterSharedAccounts(UserAccounts userAccounts, @NonNull Map<Account, Integer> unfiltered, int callingUid, - String callingPackage) { + @Nullable String callingPackage) { // first part is to filter shared accounts. // unfiltered type check is not necessary. if (getUserManager() == null || userAccounts == null || userAccounts.userId < 0 @@ -5474,7 +5480,7 @@ public class AccountManagerService */ @NonNull protected Account[] getAccountsFromCacheLocked(UserAccounts userAccounts, String accountType, - int callingUid, String callingPackage, boolean includeManagedNotVisible) { + int callingUid, @Nullable String callingPackage, boolean includeManagedNotVisible) { if (callingPackage == null) { callingPackage = getPackageNameForUid(callingUid); } diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index fef7b513b7bc..55d661cfb534 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -691,6 +691,9 @@ public class ActivityManagerService extends IActivityManager.Stub Process.setThreadPriority(tid, -2); } state.regionCounter++; + if (LockGuard.ENABLED) { + LockGuard.guard(LockGuard.INDEX_ACTIVITY); + } } static void resetPriorityAfterLockedSection() { @@ -2657,6 +2660,7 @@ public class ActivityManagerService extends IActivityManager.Stub // Note: This method is invoked on the main thread but may need to attach various // handlers to other threads. So take care to be explicit about the looper. public ActivityManagerService(Context systemContext) { + LockGuard.installLock(this, LockGuard.INDEX_ACTIVITY); mContext = systemContext; mFactoryTest = FactoryTest.getMode(); mSystemThread = ActivityThread.currentActivityThread(); @@ -6687,16 +6691,15 @@ public class ActivityManagerService extends IActivityManager.Stub : new ProfilerInfo(profileFile, profileFd, samplingInterval, profileAutoStop, profileStreamingOutput); - // We deprecated Build.SERIAL and only apps that target pre NMR1 - // SDK can see it. Since access to the serial is now behind a - // permission we push down the value. + // We deprecated Build.SERIAL and it is not accessible to + // apps that target the v2 security sandbox. Since access to + // the serial is now behind a permission we push down the value. String buildSerial = Build.UNKNOWN; - // TODO: SHTOPSHIP Uncomment the check when clients migrate -// if (appInfo.targetSdkVersion <= Build.VERSION_CODES.N_MR1) { + if (appInfo.targetSandboxVersion != 2) { buildSerial = IDeviceIdentifiersPolicyService.Stub.asInterface( ServiceManager.getService(Context.DEVICE_IDENTIFIERS_SERVICE)) .getSerial(); -// } + } // Check if this is a secondary process that should be incorporated into some // currently active instrumentation. (Note we do this AFTER all of the profiling @@ -7808,9 +7811,14 @@ public class ActivityManagerService extends IActivityManager.Stub r.pictureInPictureArgs.copyOnlySet(args); if (r.getStack().getStackId() == PINNED_STACK_ID) { // If the activity is already in picture-in-picture, update the pinned stack now + // if it is not already expanding to fullscreen. Otherwise, the arguments will + // be used the next time the activity enters PiP final PinnedActivityStack stack = r.getStack(); - stack.setPictureInPictureAspectRatio(r.pictureInPictureArgs.getAspectRatio()); - stack.setPictureInPictureActions(r.pictureInPictureArgs.getActions()); + if (!stack.isBoundsAnimatingToFullscreen()) { + stack.setPictureInPictureAspectRatio( + r.pictureInPictureArgs.getAspectRatio()); + stack.setPictureInPictureActions(r.pictureInPictureArgs.getActions()); + } } logPictureInPictureArgs(args); } @@ -8528,13 +8536,21 @@ public class ActivityManagerService extends IActivityManager.Stub // Third... does the caller itself have permission to access // this uri? - if (UserHandle.getAppId(callingUid) != Process.SYSTEM_UID) { - if (!checkHoldingPermissionsLocked(pm, pi, grantUri, callingUid, modeFlags)) { - // Require they hold a strong enough Uri permission - if (!checkUriPermissionLocked(grantUri, callingUid, modeFlags)) { - throw new SecurityException("Uid " + callingUid - + " does not have permission to uri " + grantUri); - } + final int callingAppId = UserHandle.getAppId(callingUid); + if ((callingAppId == Process.SYSTEM_UID) || (callingAppId == Process.ROOT_UID)) { + if ("com.android.settings.files".equals(grantUri.uri.getAuthority())) { + // Exempted authority for cropping user photos in Settings app + } else { + Slog.w(TAG, "For security reasons, the system cannot issue a Uri permission" + + " grant to " + grantUri + "; use startActivityAsCaller() instead"); + return -1; + } + } + if (!checkHoldingPermissionsLocked(pm, pi, grantUri, callingUid, modeFlags)) { + // Require they hold a strong enough Uri permission + if (!checkUriPermissionLocked(grantUri, callingUid, modeFlags)) { + throw new SecurityException("Uid " + callingUid + + " does not have permission to uri " + grantUri); } } return targetUid; @@ -17996,6 +18012,25 @@ public class ActivityManagerService extends IActivityManager.Stub // BROADCASTS // ========================================================= + private boolean isInstantApp(ProcessRecord record, String callerPackage, int uid) { + if (UserHandle.getAppId(uid) < Process.FIRST_APPLICATION_UID) { + return false; + } + // Easy case -- we have the app's ProcessRecord. + if (record != null) { + return record.info.isInstantApp(); + } + // Otherwise check with PackageManager. + mAppOpsService.checkPackage(uid, callerPackage); + try { + IPackageManager pm = AppGlobals.getPackageManager(); + return pm.isInstantApp(callerPackage, UserHandle.getUserId(uid)); + } catch (RemoteException e) { + Slog.e(TAG, "Error looking up if " + callerPackage + " is an instant app.", e); + return true; + } + } + boolean isPendingBroadcastProcessLocked(int pid) { return mFgBroadcastQueue.isPendingBroadcastProcessLocked(pid) || mBgBroadcastQueue.isPendingBroadcastProcessLocked(pid); @@ -18018,12 +18053,14 @@ public class ActivityManagerService extends IActivityManager.Stub } public Intent registerReceiver(IApplicationThread caller, String callerPackage, - IIntentReceiver receiver, IntentFilter filter, String permission, int userId) { + IIntentReceiver receiver, IntentFilter filter, String permission, int userId, + boolean visibleToInstantApps) { enforceNotIsolatedCaller("registerReceiver"); ArrayList<Intent> stickyIntents = null; ProcessRecord callerApp = null; int callingUid; int callingPid; + boolean instantApp; synchronized(this) { if (caller != null) { callerApp = getRecordForAppLocked(caller); @@ -18047,6 +18084,7 @@ public class ActivityManagerService extends IActivityManager.Stub callingPid = Binder.getCallingPid(); } + instantApp = isInstantApp(callerApp, callerPackage, callingUid); userId = mUserController.handleIncomingUser(callingPid, callingUid, userId, true, ALLOW_FULL_ONLY, "registerReceiver", callerPackage); @@ -18082,6 +18120,11 @@ public class ActivityManagerService extends IActivityManager.Stub // Look for any matching sticky broadcasts... for (int i = 0, N = stickyIntents.size(); i < N; i++) { Intent intent = stickyIntents.get(i); + // Don't provided intents that aren't available to instant apps. + if (instantApp && + (intent.getFlags() & Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS) == 0) { + continue; + } // If intent has scheme "content", it will need to acccess // provider that needs to lock mProviderMap in ActivityThread // and also it may need to wait application response, so we @@ -18140,7 +18183,7 @@ public class ActivityManagerService extends IActivityManager.Stub + " callerPackage is " + callerPackage); } BroadcastFilter bf = new BroadcastFilter(filter, rl, callerPackage, - permission, callingUid, userId); + permission, callingUid, userId, instantApp, visibleToInstantApps); rl.add(bf); if (!bf.debugCheck()) { Slog.w(TAG, "==> For Dynamic broadcast"); @@ -18158,7 +18201,7 @@ public class ActivityManagerService extends IActivityManager.Stub Intent intent = allSticky.get(i); BroadcastQueue queue = broadcastQueueForIntent(intent); BroadcastRecord r = new BroadcastRecord(queue, intent, null, - null, -1, -1, null, null, AppOpsManager.OP_NONE, null, receivers, + null, -1, -1, false, null, null, AppOpsManager.OP_NONE, null, receivers, null, 0, null, null, false, true, true, -1); queue.enqueueParallelBroadcastLocked(r); queue.scheduleBroadcastsLocked(); @@ -18399,6 +18442,12 @@ public class ActivityManagerService extends IActivityManager.Stub boolean ordered, boolean sticky, int callingPid, int callingUid, int userId) { intent = new Intent(intent); + final boolean callerInstantApp = isInstantApp(callerApp, callerPackage, callingUid); + // Instant Apps cannot use FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS + if (callerInstantApp) { + intent.setFlags(intent.getFlags() & ~Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS); + } + // By default broadcasts do not go to stopped apps. intent.addFlags(Intent.FLAG_EXCLUDE_STOPPED_PACKAGES); @@ -18888,9 +18937,9 @@ public class ActivityManagerService extends IActivityManager.Stub } final BroadcastQueue queue = broadcastQueueForIntent(intent); BroadcastRecord r = new BroadcastRecord(queue, intent, callerApp, - callerPackage, callingPid, callingUid, resolvedType, requiredPermissions, - appOp, brOptions, registeredReceivers, resultTo, resultCode, resultData, - resultExtras, ordered, sticky, false, userId); + callerPackage, callingPid, callingUid, callerInstantApp, resolvedType, + requiredPermissions, appOp, brOptions, registeredReceivers, resultTo, + resultCode, resultData, resultExtras, ordered, sticky, false, userId); if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Enqueueing parallel broadcast " + r); final boolean replaced = replacePending && (queue.replaceParallelBroadcastLocked(r) != null); @@ -18984,7 +19033,7 @@ public class ActivityManagerService extends IActivityManager.Stub || resultTo != null) { BroadcastQueue queue = broadcastQueueForIntent(intent); BroadcastRecord r = new BroadcastRecord(queue, intent, callerApp, - callerPackage, callingPid, callingUid, resolvedType, + callerPackage, callingPid, callingUid, callerInstantApp, resolvedType, requiredPermissions, appOp, brOptions, receivers, resultTo, resultCode, resultData, resultExtras, ordered, sticky, false, userId); @@ -22907,16 +22956,9 @@ public class ActivityManagerService extends IActivityManager.Stub } @Override - public void notifyStartingWindowDrawn() { - synchronized (ActivityManagerService.this) { - mStackSupervisor.mActivityMetricsLogger.notifyStartingWindowDrawn(); - } - } - - @Override - public void notifyAppTransitionStarting(int reason) { + public void notifyAppTransitionStarting(SparseIntArray reasons) { synchronized (ActivityManagerService.this) { - mStackSupervisor.mActivityMetricsLogger.notifyTransitionStarting(reason); + mStackSupervisor.mActivityMetricsLogger.notifyTransitionStarting(reasons); } } diff --git a/services/core/java/com/android/server/am/ActivityMetricsLogger.java b/services/core/java/com/android/server/am/ActivityMetricsLogger.java index ff796a549f37..ebbce0227b3e 100644 --- a/services/core/java/com/android/server/am/ActivityMetricsLogger.java +++ b/services/core/java/com/android/server/am/ActivityMetricsLogger.java @@ -1,24 +1,36 @@ package com.android.server.am; +import static android.app.ActivityManager.START_SUCCESS; +import static android.app.ActivityManager.START_TASK_TO_FRONT; import static android.app.ActivityManager.StackId.ASSISTANT_STACK_ID; import static android.app.ActivityManager.StackId.DOCKED_STACK_ID; import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID; import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID; +import static android.app.ActivityManager.StackId.INVALID_STACK_ID; import static android.app.ActivityManager.StackId.PINNED_STACK_ID; - +import static android.app.ActivityManagerInternal.APP_TRANSITION_TIMEOUT; +import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.APP_TRANSITION; +import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.APP_TRANSITION_ACTIVITY_NAME; +import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.APP_TRANSITION_DELAY_MS; +import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.APP_TRANSITION_DEVICE_UPTIME_SECONDS; +import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.APP_TRANSITION_STARTING_WINDOW_DELAY_MS; +import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.APP_TRANSITION_WINDOWS_DRAWN_DELAY_MS; +import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_TRANSITION_COLD_LAUNCH; +import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_TRANSITION_HOT_LAUNCH; +import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_TRANSITION_WARM_LAUNCH; import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM; import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.server.am.ActivityStack.STACK_INVISIBLE; -import android.annotation.Nullable; import android.app.ActivityManager.StackId; import android.content.Context; +import android.metrics.LogMaker; import android.os.SystemClock; import android.util.Slog; +import android.util.SparseArray; +import android.util.SparseIntArray; -import android.metrics.LogMaker; import com.android.internal.logging.MetricsLogger; -import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import java.util.ArrayList; @@ -48,12 +60,27 @@ class ActivityMetricsLogger { private long mLastLogTimeSecs; private final ActivityStackSupervisor mSupervisor; private final Context mContext; + private final MetricsLogger mMetricsLogger = new MetricsLogger(); private long mCurrentTransitionStartTime = INVALID_START_TIME; - private boolean mLoggedWindowsDrawn; - private boolean mLoggedStartingWindowDrawn; + + private int mCurrentTransitionDeviceUptime; + private int mCurrentTransitionDelayMs; private boolean mLoggedTransitionStarting; + private final SparseArray<StackTransitionInfo> mStackTransitionInfo = new SparseArray<>(); + + private final class StackTransitionInfo { + private ActivityRecord launchedActivity; + private int startResult; + private boolean currentTransitionProcessRunning; + private int windowsDrawnDelayMs; + private int startingWindowDelayMs; + private int reason = APP_TRANSITION_TIMEOUT; + private boolean loggedWindowsDrawn; + private boolean loggedStartingWindowDrawn; + } + ActivityMetricsLogger(ActivityStackSupervisor supervisor, Context context) { mLastLogTimeSecs = SystemClock.elapsedRealtime() / 1000; mSupervisor = supervisor; @@ -102,7 +129,9 @@ class ActivityMetricsLogger { * activity. */ void notifyActivityLaunching() { - mCurrentTransitionStartTime = System.currentTimeMillis(); + if (!isAnyTransitionActive()) { + mCurrentTransitionStartTime = System.currentTimeMillis(); + } } /** @@ -118,9 +147,6 @@ class ActivityMetricsLogger { launchedActivity.appInfo.uid) : null; final boolean processRunning = processRecord != null; - final String componentName = launchedActivity != null - ? launchedActivity.shortComponentName - : null; // We consider this a "process switch" if the process of the activity that gets launched // didn't have an activity that was in started state. In this case, we assume that lot @@ -129,7 +155,7 @@ class ActivityMetricsLogger { final boolean processSwitch = processRecord == null || !hasStartedActivity(processRecord, launchedActivity); - notifyActivityLaunched(resultCode, componentName, processRunning, processSwitch); + notifyActivityLaunched(resultCode, launchedActivity, processRunning, processSwitch); } private boolean hasStartedActivity(ProcessRecord record, ActivityRecord launchedActivity) { @@ -151,92 +177,120 @@ class ActivityMetricsLogger { * * @param resultCode one of the ActivityManager.START_* flags, indicating the result of the * launch - * @param componentName the component name of the activity being launched + * @param launchedActivity the activity being launched * @param processRunning whether the process that will contains the activity is already running * @param processSwitch whether the process that will contain the activity didn't have any * activity that was stopped, i.e. the started activity is "switching" * processes */ - private void notifyActivityLaunched(int resultCode, @Nullable String componentName, + private void notifyActivityLaunched(int resultCode, ActivityRecord launchedActivity, boolean processRunning, boolean processSwitch) { - if (resultCode < 0 || componentName == null || !processSwitch) { + // If we are already in an existing transition, only update the activity name, but not the + // other attributes. + final int stackId = launchedActivity != null && launchedActivity.getStack() != null + ? launchedActivity.getStack().mStackId + : INVALID_STACK_ID; + final StackTransitionInfo info = mStackTransitionInfo.get(stackId); + if (launchedActivity != null && info != null) { + info.launchedActivity = launchedActivity; + return; + } + + final boolean otherStacksLaunching = mStackTransitionInfo.size() > 0 && info == null; + if ((resultCode < 0 || launchedActivity == null || !processSwitch + || stackId == INVALID_STACK_ID) && !otherStacksLaunching) { // Failed to launch or it was not a process switch, so we don't care about the timing. - reset(); + reset(true /* abort */); + return; + } else if (otherStacksLaunching) { + // Don't log this stack but continue with the other stacks. return; } - MetricsLogger.action(mContext, MetricsEvent.APP_TRANSITION_COMPONENT_NAME, - componentName); - MetricsLogger.action(mContext, MetricsEvent.APP_TRANSITION_PROCESS_RUNNING, - processRunning); - MetricsLogger.action(mContext, MetricsEvent.APP_TRANSITION_DEVICE_UPTIME_SECONDS, - (int) (SystemClock.uptimeMillis() / 1000)); - - LogMaker builder = new LogMaker(MetricsEvent.APP_TRANSITION); - builder.addTaggedData(MetricsEvent.APP_TRANSITION_COMPONENT_NAME, componentName); - builder.addTaggedData(MetricsEvent.APP_TRANSITION_PROCESS_RUNNING, processRunning ? 1 : 0); - builder.addTaggedData(MetricsEvent.APP_TRANSITION_DEVICE_UPTIME_SECONDS, - SystemClock.uptimeMillis() / 1000); - MetricsLogger.action(builder); + final StackTransitionInfo newInfo = new StackTransitionInfo(); + newInfo.launchedActivity = launchedActivity; + newInfo.currentTransitionProcessRunning = processRunning; + newInfo.startResult = resultCode; + mStackTransitionInfo.append(stackId, newInfo); + mCurrentTransitionDeviceUptime = (int) (SystemClock.uptimeMillis() / 1000); } /** * Notifies the tracker that all windows of the app have been drawn. */ - void notifyWindowsDrawn() { - if (!isTransitionActive() || mLoggedWindowsDrawn) { + void notifyWindowsDrawn(int stackId) { + final StackTransitionInfo info = mStackTransitionInfo.get(stackId); + if (info == null || info.loggedWindowsDrawn) { return; } - MetricsLogger.action(mContext, MetricsEvent.APP_TRANSITION_WINDOWS_DRAWN_DELAY_MS, - calculateCurrentDelay()); - mLoggedWindowsDrawn = true; - if (mLoggedTransitionStarting) { - reset(); + info.windowsDrawnDelayMs = calculateCurrentDelay(); + info.loggedWindowsDrawn = true; + if (allStacksWindowsDrawn() && mLoggedTransitionStarting) { + reset(false /* abort */); } } /** * Notifies the tracker that the starting window was drawn. */ - void notifyStartingWindowDrawn() { - if (!isTransitionActive() || mLoggedStartingWindowDrawn) { + void notifyStartingWindowDrawn(int stackId) { + final StackTransitionInfo info = mStackTransitionInfo.get(stackId); + if (info == null || info.loggedStartingWindowDrawn) { return; } - mLoggedStartingWindowDrawn = true; - MetricsLogger.action(mContext, MetricsEvent.APP_TRANSITION_STARTING_WINDOW_DELAY_MS, - calculateCurrentDelay()); + info.loggedStartingWindowDrawn = true; + info.startingWindowDelayMs = calculateCurrentDelay(); } /** * Notifies the tracker that the app transition is starting. * - * @param reason The reason why we started it. Must be on of - * ActivityManagerInternal.APP_TRANSITION_* reasons. + * @param stackIdReasons A map from stack id to a reason integer, which must be on of + * ActivityManagerInternal.APP_TRANSITION_* reasons. */ - void notifyTransitionStarting(int reason) { - if (!isTransitionActive() || mLoggedTransitionStarting) { + void notifyTransitionStarting(SparseIntArray stackIdReasons) { + if (!isAnyTransitionActive() || mLoggedTransitionStarting) { return; } - MetricsLogger.action(mContext, MetricsEvent.APP_TRANSITION_REASON, reason); - MetricsLogger.action(mContext, MetricsEvent.APP_TRANSITION_DELAY_MS, - calculateCurrentDelay()); + mCurrentTransitionDelayMs = calculateCurrentDelay(); mLoggedTransitionStarting = true; - if (mLoggedWindowsDrawn) { - reset(); + for (int index = stackIdReasons.size() - 1; index >= 0; index--) { + final int stackId = stackIdReasons.keyAt(index); + final StackTransitionInfo info = mStackTransitionInfo.get(stackId); + if (info == null) { + continue; + } + info.reason = stackIdReasons.valueAt(index); } + if (allStacksWindowsDrawn()) { + reset(false /* abort */); + } + } + + private boolean allStacksWindowsDrawn() { + for (int index = mStackTransitionInfo.size() - 1; index >= 0; index--) { + if (!mStackTransitionInfo.valueAt(index).loggedWindowsDrawn) { + return false; + } + } + return true; } - private boolean isTransitionActive() { - return mCurrentTransitionStartTime != INVALID_START_TIME; + private boolean isAnyTransitionActive() { + return mCurrentTransitionStartTime != INVALID_START_TIME + && mStackTransitionInfo.size() > 0; } - private void reset() { + private void reset(boolean abort) { + if (!abort && isAnyTransitionActive()) { + logAppTransitionMultiEvents(); + } mCurrentTransitionStartTime = INVALID_START_TIME; - mLoggedWindowsDrawn = false; + mCurrentTransitionDelayMs = -1; mLoggedTransitionStarting = false; - mLoggedStartingWindowDrawn = false; + mStackTransitionInfo.clear(); } private int calculateCurrentDelay() { @@ -244,4 +298,41 @@ class ActivityMetricsLogger { // Shouldn't take more than 25 days to launch an app, so int is fine here. return (int) (System.currentTimeMillis() - mCurrentTransitionStartTime); } + + private void logAppTransitionMultiEvents() { + for (int index = mStackTransitionInfo.size() - 1; index >= 0; index--) { + final StackTransitionInfo info = mStackTransitionInfo.valueAt(index); + final int type = getTransitionType(info); + if (type == -1) { + return; + } + final LogMaker builder = new LogMaker(APP_TRANSITION); + builder.setPackageName(info.launchedActivity.packageName); + builder.addTaggedData(APP_TRANSITION_ACTIVITY_NAME, info.launchedActivity.info.name); + builder.setType(type); + builder.addTaggedData(APP_TRANSITION_DEVICE_UPTIME_SECONDS, + mCurrentTransitionDeviceUptime); + builder.addTaggedData(APP_TRANSITION_DELAY_MS, mCurrentTransitionDelayMs); + builder.setSubtype(info.reason); + if (info.startingWindowDelayMs != -1) { + builder.addTaggedData(APP_TRANSITION_STARTING_WINDOW_DELAY_MS, + info.startingWindowDelayMs); + } + builder.addTaggedData(APP_TRANSITION_WINDOWS_DRAWN_DELAY_MS, info.windowsDrawnDelayMs); + mMetricsLogger.write(builder); + } + } + + private int getTransitionType(StackTransitionInfo info) { + if (info.currentTransitionProcessRunning) { + if (info.startResult == START_SUCCESS) { + return TYPE_TRANSITION_WARM_LAUNCH; + } else if (info.startResult == START_TASK_TO_FRONT) { + return TYPE_TRANSITION_HOT_LAUNCH; + } + } else if (info.startResult == START_SUCCESS) { + return TYPE_TRANSITION_COLD_LAUNCH; + } + return -1; + } } diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java index 77564bb46af4..2e26bedd50f8 100644 --- a/services/core/java/com/android/server/am/ActivityRecord.java +++ b/services/core/java/com/android/server/am/ActivityRecord.java @@ -23,6 +23,7 @@ import static android.app.ActivityManager.StackId.ASSISTANT_STACK_ID; import static android.app.ActivityManager.StackId.DOCKED_STACK_ID; import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID; import static android.app.ActivityManager.StackId.HOME_STACK_ID; +import static android.app.ActivityManager.StackId.INVALID_STACK_ID; import static android.app.ActivityManager.StackId.PINNED_STACK_ID; import static android.app.AppOpsManager.MODE_ALLOWED; import static android.app.AppOpsManager.OP_ENTER_PICTURE_IN_PICTURE_ON_HIDE; @@ -890,6 +891,10 @@ final class ActivityRecord implements AppWindowContainerListener { return task != null ? (T) task.getStack() : null; } + private int getStackId() { + return getStack() != null ? getStack().mStackId : INVALID_STACK_ID; + } + boolean changeWindowTranslucency(boolean toOpaque) { if (fullscreen == toOpaque) { return false; @@ -1706,9 +1711,16 @@ final class ActivityRecord implements AppWindowContainerListener { } @Override + public void onStartingWindowDrawn() { + synchronized (service) { + mStackSupervisor.mActivityMetricsLogger.notifyStartingWindowDrawn(getStackId()); + } + } + + @Override public void onWindowsDrawn() { synchronized (service) { - mStackSupervisor.mActivityMetricsLogger.notifyWindowsDrawn(); + mStackSupervisor.mActivityMetricsLogger.notifyWindowsDrawn(getStackId()); if (displayStartTime != 0) { reportLaunchTimeLocked(SystemClock.uptimeMillis()); } diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java index 2d91cadbd586..2885e663af3f 100644 --- a/services/core/java/com/android/server/am/ActivityStack.java +++ b/services/core/java/com/android/server/am/ActivityStack.java @@ -66,6 +66,7 @@ import static com.android.server.am.ActivityRecord.APPLICATION_ACTIVITY_TYPE; import static com.android.server.am.ActivityRecord.ASSISTANT_ACTIVITY_TYPE; import static com.android.server.am.ActivityRecord.HOME_ACTIVITY_TYPE; import static com.android.server.am.ActivityStackSupervisor.FindTaskResult; +import static com.android.server.am.ActivityStackSupervisor.ON_TOP; import static com.android.server.am.ActivityStackSupervisor.PRESERVE_WINDOWS; import static com.android.server.wm.AppTransition.TRANSIT_ACTIVITY_CLOSE; import static com.android.server.wm.AppTransition.TRANSIT_ACTIVITY_OPEN; @@ -505,7 +506,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai } onParentChanged(); - activityDisplay.attachStack(this, onTop); + activityDisplay.attachStack(this, findStackInsertIndex(onTop)); if (mStackId == DOCKED_STACK_ID) { // If we created a docked stack we want to resize it so it resizes all other stacks // in the system. @@ -799,16 +800,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai } mStacks.remove(this); - int addIndex = mStacks.size(); - if (addIndex > 0) { - final ActivityStack topStack = mStacks.get(addIndex - 1); - if (StackId.isAlwaysOnTop(topStack.mStackId) && topStack != this) { - // If the top stack is always on top, we move this stack just below it. - addIndex--; - } - } - - mStacks.add(addIndex, this); + mStacks.add(findStackInsertIndex(ON_TOP), this); mStackSupervisor.setFocusStackUnchecked(reason, this); if (task != null) { insertTaskAtTop(task, null); @@ -841,6 +833,25 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai } } + /** + * @return the index to insert a new stack into, taking the always-on-top stacks into account. + */ + private int findStackInsertIndex(boolean onTop) { + if (onTop) { + int addIndex = mStacks.size(); + if (addIndex > 0) { + final ActivityStack topStack = mStacks.get(addIndex - 1); + if (StackId.isAlwaysOnTop(topStack.mStackId) && topStack != this) { + // If the top stack is always on top, we move this stack just below it. + addIndex--; + } + } + return addIndex; + } else { + return 0; + } + } + boolean isFocusable() { if (StackId.canReceiveKeys(mStackId)) { return true; @@ -1573,9 +1584,9 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai // If the assistant stack is focused and translucent, then the docked stack is always // visible - if (topStack.isAssistantStack() - && topStack.isStackTranslucent(starting, DOCKED_STACK_ID)) { - return STACK_VISIBLE; + if (topStack.isAssistantStack()) { + return (topStack.isStackTranslucent(starting, DOCKED_STACK_ID)) ? STACK_VISIBLE + : STACK_INVISIBLE; } // Otherwise, the docked stack is always visible, except in the case where the top @@ -3205,7 +3216,8 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai } // Move the home stack to the top if this stack is fullscreen or there is no // other visible stack. - if (mStackSupervisor.moveHomeStackTaskToTop(myReason)) { + if (task.isOverHomeStack() && + mStackSupervisor.moveHomeStackTaskToTop(myReason)) { // Activity focus was already adjusted. Nothing else to do... return; } diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java index 2ae815e305c1..42efe0b5d8e0 100644 --- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java +++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java @@ -4654,14 +4654,10 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D mDisplayId = display.getDisplayId(); } - void attachStack(ActivityStack stack, boolean onTop) { + void attachStack(ActivityStack stack, int position) { if (DEBUG_STACK) Slog.v(TAG_STACK, "attachStack: attaching " + stack - + " to displayId=" + mDisplayId + " onTop=" + onTop); - if (onTop) { - mStacks.add(stack); - } else { - mStacks.add(0, stack); - } + + " to displayId=" + mDisplayId + " position=" + position); + mStacks.add(position, stack); } void detachStack(ActivityStack stack) { diff --git a/services/core/java/com/android/server/am/BroadcastFilter.java b/services/core/java/com/android/server/am/BroadcastFilter.java index 986b8ea3c41f..f96b06fa798e 100644 --- a/services/core/java/com/android/server/am/BroadcastFilter.java +++ b/services/core/java/com/android/server/am/BroadcastFilter.java @@ -29,15 +29,20 @@ final class BroadcastFilter extends IntentFilter { final String requiredPermission; final int owningUid; final int owningUserId; + final boolean instantApp; + final boolean visibleToInstantApp; BroadcastFilter(IntentFilter _filter, ReceiverList _receiverList, - String _packageName, String _requiredPermission, int _owningUid, int _userId) { + String _packageName, String _requiredPermission, int _owningUid, int _userId, + boolean _instantApp, boolean _visibleToInstantApp) { super(_filter); receiverList = _receiverList; packageName = _packageName; requiredPermission = _requiredPermission; owningUid = _owningUid; owningUserId = _userId; + instantApp = _instantApp; + visibleToInstantApp = _visibleToInstantApp; } public void dump(PrintWriter pw, String prefix) { diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java index c9d19cb31832..278789568b85 100644 --- a/services/core/java/com/android/server/am/BroadcastQueue.java +++ b/services/core/java/com/android/server/am/BroadcastQueue.java @@ -623,6 +623,37 @@ public final class BroadcastQueue { skip = true; } + // Ensure that broadcasts are only sent to other Instant Apps if they are marked as + // visible to Instant Apps. + final boolean visibleToInstantApps = + (r.intent.getFlags() & Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS) != 0; + + if (!skip && !visibleToInstantApps && filter.instantApp + && filter.receiverList.uid != r.callingUid) { + Slog.w(TAG, "Instant App Denial: receiving " + + r.intent.toString() + + " to " + filter.receiverList.app + + " (pid=" + filter.receiverList.pid + + ", uid=" + filter.receiverList.uid + ")" + + " due to sender " + r.callerPackage + + " (uid " + r.callingUid + ")" + + " not specifying FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS"); + skip = true; + } + + if (!skip && !filter.visibleToInstantApp && r.callerInstantApp + && filter.receiverList.uid != r.callingUid) { + Slog.w(TAG, "Instant App Denial: receiving " + + r.intent.toString() + + " to " + filter.receiverList.app + + " (pid=" + filter.receiverList.pid + + ", uid=" + filter.receiverList.uid + ")" + + " requires receiver be visible to instant apps" + + " due to sender " + r.callerPackage + + " (uid " + r.callingUid + ")"); + skip = true; + } + if (skip) { r.delivery[index] = BroadcastRecord.DELIVERY_SKIPPED; return; @@ -1121,6 +1152,30 @@ public final class BroadcastQueue { skip = true; } } + final boolean visibleToInstantApps = + (r.intent.getFlags() & Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS) != 0; + if (!skip && info.activityInfo.applicationInfo.isInstantApp() + && !visibleToInstantApps + && r.callingUid != info.activityInfo.applicationInfo.uid) { + Slog.w(TAG, "Instant App Denial: receiving " + + r.intent + + " to " + component.flattenToShortString() + + " due to sender " + r.callerPackage + + " (uid " + r.callingUid + ")" + + " not specifying FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS"); + skip = true; + } + if (!skip && r.callerInstantApp + && (info.activityInfo.flags & ActivityInfo.FLAG_VISIBLE_TO_EPHEMERAL) == 0 + && r.callingUid != info.activityInfo.applicationInfo.uid) { + Slog.w(TAG, "Instant App Denial: receiving " + + r.intent + + " to " + component.flattenToShortString() + + " requires receiver have visibleToInstantApps set" + + " due to sender " + r.callerPackage + + " (uid " + r.callingUid + ")"); + skip = true; + } if (!skip) { r.manifestCount++; } else { diff --git a/services/core/java/com/android/server/am/BroadcastRecord.java b/services/core/java/com/android/server/am/BroadcastRecord.java index 1e7911a25f50..7764be794c19 100644 --- a/services/core/java/com/android/server/am/BroadcastRecord.java +++ b/services/core/java/com/android/server/am/BroadcastRecord.java @@ -48,6 +48,7 @@ final class BroadcastRecord extends Binder { final String callerPackage; // who sent this final int callingPid; // the pid of who sent this final int callingUid; // the uid of who sent this + final boolean callerInstantApp; // caller is an Instant App? final boolean ordered; // serialize the send to receivers? final boolean sticky; // originated from existing sticky data? final boolean initialSticky; // initial broadcast from register to sticky? @@ -214,11 +215,10 @@ final class BroadcastRecord extends Binder { BroadcastRecord(BroadcastQueue _queue, Intent _intent, ProcessRecord _callerApp, String _callerPackage, - int _callingPid, int _callingUid, String _resolvedType, String[] _requiredPermissions, - int _appOp, BroadcastOptions _options, List _receivers, IIntentReceiver _resultTo, - int _resultCode, String _resultData, Bundle _resultExtras, boolean _serialized, - boolean _sticky, boolean _initialSticky, - int _userId) { + int _callingPid, int _callingUid, boolean _callerInstantApp, String _resolvedType, + String[] _requiredPermissions, int _appOp, BroadcastOptions _options, List _receivers, + IIntentReceiver _resultTo, int _resultCode, String _resultData, Bundle _resultExtras, + boolean _serialized, boolean _sticky, boolean _initialSticky, int _userId) { if (_intent == null) { throw new NullPointerException("Can't construct with a null intent"); } @@ -229,6 +229,7 @@ final class BroadcastRecord extends Binder { callerPackage = _callerPackage; callingPid = _callingPid; callingUid = _callingUid; + callerInstantApp = _callerInstantApp; resolvedType = _resolvedType; requiredPermissions = _requiredPermissions; appOp = _appOp; diff --git a/services/core/java/com/android/server/am/PinnedActivityStack.java b/services/core/java/com/android/server/am/PinnedActivityStack.java index aa7ab15b7749..1708fe503b64 100644 --- a/services/core/java/com/android/server/am/PinnedActivityStack.java +++ b/services/core/java/com/android/server/am/PinnedActivityStack.java @@ -52,4 +52,8 @@ class PinnedActivityStack extends ActivityStack<PinnedStackWindowController> { void setPictureInPictureActions(List<RemoteAction> actions) { getWindowContainerController().setPictureInPictureActions(actions); } + + boolean isBoundsAnimatingToFullscreen() { + return getWindowContainerController().isBoundsAnimatingToFullscreen(); + } } diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java index c0c433e5e1b9..99fe418e66e1 100644 --- a/services/core/java/com/android/server/am/TaskRecord.java +++ b/services/core/java/com/android/server/am/TaskRecord.java @@ -2013,10 +2013,6 @@ final class TaskRecord extends ConfigurationContainer implements TaskWindowConta final Configuration parentConfig = getParent().getConfiguration(); final float density = parentConfig.densityDpi * DisplayMetrics.DENSITY_DEFAULT_SCALE; - // TODO: Orientation? - config.orientation = (config.screenWidthDp <= config.screenHeightDp) - ? Configuration.ORIENTATION_PORTRAIT - : Configuration.ORIENTATION_LANDSCAPE; if (mStack != null) { final StackWindowController stackController = mStack.getWindowContainerController(); stackController.adjustConfigurationForBounds(bounds, insetBounds, @@ -2030,6 +2026,10 @@ final class TaskRecord extends ConfigurationContainer implements TaskWindowConta Slog.wtf(TAG, "Expected stack when caclulating override config"); } + config.orientation = (config.screenWidthDp <= config.screenHeightDp) + ? Configuration.ORIENTATION_PORTRAIT + : Configuration.ORIENTATION_LANDSCAPE; + // For calculating screen layout, we need to use the non-decor inset screen area for the // calculation for compatibility reasons, i.e. screen area without system bars that could // never go away in Honeycomb. diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index 79b99a33d6d3..333d27bd5360 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -1241,6 +1241,13 @@ public class AudioService extends IAudioService.Stub /** @see AudioManager#adjustStreamVolume(int, int, int) */ public void adjustStreamVolume(int streamType, int direction, int flags, String callingPackage) { + if ( streamType == AudioManager.STREAM_ACCESSIBILITY + && (PackageManager.PERMISSION_GRANTED != mContext.checkCallingOrSelfPermission( + android.Manifest.permission.BIND_ACCESSIBILITY_SERVICE))) { + Log.w(TAG, "Trying to call adjustStreamVolume() for a11y without" + + "BIND_ACCESSIBILITY_SERVICE / callingPackage=" + callingPackage); + return; + } adjustStreamVolume(streamType, direction, flags, callingPackage, callingPackage, Binder.getCallingUid()); } @@ -1552,6 +1559,13 @@ public class AudioService extends IAudioService.Stub /** @see AudioManager#setStreamVolume(int, int, int) */ public void setStreamVolume(int streamType, int index, int flags, String callingPackage) { + if ( streamType == AudioManager.STREAM_ACCESSIBILITY + && (PackageManager.PERMISSION_GRANTED != mContext.checkCallingOrSelfPermission( + android.Manifest.permission.BIND_ACCESSIBILITY_SERVICE))) { + Log.w(TAG, "Trying to call setStreamVolume() for a11y without" + + " BIND_ACCESSIBILITY_SERVICE callingPackage=" + callingPackage); + return; + } setStreamVolume(streamType, index, flags, callingPackage, callingPackage, Binder.getCallingUid()); } diff --git a/services/core/java/com/android/server/connectivity/IpConnectivityEventBuilder.java b/services/core/java/com/android/server/connectivity/IpConnectivityEventBuilder.java index d5fa26c49251..81e891a90499 100644 --- a/services/core/java/com/android/server/connectivity/IpConnectivityEventBuilder.java +++ b/services/core/java/com/android/server/connectivity/IpConnectivityEventBuilder.java @@ -151,7 +151,8 @@ final public class IpConnectivityEventBuilder { } private static void setDnsEvent(IpConnectivityEvent out, DnsEvent in) { - IpConnectivityLogClass.DNSLookupBatch dnsLookupBatch = new IpConnectivityLogClass.DNSLookupBatch(); + IpConnectivityLogClass.DNSLookupBatch dnsLookupBatch = + new IpConnectivityLogClass.DNSLookupBatch(); dnsLookupBatch.networkId = netIdOf(in.netId); dnsLookupBatch.eventTypes = bytesToInts(in.eventTypes); dnsLookupBatch.returnCodes = bytesToInts(in.returnCodes); @@ -160,7 +161,8 @@ final public class IpConnectivityEventBuilder { } private static void setIpManagerEvent(IpConnectivityEvent out, IpManagerEvent in) { - IpConnectivityLogClass.IpProvisioningEvent ipProvisioningEvent = new IpConnectivityLogClass.IpProvisioningEvent(); + IpConnectivityLogClass.IpProvisioningEvent ipProvisioningEvent = + new IpConnectivityLogClass.IpProvisioningEvent(); ipProvisioningEvent.ifName = in.ifName; ipProvisioningEvent.eventType = in.eventType; ipProvisioningEvent.latencyMs = (int) in.durationMs; @@ -168,14 +170,16 @@ final public class IpConnectivityEventBuilder { } private static void setIpReachabilityEvent(IpConnectivityEvent out, IpReachabilityEvent in) { - IpConnectivityLogClass.IpReachabilityEvent ipReachabilityEvent = new IpConnectivityLogClass.IpReachabilityEvent(); + IpConnectivityLogClass.IpReachabilityEvent ipReachabilityEvent = + new IpConnectivityLogClass.IpReachabilityEvent(); ipReachabilityEvent.ifName = in.ifName; ipReachabilityEvent.eventType = in.eventType; out.setIpReachabilityEvent(ipReachabilityEvent); } private static void setDefaultNetworkEvent(IpConnectivityEvent out, DefaultNetworkEvent in) { - IpConnectivityLogClass.DefaultNetworkEvent defaultNetworkEvent = new IpConnectivityLogClass.DefaultNetworkEvent(); + IpConnectivityLogClass.DefaultNetworkEvent defaultNetworkEvent = + new IpConnectivityLogClass.DefaultNetworkEvent(); defaultNetworkEvent.networkId = netIdOf(in.netId); defaultNetworkEvent.previousNetworkId = netIdOf(in.prevNetId); defaultNetworkEvent.transportTypes = in.transportTypes; @@ -184,7 +188,8 @@ final public class IpConnectivityEventBuilder { } private static void setNetworkEvent(IpConnectivityEvent out, NetworkEvent in) { - IpConnectivityLogClass.NetworkEvent networkEvent = new IpConnectivityLogClass.NetworkEvent(); + IpConnectivityLogClass.NetworkEvent networkEvent = + new IpConnectivityLogClass.NetworkEvent(); networkEvent.networkId = netIdOf(in.netId); networkEvent.eventType = in.eventType; networkEvent.latencyMs = (int) in.durationMs; @@ -192,7 +197,8 @@ final public class IpConnectivityEventBuilder { } private static void setValidationProbeEvent(IpConnectivityEvent out, ValidationProbeEvent in) { - IpConnectivityLogClass.ValidationProbeEvent validationProbeEvent = new IpConnectivityLogClass.ValidationProbeEvent(); + IpConnectivityLogClass.ValidationProbeEvent validationProbeEvent = + new IpConnectivityLogClass.ValidationProbeEvent(); validationProbeEvent.networkId = netIdOf(in.netId); validationProbeEvent.latencyMs = (int) in.durationMs; validationProbeEvent.probeType = in.probeType; @@ -201,8 +207,10 @@ final public class IpConnectivityEventBuilder { } private static void setApfProgramEvent(IpConnectivityEvent out, ApfProgramEvent in) { - IpConnectivityLogClass.ApfProgramEvent apfProgramEvent = new IpConnectivityLogClass.ApfProgramEvent(); + IpConnectivityLogClass.ApfProgramEvent apfProgramEvent = + new IpConnectivityLogClass.ApfProgramEvent(); apfProgramEvent.lifetime = in.lifetime; + apfProgramEvent.effectiveLifetime = in.actualLifetime; apfProgramEvent.filteredRas = in.filteredRas; apfProgramEvent.currentRas = in.currentRas; apfProgramEvent.programLength = in.programLength; @@ -216,7 +224,8 @@ final public class IpConnectivityEventBuilder { } private static void setApfStats(IpConnectivityEvent out, ApfStats in) { - IpConnectivityLogClass.ApfStatistics apfStatistics = new IpConnectivityLogClass.ApfStatistics(); + IpConnectivityLogClass.ApfStatistics apfStatistics = + new IpConnectivityLogClass.ApfStatistics(); apfStatistics.durationMs = in.durationMs; apfStatistics.receivedRas = in.receivedRas; apfStatistics.matchingRas = in.matchingRas; @@ -224,6 +233,8 @@ final public class IpConnectivityEventBuilder { apfStatistics.zeroLifetimeRas = in.zeroLifetimeRas; apfStatistics.parseErrors = in.parseErrors; apfStatistics.programUpdates = in.programUpdates; + apfStatistics.programUpdatesAll = in.programUpdatesAll; + apfStatistics.programUpdatesAllowingMulticast = in.programUpdatesAllowingMulticast; apfStatistics.maxProgramSize = in.maxProgramSize; out.setApfStatistics(apfStatistics); } diff --git a/services/core/java/com/android/server/fingerprint/AuthenticationClient.java b/services/core/java/com/android/server/fingerprint/AuthenticationClient.java index 14f2e8617081..552f0d1f8b17 100644 --- a/services/core/java/com/android/server/fingerprint/AuthenticationClient.java +++ b/services/core/java/com/android/server/fingerprint/AuthenticationClient.java @@ -83,6 +83,7 @@ public abstract class AuthenticationClient extends ClientMonitor { if (inLockoutMode) { try { Slog.w(TAG, "Forcing lockout (fp driver code should do this!)"); + stop(false); // cancel fingerprint authentication receiver.onError(getHalDeviceId(), FingerprintManager.FINGERPRINT_ERROR_LOCKOUT, 0 /* vendorCode */); } catch (RemoteException e) { @@ -107,7 +108,7 @@ public abstract class AuthenticationClient extends ClientMonitor { public int start() { IBiometricsFingerprint daemon = getFingerprintDaemon(); if (daemon == null) { - Slog.w(TAG, "start authentication: no fingeprintd!"); + Slog.w(TAG, "start authentication: no fingerprint HAL!"); return ERROR_ESRCH; } try { @@ -130,7 +131,7 @@ public abstract class AuthenticationClient extends ClientMonitor { public int stop(boolean initiatedByClient) { IBiometricsFingerprint daemon = getFingerprintDaemon(); if (daemon == null) { - Slog.w(TAG, "stopAuthentication: no fingeprintd!"); + Slog.w(TAG, "stopAuthentication: no fingerprint HAL!"); return ERROR_ESRCH; } try { diff --git a/services/core/java/com/android/server/fingerprint/ClientMonitor.java b/services/core/java/com/android/server/fingerprint/ClientMonitor.java index 43bb21d2b44a..492cd61c9ca9 100644 --- a/services/core/java/com/android/server/fingerprint/ClientMonitor.java +++ b/services/core/java/com/android/server/fingerprint/ClientMonitor.java @@ -28,13 +28,13 @@ import android.util.Slog; import java.util.NoSuchElementException; /** - * Abstract base class for keeping track and dispatching events from fingerprintd to the + * Abstract base class for keeping track and dispatching events from fingerprint HAL to the * the current client. Subclasses are responsible for coordinating the interaction with - * fingerprintd for the specific action (e.g. authenticate, enroll, enumerate, etc.). + * fingerprint HAL for the specific action (e.g. authenticate, enroll, enumerate, etc.). */ public abstract class ClientMonitor implements IBinder.DeathRecipient { protected static final String TAG = FingerprintService.TAG; // TODO: get specific name - protected static final int ERROR_ESRCH = 3; // Likely fingerprintd is dead. See errno.h. + protected static final int ERROR_ESRCH = 3; // Likely fingerprint HAL is dead. See errno.h. protected static final boolean DEBUG = FingerprintService.DEBUG; private IBinder mToken; private IFingerprintServiceReceiver mReceiver; @@ -77,13 +77,13 @@ public abstract class ClientMonitor implements IBinder.DeathRecipient { } /** - * Contacts fingerprintd to start the client. + * Contacts fingerprint HAL to start the client. * @return 0 on succes, errno from driver on failure */ public abstract int start(); /** - * Contacts fingerprintd to stop the client. + * Contacts fingerprint HAL to stop the client. * @param initiatedByClient whether the operation is at the request of a client */ public abstract int stop(boolean initiatedByClient); @@ -108,7 +108,7 @@ public abstract class ClientMonitor implements IBinder.DeathRecipient { public abstract boolean onEnumerationResult(int fingerId, int groupId, int remaining); /** - * Called when we get notification from fingerprintd that an image has been acquired. + * Called when we get notification from fingerprint HAL that an image has been acquired. * Common to authenticate and enroll. * @param acquiredInfo info about the current image acquisition * @return true if client should be removed @@ -131,7 +131,7 @@ public abstract class ClientMonitor implements IBinder.DeathRecipient { } /** - * Called when we get notification from fingerprintd that an error has occurred with the + * Called when we get notification from fingerprint HAL that an error has occurred with the * current operation. Common to authenticate, enroll, enumerate and remove. * @param error * @return true if client should be removed diff --git a/services/core/java/com/android/server/fingerprint/EnrollClient.java b/services/core/java/com/android/server/fingerprint/EnrollClient.java index eddcd5b89865..e1b78a8d3c68 100644 --- a/services/core/java/com/android/server/fingerprint/EnrollClient.java +++ b/services/core/java/com/android/server/fingerprint/EnrollClient.java @@ -80,7 +80,7 @@ public abstract class EnrollClient extends ClientMonitor { public int start() { IBiometricsFingerprint daemon = getFingerprintDaemon(); if (daemon == null) { - Slog.w(TAG, "enroll: no fingeprintd!"); + Slog.w(TAG, "enroll: no fingerprint HAL!"); return ERROR_ESRCH; } final int timeout = (int) (ENROLLMENT_TIMEOUT_MS / MS_PER_SEC); @@ -102,7 +102,7 @@ public abstract class EnrollClient extends ClientMonitor { public int stop(boolean initiatedByClient) { IBiometricsFingerprint daemon = getFingerprintDaemon(); if (daemon == null) { - Slog.w(TAG, "stopEnrollment: no fingeprintd!"); + Slog.w(TAG, "stopEnrollment: no fingerprint HAL!"); return ERROR_ESRCH; } try { diff --git a/services/core/java/com/android/server/fingerprint/EnumerateClient.java b/services/core/java/com/android/server/fingerprint/EnumerateClient.java index 55bf689bd3c6..34f245f19d12 100644 --- a/services/core/java/com/android/server/fingerprint/EnumerateClient.java +++ b/services/core/java/com/android/server/fingerprint/EnumerateClient.java @@ -58,7 +58,7 @@ public abstract class EnumerateClient extends ClientMonitor { public int stop(boolean initiatedByClient) { IBiometricsFingerprint daemon = getFingerprintDaemon(); if (daemon == null) { - Slog.w(TAG, "stopAuthentication: no fingeprintd!"); + Slog.w(TAG, "stopAuthentication: no fingerprint HAL!"); return ERROR_ESRCH; } try { diff --git a/services/core/java/com/android/server/fingerprint/FingerprintService.java b/services/core/java/com/android/server/fingerprint/FingerprintService.java index 0f299426f73f..32621515130d 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 @@ public class FingerprintService extends SystemService implements IHwBinder.Death @Override public void serviceDied(long cookie) { - Slog.v(TAG, "fingerprintd died"); + Slog.v(TAG, "fingerprint HAL died"); MetricsLogger.count(mContext, "fingerprintd_died", 1); synchronized (this) { mDaemon = null; @@ -235,7 +235,7 @@ public class FingerprintService extends SystemService implements IHwBinder.Death try { mHalDeviceId = mDaemon.setNotify(mDaemonCallback); } catch (RemoteException e) { - Slog.e(TAG, "Failed to open fingeprintd HAL", e); + Slog.e(TAG, "Failed to open fingerprint HAL", e); mDaemon = null; // try again later! } @@ -391,7 +391,7 @@ public class FingerprintService extends SystemService implements IHwBinder.Death public long startPreEnroll(IBinder token) { IBiometricsFingerprint daemon = getFingerprintDaemon(); if (daemon == null) { - Slog.w(TAG, "startPreEnroll: no fingeprintd!"); + Slog.w(TAG, "startPreEnroll: no fingerprint HAL!"); return 0; } try { @@ -405,7 +405,7 @@ public class FingerprintService extends SystemService implements IHwBinder.Death public int startPostEnroll(IBinder token) { IBiometricsFingerprint daemon = getFingerprintDaemon(); if (daemon == null) { - Slog.w(TAG, "startPostEnroll: no fingeprintd!"); + Slog.w(TAG, "startPostEnroll: no fingerprint HAL!"); return 0; } try { @@ -417,7 +417,7 @@ public class FingerprintService extends SystemService implements IHwBinder.Death } /** - * Calls fingerprintd to switch states to the new task. If there's already a current task, + * Calls fingerprint HAL to switch states to the new task. If there's already a current task, * it calls cancel() and sets mPendingClient to begin when the current task finishes * ({@link FingerprintManager#FINGERPRINT_ERROR_CANCELED}). * @param newClient the new client that wants to connect @@ -447,7 +447,7 @@ public class FingerprintService extends SystemService implements IHwBinder.Death IFingerprintServiceReceiver receiver, boolean restricted) { IBiometricsFingerprint daemon = getFingerprintDaemon(); if (daemon == null) { - Slog.w(TAG, "startRemove: no fingeprintd!"); + Slog.w(TAG, "startRemove: no fingerprint HAL!"); return; } RemovalClient client = new RemovalClient(getContext(), mHalDeviceId, token, @@ -469,7 +469,7 @@ public class FingerprintService extends SystemService implements IHwBinder.Death IFingerprintServiceReceiver receiver, boolean restricted) { IBiometricsFingerprint daemon = getFingerprintDaemon(); if (daemon == null) { - Slog.w(TAG, "startEnumerate: no fingeprintd!"); + Slog.w(TAG, "startEnumerate: no fingerprint HAL!"); return; } EnumerateClient client = new EnumerateClient(getContext(), mHalDeviceId, token, diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java index d81e092fa57c..a50ec49f644b 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java @@ -72,7 +72,8 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { @ServiceThreadOnly private boolean mArcEstablished = false; - // Stores whether ARC feature is enabled per port. True by default for all the ARC-enabled ports. + // Stores whether ARC feature is enabled per port. + // True by default for all the ARC-enabled ports. private final SparseBooleanArray mArcFeatureEnabled = new SparseBooleanArray(); // Whether System audio mode is activated or not. @@ -80,6 +81,10 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { @GuardedBy("mLock") private boolean mSystemAudioActivated = false; + // Whether the System Audio Control feature is enabled or not. True by default. + @GuardedBy("mLock") + private boolean mSystemAudioControlFeatureEnabled; + // The previous port id (input) before switching to the new one. This is remembered in order to // be able to switch to it upon receiving <Inactive Source> from currently active source. // This remains valid only when the active source was switched via one touch play operation @@ -186,6 +191,8 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { mAutoDeviceOff = mService.readBooleanSetting(Global.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED, true); mAutoWakeup = mService.readBooleanSetting(Global.HDMI_CONTROL_AUTO_WAKEUP_ENABLED, true); + mSystemAudioControlFeatureEnabled = + mService.readBooleanSetting(Global.HDMI_SYSTEM_AUDIO_CONTROL_ENABLED, true); mStandbyHandler = new HdmiCecStandbyModeHandler(service, this); } @@ -778,14 +785,11 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { addAndStartAction(new HotplugDetectionAction(HdmiCecLocalDeviceTv.this)); addAndStartAction(new PowerStatusMonitorAction(HdmiCecLocalDeviceTv.this)); - // If there is AVR, initiate System Audio Auto initiation action, - // which turns on and off system audio according to last system - // audio setting. HdmiDeviceInfo avr = getAvrDeviceInfo(); if (avr != null) { onNewAvrAdded(avr); } else { - setSystemAudioMode(false, true); + setSystemAudioMode(false); } } }); @@ -818,13 +822,13 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { void changeSystemAudioMode(boolean enabled, IHdmiControlCallback callback) { assertRunOnServiceThread(); if (!mService.isControlEnabled() || hasAction(DeviceDiscoveryAction.class)) { - setSystemAudioMode(false, true); + setSystemAudioMode(false); invokeCallback(callback, HdmiControlManager.RESULT_INCORRECT_MODE); return; } HdmiDeviceInfo avr = getAvrDeviceInfo(); if (avr == null) { - setSystemAudioMode(false, true); + setSystemAudioMode(false); invokeCallback(callback, HdmiControlManager.RESULT_TARGET_NOT_AVAILABLE); return; } @@ -834,12 +838,13 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { } // # Seq 25 - void setSystemAudioMode(boolean on, boolean updateSetting) { - HdmiLogger.debug("System Audio Mode change[old:%b new:%b]", mSystemAudioActivated, on); - - if (updateSetting) { - mService.writeBooleanSetting(Global.HDMI_SYSTEM_AUDIO_ENABLED, on); + void setSystemAudioMode(boolean on) { + if (!isSystemAudioControlFeatureEnabled() && on) { + HdmiLogger.debug("Cannot turn on system audio mode " + + "because the System Audio Control feature is disabled."); + return; } + HdmiLogger.debug("System Audio Mode change[old:%b new:%b]", mSystemAudioActivated, on); updateAudioManagerForSystemAudio(on); synchronized (mLock) { if (mSystemAudioActivated != on) { @@ -863,8 +868,21 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { } } - boolean getSystemAudioModeSetting() { - return mService.readBooleanSetting(Global.HDMI_SYSTEM_AUDIO_ENABLED, false); + @ServiceThreadOnly + void setSystemAudioControlFeatureEnabled(boolean enabled) { + assertRunOnServiceThread(); + synchronized (mLock) { + mSystemAudioControlFeatureEnabled = enabled; + } + if (hasSystemAudioDevice()) { + changeSystemAudioMode(enabled, null); + } + } + + boolean isSystemAudioControlFeatureEnabled() { + synchronized (mLock) { + return mSystemAudioControlFeatureEnabled; + } } /** @@ -1112,6 +1130,7 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { @ServiceThreadOnly protected boolean handleSetSystemAudioMode(HdmiCecMessage message) { assertRunOnServiceThread(); + boolean systemAudioStatus = HdmiUtils.parseCommandParamSystemAudioStatus(message); if (!isMessageForSystemAudio(message)) { if (getAvrDeviceInfo() == null) { // AVR may not have been discovered yet. Delay the message processing. @@ -1121,10 +1140,15 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED); } return true; + } else if (systemAudioStatus && !isSystemAudioControlFeatureEnabled()) { + HdmiLogger.debug("Ignoring <Set System Audio Mode> message " + + "because the System Audio Control feature is disabled: %s", message); + mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED); + return true; } removeAction(SystemAudioAutoInitiationAction.class); SystemAudioActionFromAvr action = new SystemAudioActionFromAvr(this, - message.getSource(), HdmiUtils.parseCommandParamSystemAudioStatus(message), null); + message.getSource(), systemAudioStatus, null); addAndStartAction(action); return true; } @@ -1138,7 +1162,7 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { // Ignore this message. return true; } - setSystemAudioMode(HdmiUtils.parseCommandParamSystemAudioStatus(message), true); + setSystemAudioMode(HdmiUtils.parseCommandParamSystemAudioStatus(message)); return true; } @@ -1882,6 +1906,7 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { pw.println("mArcFeatureEnabled: " + mArcFeatureEnabled); pw.println("mSystemAudioActivated: " + mSystemAudioActivated); pw.println("mSystemAudioMute: " + mSystemAudioMute); + pw.println("mSystemAudioControlFeatureEnabled: " + mSystemAudioControlFeatureEnabled); pw.println("mAutoDeviceOff: " + mAutoDeviceOff); pw.println("mAutoWakeup: " + mAutoWakeup); pw.println("mSkipRoutingControl: " + mSkipRoutingControl); diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java index 89b10acd43a3..6864e1edaf08 100644 --- a/services/core/java/com/android/server/hdmi/HdmiControlService.java +++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java @@ -486,7 +486,7 @@ public final class HdmiControlService extends SystemService { Global.HDMI_CONTROL_ENABLED, Global.HDMI_CONTROL_AUTO_WAKEUP_ENABLED, Global.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED, - Global.HDMI_SYSTEM_AUDIO_ENABLED, + Global.HDMI_SYSTEM_AUDIO_CONTROL_ENABLED, Global.MHL_INPUT_SWITCHING_ENABLED, Global.MHL_POWER_CHARGE_ENABLED }; @@ -525,9 +525,9 @@ public final class HdmiControlService extends SystemService { } // No need to propagate to HAL. break; - case Global.HDMI_SYSTEM_AUDIO_ENABLED: - if (isTvDeviceEnabled() && tv().isSystemAudioActivated() != enabled) { - tv().changeSystemAudioMode(enabled, null); + case Global.HDMI_SYSTEM_AUDIO_CONTROL_ENABLED: + if (isTvDeviceEnabled()) { + tv().setSystemAudioControlFeatureEnabled(enabled); } break; case Global.MHL_INPUT_SWITCHING_ENABLED: diff --git a/services/core/java/com/android/server/hdmi/HotplugDetectionAction.java b/services/core/java/com/android/server/hdmi/HotplugDetectionAction.java index e1bcd9952457..7670dccf9c0a 100644 --- a/services/core/java/com/android/server/hdmi/HotplugDetectionAction.java +++ b/services/core/java/com/android/server/hdmi/HotplugDetectionAction.java @@ -262,8 +262,7 @@ final class HotplugDetectionAction extends HdmiCecFeatureAction { return; } - // Turn off system audio mode and update settings. - tv().setSystemAudioMode(false, true); + tv().setSystemAudioMode(false); if (tv().isArcEstablished()) { tv().enableAudioReturnChannel(false); addAndStartAction(new RequestArcTerminationAction(localDevice(), address)); diff --git a/services/core/java/com/android/server/hdmi/SystemAudioAction.java b/services/core/java/com/android/server/hdmi/SystemAudioAction.java index af1a85d31443..449b2085715c 100644 --- a/services/core/java/com/android/server/hdmi/SystemAudioAction.java +++ b/services/core/java/com/android/server/hdmi/SystemAudioAction.java @@ -133,7 +133,7 @@ abstract class SystemAudioAction extends HdmiCecFeatureAction { } protected void setSystemAudioMode(boolean mode) { - tv().setSystemAudioMode(mode, true); + tv().setSystemAudioMode(mode); } @Override diff --git a/services/core/java/com/android/server/hdmi/SystemAudioAutoInitiationAction.java b/services/core/java/com/android/server/hdmi/SystemAudioAutoInitiationAction.java index 01063b757d43..d347a9188dee 100644 --- a/services/core/java/com/android/server/hdmi/SystemAudioAutoInitiationAction.java +++ b/services/core/java/com/android/server/hdmi/SystemAudioAutoInitiationAction.java @@ -50,7 +50,7 @@ final class SystemAudioAutoInitiationAction extends HdmiCecFeatureAction { @Override public void onSendCompleted(int error) { if (error != SendMessageResult.SUCCESS) { - tv().setSystemAudioMode(false, true); + tv().setSystemAudioMode(false); finish(); } } @@ -71,18 +71,24 @@ final class SystemAudioAutoInitiationAction extends HdmiCecFeatureAction { return false; } - private void handleSystemAudioModeStatusMessage(boolean isSystemAudioModeOn) { + private void handleSystemAudioModeStatusMessage(boolean currentSystemAudioMode) { if (!canChangeSystemAudio()) { HdmiLogger.debug("Cannot change system audio mode in auto initiation action."); finish(); return; } - boolean systemAudioModeSetting = tv().getSystemAudioModeSetting(); - if (systemAudioModeSetting && !isSystemAudioModeOn) { - addAndStartAction(new SystemAudioActionFromTv(tv(), mAvrAddress, systemAudioModeSetting, null)); + // If System Audio Control feature is enabled, turn on system audio mode when new AVR is + // detected. Otherwise, turn off system audio mode. + boolean targetSystemAudioMode = tv().isSystemAudioControlFeatureEnabled(); + if (currentSystemAudioMode != targetSystemAudioMode) { + // Start System Audio Control feature actions only if necessary. + addAndStartAction( + new SystemAudioActionFromTv(tv(), mAvrAddress, targetSystemAudioMode, null)); } else { - tv().setSystemAudioMode(isSystemAudioModeOn, true); + // If AVR already has correct system audio mode, update target system audio mode + // immediately rather than starting feature action. + tv().setSystemAudioMode(targetSystemAudioMode); } finish(); } @@ -101,13 +107,15 @@ final class SystemAudioAutoInitiationAction extends HdmiCecFeatureAction { } private void handleSystemAudioModeStatusTimeout() { - if (tv().getSystemAudioModeSetting()) { - if (canChangeSystemAudio()) { - addAndStartAction(new SystemAudioActionFromTv(tv(), mAvrAddress, true, null)); - } - } else { - tv().setSystemAudioMode(false, true); + if (!canChangeSystemAudio()) { + HdmiLogger.debug("Cannot change system audio mode in auto initiation action."); + finish(); + return; } + // If we can't get the current system audio mode status, just try to turn on/off system + // audio mode according to the system audio control setting. + addAndStartAction(new SystemAudioActionFromTv(tv(), mAvrAddress, + tv().isSystemAudioControlFeatureEnabled(), null)); finish(); } diff --git a/services/core/java/com/android/server/notification/RankingHelper.java b/services/core/java/com/android/server/notification/RankingHelper.java index 60970712394a..4b1804c8e7a7 100644 --- a/services/core/java/com/android/server/notification/RankingHelper.java +++ b/services/core/java/com/android/server/notification/RankingHelper.java @@ -510,9 +510,6 @@ public class RankingHelper implements RankingConfig { if (r == null) { throw new IllegalArgumentException("Invalid package"); } - if (IMPORTANCE_NONE == r.importance) { - throw new IllegalArgumentException("Package blocked"); - } if (channel.getGroup() != null && !r.groups.containsKey(channel.getGroup())) { throw new IllegalArgumentException("NotificationChannelGroup doesn't exist"); } diff --git a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java index b085179c7fc5..6af1c3b20df2 100644 --- a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java +++ b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java @@ -288,6 +288,10 @@ final class OverlayManagerServiceImpl { if (overlayPackage == null) { return false; } + // Static overlay is always being enabled. + if (!enable && overlayPackage.isStaticOverlay) { + return false; + } try { final OverlayInfo oi = mSettings.getOverlayInfo(packageName, userId); @@ -333,17 +337,28 @@ final class OverlayManagerServiceImpl { } } + boolean isPackageUpdatableOverlay(@NonNull final String packageName, final int userId) { + final PackageInfo overlayPackage = mPackageManager.getPackageInfo(packageName, userId); + if (overlayPackage == null || overlayPackage.isStaticOverlay) { + return false; + } + return true; + } + boolean setPriority(@NonNull final String packageName, @NonNull final String newParentPackageName, final int userId) { - return mSettings.setPriority(packageName, newParentPackageName, userId); + return isPackageUpdatableOverlay(packageName, userId) && + mSettings.setPriority(packageName, newParentPackageName, userId); } boolean setHighestPriority(@NonNull final String packageName, final int userId) { - return mSettings.setHighestPriority(packageName, userId); + return isPackageUpdatableOverlay(packageName, userId) && + mSettings.setHighestPriority(packageName, userId); } boolean setLowestPriority(@NonNull final String packageName, final int userId) { - return mSettings.setLowestPriority(packageName, userId); + return isPackageUpdatableOverlay(packageName, userId) && + mSettings.setLowestPriority(packageName, userId); } void onDump(@NonNull final PrintWriter pw) { @@ -368,7 +383,9 @@ final class OverlayManagerServiceImpl { private void updateState(@Nullable final PackageInfo targetPackage, @NonNull final PackageInfo overlayPackage, final int userId) throws OverlayManagerSettings.BadKeyException { - if (targetPackage != null) { + // Static RROs targeting to "android", ie framework-res.apk, are handled by native layers. + if (targetPackage != null && + !("android".equals(targetPackage.packageName) && overlayPackage.isStaticOverlay)) { mIdmapManager.createIdmap(targetPackage, overlayPackage, userId); } diff --git a/services/core/java/com/android/server/pm/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/DefaultPermissionGrantPolicy.java index 6365d1581cee..bb7ffda9863d 100644 --- a/services/core/java/com/android/server/pm/DefaultPermissionGrantPolicy.java +++ b/services/core/java/com/android/server/pm/DefaultPermissionGrantPolicy.java @@ -207,9 +207,35 @@ final class DefaultPermissionGrantPolicy { } public void grantDefaultPermissions(int userId) { - grantPermissionsToSysComponentsAndPrivApps(userId); - grantDefaultSystemHandlerPermissions(userId); - grantDefaultPermissionExceptions(userId); + if (mService.hasSystemFeature(PackageManager.FEATURE_EMBEDDED, 0)) { + grantAllRuntimePermissions(userId); + } else { + grantPermissionsToSysComponentsAndPrivApps(userId); + grantDefaultSystemHandlerPermissions(userId); + grantDefaultPermissionExceptions(userId); + } + } + + private void grantRuntimePermissionsForPackageLocked(int userId, PackageParser.Package pkg) { + Set<String> permissions = new ArraySet<>(); + for (String permission : pkg.requestedPermissions) { + BasePermission bp = mService.mSettings.mPermissions.get(permission); + if (bp != null && bp.isRuntime()) { + permissions.add(permission); + } + } + if (!permissions.isEmpty()) { + grantRuntimePermissionsLPw(pkg, permissions, true, userId); + } + } + + private void grantAllRuntimePermissions(int userId) { + Log.i(TAG, "Granting all runtime permissions for user " + userId); + synchronized (mService.mPackages) { + for (PackageParser.Package pkg : mService.mPackages.values()) { + grantRuntimePermissionsForPackageLocked(userId, pkg); + } + } } public void scheduleReadDefaultPermissionExceptions() { @@ -226,18 +252,7 @@ final class DefaultPermissionGrantPolicy { || pkg.requestedPermissions.isEmpty()) { continue; } - Set<String> permissions = new ArraySet<>(); - final int permissionCount = pkg.requestedPermissions.size(); - for (int i = 0; i < permissionCount; i++) { - String permission = pkg.requestedPermissions.get(i); - BasePermission bp = mService.mSettings.mPermissions.get(permission); - if (bp != null && bp.isRuntime()) { - permissions.add(permission); - } - } - if (!permissions.isEmpty()) { - grantRuntimePermissionsLPw(pkg, permissions, true, userId); - } + grantRuntimePermissionsForPackageLocked(userId, pkg); } } } diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java index 71bfa648bd5f..e1426fdcf3ef 100644 --- a/services/core/java/com/android/server/pm/LauncherAppsService.java +++ b/services/core/java/com/android/server/pm/LauncherAppsService.java @@ -245,9 +245,12 @@ public class LauncherAppsService extends SystemService { // STOPSHIP Remove the whitelist. if ("com.google.android.talk".equals(callingPackage) - || "com.google.android.quicksearchbox".equals(callingPackage)) { + || "com.google.android.quicksearchbox".equals(callingPackage) + || "com.google.android.googlequicksearchbox".equals(callingPackage) + ) { return false; } + // STOPSHIP Change it to 'e'. Slog.wtfStack(TAG, message + " by " + callingPackage + " for another profile " + targetUserId + " from " + callingUserId); diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index fd731c323a74..0ec85aa993cb 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -56,7 +56,9 @@ import android.os.Looper; import android.os.Message; import android.os.ParcelFileDescriptor; import android.os.Process; +import android.os.ProxyFileDescriptorCallback; import android.os.RemoteException; +import android.os.RevocableFileDescriptor; import android.os.UserHandle; import android.os.storage.StorageManager; import android.system.ErrnoException; @@ -148,7 +150,9 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { private String mFinalMessage; @GuardedBy("mLock") - private ArrayList<FileBridge> mBridges = new ArrayList<>(); + private final ArrayList<RevocableFileDescriptor> mFds = new ArrayList<>(); + @GuardedBy("mLock") + private final ArrayList<FileBridge> mBridges = new ArrayList<>(); @GuardedBy("mLock") private IPackageInstallObserver2 mRemoteObserver; @@ -430,12 +434,20 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { // Quick sanity check of state, and allocate a pipe for ourselves. We // then do heavy disk allocation outside the lock, but this open pipe // will block any attempted install transitions. + final RevocableFileDescriptor fd; final FileBridge bridge; synchronized (mLock) { assertPreparedAndNotSealed("openWrite"); - bridge = new FileBridge(); - mBridges.add(bridge); + if (PackageInstaller.ENABLE_REVOCABLE_FD) { + fd = new RevocableFileDescriptor(); + bridge = null; + mFds.add(fd); + } else { + fd = null; + bridge = new FileBridge(); + mBridges.add(bridge); + } } try { @@ -468,9 +480,14 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { Libcore.os.lseek(targetFd, offsetBytes, OsConstants.SEEK_SET); } - bridge.setTargetFile(targetFd); - bridge.start(); - return new ParcelFileDescriptor(bridge.getClientSocket()); + if (PackageInstaller.ENABLE_REVOCABLE_FD) { + fd.init(mContext, targetFd); + return fd.getRevocableFileDescriptor(); + } else { + bridge.setTargetFile(targetFd); + bridge.start(); + return new ParcelFileDescriptor(bridge.getClientSocket()); + } } catch (ErrnoException e) { throw e.rethrowAsIOException(); @@ -512,6 +529,11 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { wasSealed = mSealed; if (!mSealed) { // Verify that all writers are hands-off + for (RevocableFileDescriptor fd : mFds) { + if (!fd.isRevoked()) { + throw new SecurityException("Files still open"); + } + } for (FileBridge bridge : mBridges) { if (!bridge.isClosed()) { throw new SecurityException("Files still open"); @@ -1170,6 +1192,9 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { mDestroyed = true; // Force shut down all bridges + for (RevocableFileDescriptor fd : mFds) { + fd.revoke(); + } for (FileBridge bridge : mBridges) { bridge.forceClose(); } @@ -1211,6 +1236,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { pw.printPair("mPermissionsAccepted", mPermissionsAccepted); pw.printPair("mRelinquished", mRelinquished); pw.printPair("mDestroyed", mDestroyed); + pw.printPair("mFds", mFds.size()); pw.printPair("mBridges", mBridges.size()); pw.printPair("mFinalStatus", mFinalStatus); pw.printPair("mFinalMessage", mFinalMessage); diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index e8af3108011b..4ac1cce91402 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -266,6 +266,7 @@ import com.android.server.EventLogTags; import com.android.server.FgThread; import com.android.server.IntentResolver; import com.android.server.LocalServices; +import com.android.server.LockGuard; import com.android.server.ServiceThread; import com.android.server.SystemConfig; import com.android.server.SystemServerInitThreadPool; @@ -398,7 +399,7 @@ public class PackageManagerService extends IPackageManager.Stub { private static final boolean HIDE_EPHEMERAL_APIS = false; private static final boolean ENABLE_FREE_CACHE_V2 = - SystemProperties.getBoolean("fw.free_cache_v2", false); + SystemProperties.getBoolean("fw.free_cache_v2", true); private static final int RADIO_UID = Process.PHONE_UID; private static final int LOG_UID = Process.LOG_UID; @@ -2212,6 +2213,7 @@ public class PackageManagerService extends IPackageManager.Stub { public PackageManagerService(Context context, Installer installer, boolean factoryTest, boolean onlyCore) { + LockGuard.installLock(mPackages, LockGuard.INDEX_PACKAGES); Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "create package manager"); EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_START, SystemClock.uptimeMillis()); @@ -10563,9 +10565,9 @@ public class PackageManagerService extends IPackageManager.Stub { ps.pkg.applicationInfo.primaryCpuAbi = adjustedAbi; Slog.i(TAG, "Adjusting ABI for " + ps.name + " to " + adjustedAbi + " (requirer=" - + (requirer == null ? "null" : requirer.pkg.packageName) + + (requirer != null ? requirer.pkg : "null") + ", scannedPackage=" - + (scannedPackage != null ? scannedPackage.packageName : "null") + + (scannedPackage != null ? scannedPackage : "null") + ")"); try { mInstaller.rmdex(ps.codePathString, @@ -12188,6 +12190,11 @@ public class PackageManagerService extends IPackageManager.Stub { if (!isInstantApp && userState.instantApp) { return null; } + // throw out instant app filters if updates are available; will trigger + // instant app resolution + if (userState.instantApp && ps.isUpdateAvailable()) { + return null; + } final ResolveInfo res = new ResolveInfo(); res.activityInfo = ai; if ((mFlags&PackageManager.GET_RESOLVED_FILTER) != 0) { @@ -16883,6 +16890,7 @@ public class PackageManagerService extends IPackageManager.Stub { final PackageSetting ps = mSettings.mPackages.get(pkgName); if (ps != null) { res.newUsers = ps.queryInstalledUsers(sUserManager.getUserIds(), true); + ps.setUpdateAvailable(false /*updateAvailable*/); } final int childCount = (pkg.childPackages != null) ? pkg.childPackages.size() : 0; @@ -19672,6 +19680,17 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName()); } @Override + public void setUpdateAvailable(String packageName, boolean updateAvailable) { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.INSTALL_PACKAGES, null); + synchronized (mPackages) { + final PackageSetting pkgSetting = mSettings.mPackages.get(packageName); + if (pkgSetting != null) { + pkgSetting.setUpdateAvailable(updateAvailable); + } + } + } + + @Override public void setComponentEnabledSetting(ComponentName componentName, int newState, int flags, int userId) { if (!sUserManager.exists(userId)) return; diff --git a/services/core/java/com/android/server/pm/PackageSettingBase.java b/services/core/java/com/android/server/pm/PackageSettingBase.java index b9c43da77916..dfed72fa9b61 100644 --- a/services/core/java/com/android/server/pm/PackageSettingBase.java +++ b/services/core/java/com/android/server/pm/PackageSettingBase.java @@ -141,6 +141,8 @@ abstract class PackageSettingBase extends SettingBase { String volumeUuid; /** The category of this app, as hinted by the installer */ int categoryHint = ApplicationInfo.CATEGORY_UNDEFINED; + /** Whether or not an update is available. Ostensibly only for instant apps. */ + boolean updateAvailable; IntentFilterVerificationInfo verificationInfo; @@ -219,6 +221,14 @@ abstract class PackageSettingBase extends SettingBase { timeStamp = newStamp; } + public void setUpdateAvailable(boolean updateAvailable) { + this.updateAvailable = updateAvailable; + } + + public boolean isUpdateAvailable() { + return updateAvailable; + } + /** * Makes a shallow copy of the given package settings. * @@ -268,6 +278,7 @@ abstract class PackageSettingBase extends SettingBase { usesStaticLibrariesVersions = orig.usesStaticLibrariesVersions != null ? Arrays.copyOf(orig.usesStaticLibrariesVersions, orig.usesStaticLibrariesVersions.length) : null; + updateAvailable = orig.updateAvailable; } private PackageUserState modifyUserState(int userId) { diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java index 570b31ffa7b5..7bd34246fe02 100644 --- a/services/core/java/com/android/server/pm/Settings.java +++ b/services/core/java/com/android/server/pm/Settings.java @@ -2831,6 +2831,9 @@ final class Settings { if (pkg.parentPackageName != null) { serializer.attribute(null, "parentPackageName", pkg.parentPackageName); } + if (pkg.updateAvailable) { + serializer.attribute(null, "updateAvailable", "true"); + } writeChildPackagesLPw(serializer, pkg.childPackageNames); @@ -3698,6 +3701,7 @@ final class Settings { String isOrphaned = null; String volumeUuid = null; String categoryHintString = null; + String updateAvailable = null; int categoryHint = ApplicationInfo.CATEGORY_UNDEFINED; String uidError = null; int pkgFlags = 0; @@ -3726,6 +3730,7 @@ final class Settings { primaryCpuAbiString = parser.getAttributeValue(null, "primaryCpuAbi"); secondaryCpuAbiString = parser.getAttributeValue(null, "secondaryCpuAbi"); cpuAbiOverrideString = parser.getAttributeValue(null, "cpuAbiOverride"); + updateAvailable = parser.getAttributeValue(null, "updateAvailable"); if (primaryCpuAbiString == null && legacyCpuAbiString != null) { primaryCpuAbiString = legacyCpuAbiString; @@ -3905,6 +3910,7 @@ final class Settings { packageSetting.legacyNativeLibraryPathString = legacyNativeLibraryPathStr; packageSetting.primaryCpuAbiString = primaryCpuAbiString; packageSetting.secondaryCpuAbiString = secondaryCpuAbiString; + packageSetting.updateAvailable = "true".equals(updateAvailable); // Handle legacy string here for single-user mode final String enabledStr = parser.getAttributeValue(null, ATTR_ENABLED); if (enabledStr != null) { diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java index 7885748fdf13..21fe5ba6cfba 100644 --- a/services/core/java/com/android/server/pm/ShortcutPackage.java +++ b/services/core/java/com/android/server/pm/ShortcutPackage.java @@ -720,7 +720,11 @@ class ShortcutPackage extends ShortcutPackageItem { // Disable dynamic shortcuts whose target activity is gone. if (si.isDynamic()) { - if (!s.injectIsMainActivity(si.getActivity(), getPackageUserId())) { + if (si.getActivity() == null) { + // Note if it's dynamic, it must have a target activity, but b/36228253. + s.wtf("null activity detected."); + // TODO Maybe remove it? + } else if (!s.injectIsMainActivity(si.getActivity(), getPackageUserId())) { Slog.w(TAG, String.format( "%s is no longer main activity. Disabling shorcut %s.", getPackageName(), si.getId())); @@ -931,6 +935,10 @@ class ShortcutPackage extends ShortcutPackageItem { } final ComponentName activity = si.getActivity(); + if (activity == null) { + mShortcutUser.mService.wtf("null activity detected."); + continue; + } ArrayList<ShortcutInfo> list = activitiesToShortcuts.get(activity); if (list == null) { diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java index 8998212039e9..ef46baebfd0e 100644 --- a/services/core/java/com/android/server/pm/ShortcutService.java +++ b/services/core/java/com/android/server/pm/ShortcutService.java @@ -3237,6 +3237,10 @@ public class ShortcutService extends IShortcutService.Stub { boolean injectIsMainActivity(@NonNull ComponentName activity, int userId) { final long start = injectElapsedRealtime(); try { + if (activity == null) { + wtf("null activity detected"); + return false; + } if (DUMMY_MAIN_ACTIVITY.equals(activity.getClassName())) { return true; } diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index 627fa545bf58..b9fcf4e39203 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -94,6 +94,7 @@ import com.android.internal.util.Preconditions; import com.android.internal.util.XmlUtils; import com.android.internal.widget.LockPatternUtils; import com.android.server.LocalServices; +import com.android.server.LockGuard; import com.android.server.SystemService; import com.android.server.am.UserState; import com.android.server.storage.DeviceStorageMonitorInternal; @@ -227,7 +228,7 @@ public class UserManagerService extends IUserManager.Stub { private final Object mPackagesLock; private final UserDataPreparer mUserDataPreparer; // Short-term lock for internal state, when interaction/sync with PM is not required - private final Object mUsersLock = new Object(); + private final Object mUsersLock = LockGuard.installNewLock(LockGuard.INDEX_USER); private final Object mRestrictionsLock = new Object(); private final Handler mHandler; diff --git a/services/core/java/com/android/server/pm/dex/DexManager.java b/services/core/java/com/android/server/pm/dex/DexManager.java index 755c486d1be5..83dd392988bf 100644 --- a/services/core/java/com/android/server/pm/dex/DexManager.java +++ b/services/core/java/com/android/server/pm/dex/DexManager.java @@ -432,7 +432,7 @@ public class DexManager { // Ignore framework code. // TODO(calin): is there a better way to detect it? if (dexPath.startsWith("/system/framework/")) { - new DexSearchResult("framework", DEX_SEARCH_NOT_FOUND); + return new DexSearchResult("framework", DEX_SEARCH_NOT_FOUND); } // First, check if the package which loads the dex file actually owns it. diff --git a/services/core/java/com/android/server/policy/AccessibilityShortcutController.java b/services/core/java/com/android/server/policy/AccessibilityShortcutController.java index eec1fefde568..cd55f50d9b4b 100644 --- a/services/core/java/com/android/server/policy/AccessibilityShortcutController.java +++ b/services/core/java/com/android/server/policy/AccessibilityShortcutController.java @@ -29,6 +29,7 @@ import android.media.Ringtone; import android.media.RingtoneManager; import android.os.Handler; import android.os.UserHandle; +import android.os.Vibrator; import android.provider.Settings; import android.text.TextUtils; import android.util.Slog; @@ -48,6 +49,11 @@ import static android.view.WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG; */ public class AccessibilityShortcutController { private static final String TAG = "AccessibilityShortcutController"; + private static final AudioAttributes VIBRATION_ATTRIBUTES = new AudioAttributes.Builder() + .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) + .setUsage(AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY) + .build(); + private final Context mContext; private AlertDialog mAlertDialog; @@ -100,6 +106,8 @@ public class AccessibilityShortcutController { final int userId = ActivityManager.getCurrentUser(); final int dialogAlreadyShown = Settings.Secure.getIntForUser( cr, Settings.Secure.ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 0, userId); + + // Play a notification tone final Ringtone tone = RingtoneManager.getRingtone(mContext, Settings.System.DEFAULT_NOTIFICATION_URI); if (tone != null) { @@ -108,6 +116,18 @@ public class AccessibilityShortcutController { .build()); tone.play(); } + + // Play a notification vibration + Vibrator vibrator = (Vibrator) mContext.getSystemService(Context.VIBRATOR_SERVICE); + if ((vibrator != null) && vibrator.hasVibrator()) { + // Don't check if haptics are disabled, as we need to alert the user that their + // way of interacting with the phone may change if they activate the shortcut + long[] vibePattern = PhoneWindowManager.getLongIntArray(mContext.getResources(), + R.array.config_safeModeDisabledVibePattern); + vibrator.vibrate(vibePattern, -1, VIBRATION_ATTRIBUTES); + } + + if (dialogAlreadyShown == 0) { // The first time, we show a warning rather than toggle the service to give the user a // chance to turn off this feature before stuff gets enabled. diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index 8a597267aab3..548fa1ec6d75 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -1806,7 +1806,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { mWakeGestureListener = new MyWakeGestureListener(mContext, mHandler); mOrientationListener = new MyOrientationListener(mContext, mHandler); try { - mOrientationListener.setCurrentRotation(windowManager.getRotation()); + mOrientationListener.setCurrentRotation(windowManager.getDefaultDisplayRotation()); } catch (RemoteException ex) { } mSettingsObserver = new SettingsObserver(mHandler); mSettingsObserver.observe(); diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java index 24f6f896325b..4f67e8ce2d15 100644 --- a/services/core/java/com/android/server/power/PowerManagerService.java +++ b/services/core/java/com/android/server/power/PowerManagerService.java @@ -76,6 +76,7 @@ import com.android.internal.hardware.AmbientDisplayConfiguration; import com.android.internal.os.BackgroundThread; import com.android.internal.util.ArrayUtils; import com.android.server.EventLogTags; +import com.android.server.LockGuard; import com.android.server.RescueParty; import com.android.server.ServiceThread; import com.android.server.SystemService; @@ -210,7 +211,7 @@ public final class PowerManagerService extends SystemService private DreamManagerInternal mDreamManager; private Light mAttentionLight; - private final Object mLock = new Object(); + private final Object mLock = LockGuard.installNewLock(LockGuard.INDEX_POWER); // A bitfield that indicates what parts of the power state have // changed and need to be recalculated. diff --git a/services/core/java/com/android/server/storage/CacheQuotaStrategy.java b/services/core/java/com/android/server/storage/CacheQuotaStrategy.java index c06439278954..7720e2444133 100644 --- a/services/core/java/com/android/server/storage/CacheQuotaStrategy.java +++ b/services/core/java/com/android/server/storage/CacheQuotaStrategy.java @@ -41,6 +41,7 @@ import android.os.RemoteException; import android.os.UserHandle; import android.os.UserManager; import android.text.format.DateUtils; +import android.util.ArrayMap; import android.util.Pair; import android.util.Slog; import android.util.Xml; @@ -64,6 +65,7 @@ import java.io.InputStream; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; +import java.util.Map; /** * CacheQuotaStrategy is a strategy for determining cache quotas using usage stats and foreground @@ -85,15 +87,18 @@ public class CacheQuotaStrategy implements RemoteCallback.OnResultListener { private final Context mContext; private final UsageStatsManagerInternal mUsageStats; private final Installer mInstaller; + private final Map<String, Map<Integer, Long>> mQuotaMap; private ServiceConnection mServiceConnection; private ICacheQuotaService mRemoteService; private AtomicFile mPreviousValuesFile; public CacheQuotaStrategy( - Context context, UsageStatsManagerInternal usageStatsManager, Installer installer) { + Context context, UsageStatsManagerInternal usageStatsManager, Installer installer, + Map<String, Map<Integer, Long>> quotaMap) { mContext = Preconditions.checkNotNull(context); mUsageStats = Preconditions.checkNotNull(usageStatsManager); mInstaller = Preconditions.checkNotNull(installer); + mQuotaMap = Preconditions.checkNotNull(quotaMap); mPreviousValuesFile = new AtomicFile(new File( new File(Environment.getDataDirectory(), "system"), "cachequota.xml")); } @@ -221,6 +226,9 @@ public class CacheQuotaStrategy implements RemoteCallback.OnResultListener { mInstaller.setAppQuota(request.getVolumeUuid(), UserHandle.getUserId(uid), UserHandle.getAppId(uid), proposedQuota); + insertIntoQuotaMap(request.getVolumeUuid(), + UserHandle.getUserId(uid), + UserHandle.getAppId(uid), proposedQuota); } catch (Installer.InstallerException ex) { Slog.w(TAG, "Failed to set cache quota for " + request.getUid(), @@ -231,6 +239,15 @@ public class CacheQuotaStrategy implements RemoteCallback.OnResultListener { disconnectService(); } + private void insertIntoQuotaMap(String volumeUuid, int userId, int appId, long quota) { + Map<Integer, Long> volumeMap = mQuotaMap.get(volumeUuid); + if (volumeMap == null) { + volumeMap = new ArrayMap<>(); + mQuotaMap.put(volumeUuid, volumeMap); + } + volumeMap.put(UserHandle.getUid(userId, appId), quota); + } + private void disconnectService() { if (mServiceConnection != null) { mContext.unbindService(mServiceConnection); diff --git a/services/core/java/com/android/server/tv/TvInputHardwareManager.java b/services/core/java/com/android/server/tv/TvInputHardwareManager.java index 8043c651751c..08eca73e1702 100644 --- a/services/core/java/com/android/server/tv/TvInputHardwareManager.java +++ b/services/core/java/com/android/server/tv/TvInputHardwareManager.java @@ -17,6 +17,7 @@ package com.android.server.tv; import static android.media.tv.TvInputManager.INPUT_STATE_CONNECTED; +import static android.media.tv.TvInputManager.INPUT_STATE_CONNECTED_STANDBY; import static android.media.tv.TvInputManager.INPUT_STATE_DISCONNECTED; import android.content.BroadcastReceiver; @@ -109,7 +110,6 @@ class TvInputHardwareManager implements TvInputHal.Callback { private int mCurrentIndex = 0; private int mCurrentMaxIndex = 0; - // TODO: Should handle STANDBY case. private final SparseBooleanArray mHdmiStateMap = new SparseBooleanArray(); private final List<Message> mPendingHdmiDeviceEvents = new LinkedList<>(); @@ -209,11 +209,13 @@ class TvInputHardwareManager implements TvInputHal.Callback { + deviceId); return; } + int previousConfigsLength = connection.getConfigsLengthLocked(); connection.updateConfigsLocked(configs); String inputId = mHardwareInputIdMap.get(deviceId); - if (inputId != null) { + if (inputId != null + && (previousConfigsLength == 0) != (connection.getConfigsLengthLocked() == 0)) { mHandler.obtainMessage(ListenerHandler.STATE_CHANGED, - convertConnectedToState(configs.length > 0), 0, inputId).sendToTarget(); + connection.getInputStateLocked(), 0, inputId).sendToTarget(); } ITvInputHardwareCallback callback = connection.getCallbackLocked(); if (callback != null) { @@ -263,14 +265,6 @@ class TvInputHardwareManager implements TvInputHal.Callback { || connectionCallingUid != callingUid || connectionResolvedUserId != resolvedUserId; } - private int convertConnectedToState(boolean connected) { - if (connected) { - return INPUT_STATE_CONNECTED; - } else { - return INPUT_STATE_DISCONNECTED; - } - } - public void addHardwareInput(int deviceId, TvInputInfo info) { synchronized (mLock) { String oldInputId = mHardwareInputIdMap.get(deviceId); @@ -293,18 +287,22 @@ class TvInputHardwareManager implements TvInputHal.Callback { } String inputId = mHardwareInputIdMap.get(hardwareInfo.getDeviceId()); if (inputId != null && inputId.equals(info.getId())) { - mHandler.obtainMessage(ListenerHandler.STATE_CHANGED, - convertConnectedToState(mHdmiStateMap.valueAt(i)), 0, - inputId).sendToTarget(); + // No HDMI hotplug does not necessarily mean disconnected, as old devices may + // not report hotplug state correctly. Using INPUT_STATE_CONNECTED_STANDBY to + // denote unknown state. + int state = mHdmiStateMap.valueAt(i) + ? INPUT_STATE_CONNECTED + : INPUT_STATE_CONNECTED_STANDBY; + mHandler.obtainMessage( + ListenerHandler.STATE_CHANGED, state, 0, inputId).sendToTarget(); return; } } - // For the rest of the devices, we can tell by the number of available streams. + // For the rest of the devices, we can tell by the cable connection status. Connection connection = mConnections.get(deviceId); if (connection != null) { mHandler.obtainMessage(ListenerHandler.STATE_CHANGED, - convertConnectedToState(connection.getConfigsLocked().length > 0), 0, - info.getId()).sendToTarget(); + connection.getInputStateLocked(), 0, info.getId()).sendToTarget(); } } } @@ -716,6 +714,26 @@ class TvInputHardwareManager implements TvInputHal.Callback { + ", mResolvedUserId: " + mResolvedUserId + " }"; } + + private int getConfigsLengthLocked() { + return mConfigs == null ? 0 : mConfigs.length; + } + + private int getInputStateLocked() { + int configsLength = getConfigsLengthLocked(); + if (configsLength > 0) { + return INPUT_STATE_CONNECTED; + } + switch (mHardwareInfo.getCableConnectionStatus()) { + case TvInputHardwareInfo.CABLE_CONNECTION_STATUS_CONNECTED: + return INPUT_STATE_CONNECTED; + case TvInputHardwareInfo.CABLE_CONNECTION_STATUS_DISCONNECTED: + return INPUT_STATE_DISCONNECTED; + case TvInputHardwareInfo.CABLE_CONNECTION_STATUS_UNKNOWN: + default: + return INPUT_STATE_CONNECTED_STANDBY; + } + } } private class TvInputHardwareImpl extends ITvInputHardware.Stub { @@ -1199,8 +1217,14 @@ class TvInputHardwareManager implements TvInputHal.Callback { if (inputId == null) { return; } - mHandler.obtainMessage(ListenerHandler.STATE_CHANGED, - convertConnectedToState(event.isConnected()), 0, inputId).sendToTarget(); + // No HDMI hotplug does not necessarily mean disconnected, as old devices may + // not report hotplug state correctly. Using INPUT_STATE_CONNECTED_STANDBY to + // denote unknown state. + int state = event.isConnected() + ? INPUT_STATE_CONNECTED + : INPUT_STATE_CONNECTED_STANDBY; + mHandler.obtainMessage( + ListenerHandler.STATE_CHANGED, state, 0, inputId).sendToTarget(); } } } diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java index e0261304f0f0..52763a179e73 100644 --- a/services/core/java/com/android/server/tv/TvInputManagerService.java +++ b/services/core/java/com/android/server/tv/TvInputManagerService.java @@ -288,7 +288,7 @@ public final class TvInputManagerService extends SystemService { userState.serviceStateMap.put(component, serviceState); updateServiceConnectionLocked(component, userId); } else { - inputList.addAll(serviceState.hardwareInputList); + inputList.addAll(serviceState.hardwareInputMap.values()); } } else { try { @@ -2105,7 +2105,7 @@ public final class TvInputManagerService extends SystemService { private final ServiceConnection connection; private final ComponentName component; private final boolean isHardware; - private final List<TvInputInfo> hardwareInputList = new ArrayList<>(); + private final Map<String, TvInputInfo> hardwareInputMap = new HashMap<>(); private ITvInputService service; private ServiceCallback callback; @@ -2216,7 +2216,7 @@ public final class TvInputManagerService extends SystemService { } if (serviceState.isHardware) { - serviceState.hardwareInputList.clear(); + serviceState.hardwareInputMap.clear(); for (TvInputHardwareInfo hardware : mTvInputHardwareManager.getHardwareList()) { try { serviceState.service.notifyHardwareAdded(hardware); @@ -2283,7 +2283,7 @@ public final class TvInputManagerService extends SystemService { private void addHardwareInputLocked(TvInputInfo inputInfo) { ServiceState serviceState = getServiceStateLocked(mComponent, mUserId); - serviceState.hardwareInputList.add(inputInfo); + serviceState.hardwareInputMap.put(inputInfo.getId(), inputInfo); buildTvInputListLocked(mUserId, null); } @@ -2309,15 +2309,7 @@ public final class TvInputManagerService extends SystemService { ensureHardwarePermission(); synchronized (mLock) { ServiceState serviceState = getServiceStateLocked(mComponent, mUserId); - boolean removed = false; - for (Iterator<TvInputInfo> it = serviceState.hardwareInputList.iterator(); - it.hasNext(); ) { - if (it.next().getId().equals(inputId)) { - it.remove(); - removed = true; - break; - } - } + boolean removed = serviceState.hardwareInputMap.remove(inputId) != null; if (removed) { buildTvInputListLocked(mUserId, null); mTvInputHardwareManager.removeHardwareInput(inputId); diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java index b7479da8d683..5abc4e4d2e56 100644 --- a/services/core/java/com/android/server/wm/AccessibilityController.java +++ b/services/core/java/com/android/server/wm/AccessibilityController.java @@ -156,9 +156,9 @@ final class AccessibilityController { } } - public void onRotationChangedLocked(DisplayContent displayContent, int rotation) { + public void onRotationChangedLocked(DisplayContent displayContent) { if (mDisplayMagnifier != null) { - mDisplayMagnifier.onRotationChangedLocked(displayContent, rotation); + mDisplayMagnifier.onRotationChangedLocked(displayContent); } if (mWindowsForAccessibilityObserver != null) { mWindowsForAccessibilityObserver.scheduleComputeChangedWindowsLocked(); @@ -222,6 +222,14 @@ final class AccessibilityController { || mWindowsForAccessibilityObserver != null); } + /** NOTE: This has to be called within a surface transaction. */ + public void setForceShowMagnifiableBoundsLocked(boolean show) { + if (mDisplayMagnifier != null) { + mDisplayMagnifier.setForceShowMagnifiableBoundsLocked(show); + mDisplayMagnifier.drawMagnifiedRegionBorderIfNeededLocked(); + } + } + private static void populateTransformationMatrixLocked(WindowState windowState, Matrix outMatrix) { sTempFloats[Matrix.MSCALE_X] = windowState.mWinAnimator.mDsDx; @@ -266,6 +274,8 @@ final class AccessibilityController { private final long mLongAnimationDuration; + private boolean mForceShowMagnifiableBounds = false; + public DisplayMagnifier(WindowManagerService windowManagerService, MagnificationCallbacks callbacks) { mContext = windowManagerService.mContext; @@ -283,6 +293,15 @@ final class AccessibilityController { mWindowManagerService.scheduleAnimationLocked(); } + public void setForceShowMagnifiableBoundsLocked(boolean show) { + mForceShowMagnifiableBounds = show; + mMagnifedViewport.setMagnifiedRegionBorderShownLocked(show, true); + } + + public boolean isForceShowingMagnifiableBoundsLocked() { + return mForceShowMagnifiableBounds; + } + public void onRectangleOnScreenRequestedLocked(Rect rectangle) { if (DEBUG_RECTANGLE_REQUESTED) { Slog.i(LOG_TAG, "Rectangle on screen requested: " + rectangle); @@ -312,9 +331,10 @@ final class AccessibilityController { mWindowManagerService.scheduleAnimationLocked(); } - public void onRotationChangedLocked(DisplayContent displayContent, int rotation) { + public void onRotationChangedLocked(DisplayContent displayContent) { if (DEBUG_ROTATION) { - Slog.i(LOG_TAG, "Rotaton: " + Surface.rotationToString(rotation) + final int rotation = displayContent.getRotation(); + Slog.i(LOG_TAG, "Rotation: " + Surface.rotationToString(rotation) + " displayId: " + displayContent.getDisplayId()); } mMagnifedViewport.onRotationChangedLocked(); @@ -487,7 +507,8 @@ final class AccessibilityController { // to show the border. We will do so when the pending message is handled. if (!mHandler.hasMessages( MyHandler.MESSAGE_SHOW_MAGNIFIED_REGION_BOUNDS_IF_NEEDED)) { - setMagnifiedRegionBorderShownLocked(isMagnifyingLocked(), true); + setMagnifiedRegionBorderShownLocked( + isMagnifyingLocked() || isForceShowingMagnifiableBoundsLocked(), true); } } @@ -599,11 +620,11 @@ final class AccessibilityController { } public void onRotationChangedLocked() { - // If we are magnifying, hide the magnified border window immediately so + // If we are showing the magnification border, hide it immediately so // the user does not see strange artifacts during rotation. The screenshot - // used for rotation has already the border. After the rotation is complete + // used for rotation already has the border. After the rotation is complete // we will show the border. - if (isMagnifyingLocked()) { + if (isMagnifyingLocked() || isForceShowingMagnifiableBoundsLocked()) { setMagnifiedRegionBorderShownLocked(false, false); final long delay = (long) (mLongAnimationDuration * mWindowManagerService.getWindowAnimationScaleLocked()); @@ -925,7 +946,8 @@ final class AccessibilityController { case MESSAGE_SHOW_MAGNIFIED_REGION_BOUNDS_IF_NEEDED : { synchronized (mWindowManagerService.mWindowMap) { - if (mMagnifedViewport.isMagnifyingLocked()) { + if (mMagnifedViewport.isMagnifyingLocked() + || isForceShowingMagnifiableBoundsLocked()) { mMagnifedViewport.setMagnifiedRegionBorderShownLocked(true, true); mWindowManagerService.scheduleAnimationLocked(); } diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java index 4eb8e0291d0c..7a36da202aad 100644 --- a/services/core/java/com/android/server/wm/AppTransition.java +++ b/services/core/java/com/android/server/wm/AppTransition.java @@ -44,6 +44,7 @@ import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ANIM; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_APP_TRANSITIONS; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; +import static com.android.server.wm.WindowStateAnimator.STACK_CLIP_BEFORE_ANIM; import static com.android.server.wm.WindowStateAnimator.STACK_CLIP_NONE; import static com.android.server.wm.WindowStateAnimator.STACK_CLIP_AFTER_ANIM; @@ -692,7 +693,7 @@ public class AppTransition implements Dump { private void getDefaultNextAppTransitionStartRect(Rect rect) { if (mDefaultNextAppTransitionAnimationSpec == null || mDefaultNextAppTransitionAnimationSpec.rect == null) { - Slog.wtf(TAG, "Starting rect for app requested, but none available", new Throwable()); + Slog.e(TAG, "Starting rect for app requested, but none available", new Throwable()); rect.setEmpty(); } else { rect.set(mDefaultNextAppTransitionAnimationSpec.rect); @@ -705,7 +706,7 @@ public class AppTransition implements Dump { spec = mDefaultNextAppTransitionAnimationSpec; } if (spec == null || spec.rect == null) { - Slog.wtf(TAG, "Starting rect for task: " + taskId + " requested, but not available", + Slog.e(TAG, "Starting rect for task: " + taskId + " requested, but not available", new Throwable()); rect.setEmpty(); } else { @@ -1652,10 +1653,14 @@ public class AppTransition implements Dump { } int getAppStackClipMode() { + // When dismiss keyguard animation occurs, clip before the animation to prevent docked + // app from showing beyond the divider + if (mNextAppTransition == TRANSIT_KEYGUARD_GOING_AWAY + || mNextAppTransition == TRANSIT_KEYGUARD_GOING_AWAY_ON_WALLPAPER) { + return STACK_CLIP_BEFORE_ANIM; + } return mNextAppTransition == TRANSIT_ACTIVITY_RELAUNCH || mNextAppTransition == TRANSIT_DOCK_TASK_FROM_RECENTS - || mNextAppTransition == TRANSIT_KEYGUARD_GOING_AWAY - || mNextAppTransition == TRANSIT_KEYGUARD_GOING_AWAY_ON_WALLPAPER || mNextAppTransitionType == NEXT_TRANSIT_TYPE_CLIP_REVEAL ? STACK_CLIP_NONE : STACK_CLIP_AFTER_ANIM; @@ -2058,15 +2063,7 @@ public class AppTransition implements Dump { * @return whether the transition should show the thumbnail being scaled down. */ private boolean shouldScaleDownThumbnailTransition(int uiMode, int orientation) { - return isTvUiMode(uiMode) - || mGridLayoutRecentsEnabled + return mGridLayoutRecentsEnabled || orientation == Configuration.ORIENTATION_PORTRAIT; } - - /** - * @return whether the specified {@param uiMode} is the TV mode. - */ - private boolean isTvUiMode(int uiMode) { - return (uiMode & Configuration.UI_MODE_TYPE_TELEVISION) > 0; - } } diff --git a/services/core/java/com/android/server/wm/AppWindowContainerController.java b/services/core/java/com/android/server/wm/AppWindowContainerController.java index 266ab4cfde0b..b90a82a88641 100644 --- a/services/core/java/com/android/server/wm/AppWindowContainerController.java +++ b/services/core/java/com/android/server/wm/AppWindowContainerController.java @@ -59,6 +59,15 @@ public class AppWindowContainerController private final IApplicationToken mToken; private final Handler mHandler; + private final Runnable mOnStartingWindowDrawn = () -> { + if (mListener == null) { + return; + } + if (DEBUG_VISIBILITY) Slog.v(TAG_WM, "Reporting drawn in " + + AppWindowContainerController.this.mToken); + mListener.onStartingWindowDrawn(); + }; + private final Runnable mOnWindowsDrawn = () -> { if (mListener == null) { return; @@ -655,6 +664,9 @@ public class AppWindowContainerController } } + void reportStartingWindowDrawn() { + mHandler.post(mOnStartingWindowDrawn); + } void reportWindowsDrawn() { mHandler.post(mOnWindowsDrawn); diff --git a/services/core/java/com/android/server/wm/AppWindowContainerListener.java b/services/core/java/com/android/server/wm/AppWindowContainerListener.java index 12d4b2fcb132..9d459cfccc09 100644 --- a/services/core/java/com/android/server/wm/AppWindowContainerListener.java +++ b/services/core/java/com/android/server/wm/AppWindowContainerListener.java @@ -24,6 +24,12 @@ public interface AppWindowContainerListener extends WindowContainerListener { void onWindowsVisible(); /** Called when the windows associated app window container are no longer visible. */ void onWindowsGone(); + + /** + * Called when the starting window for this container is drawn. + */ + void onStartingWindowDrawn(); + /** * Called when the key dispatching to a window associated with the app window container * timed-out. diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java index 4aa013ae90c1..c20ee973bac5 100644 --- a/services/core/java/com/android/server/wm/AppWindowToken.java +++ b/services/core/java/com/android/server/wm/AppWindowToken.java @@ -42,7 +42,6 @@ import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WINDOW_MOVEME 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.H.NOTIFY_ACTIVITY_DRAWN; -import static com.android.server.wm.WindowManagerService.H.NOTIFY_STARTING_WINDOW_DRAWN; import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_NORMAL; import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_WILL_PLACE_SURFACES; import static com.android.server.wm.WindowManagerService.logWithStack; @@ -164,9 +163,6 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree private boolean mLastContainsShowWhenLockedWindow; private boolean mLastContainsDismissKeyguardWindow; - private ArrayList<WindowSurfaceController.SurfaceControlWithBackground> mSurfaceViewBackgrounds = - new ArrayList<>(); - ArrayDeque<Rect> mFrozenBounds = new ArrayDeque<>(); ArrayDeque<Configuration> mFrozenMergedConfig = new ArrayDeque<>(); @@ -668,6 +664,15 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree return (Task) getParent(); } + TaskStack getStack() { + final Task task = getTask(); + if (task != null) { + return task.mStack; + } else { + return null; + } + } + @Override void onParentSet() { super.onParentSet(); @@ -962,36 +967,6 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree mService.mWindowPlacerLocked.performSurfacePlacement(); } - void addSurfaceViewBackground(WindowSurfaceController.SurfaceControlWithBackground background) { - mSurfaceViewBackgrounds.add(background); - } - - void removeSurfaceViewBackground(WindowSurfaceController.SurfaceControlWithBackground background) { - mSurfaceViewBackgrounds.remove(background); - updateSurfaceViewBackgroundVisibilities(); - } - - // We use DimLayers behind SurfaceViews to prevent holes while resizing and creating. - // However, we need to ensure one SurfaceView doesn't cover another when they are both placed - // below the main app window (as traditionally a SurfaceView which is never drawn - // to is totally translucent). So we look at all our SurfaceView backgrounds and only enable - // the background for the SurfaceView with lowest Z order - void updateSurfaceViewBackgroundVisibilities() { - WindowSurfaceController.SurfaceControlWithBackground bottom = null; - int bottomLayer = Integer.MAX_VALUE; - for (int i = 0; i < mSurfaceViewBackgrounds.size(); i++) { - WindowSurfaceController.SurfaceControlWithBackground sc = mSurfaceViewBackgrounds.get(i); - if (sc.mVisible && sc.mLayer < bottomLayer) { - bottomLayer = sc.mLayer; - bottom = sc; - } - } - for (int i = 0; i < mSurfaceViewBackgrounds.size(); i++) { - WindowSurfaceController.SurfaceControlWithBackground sc = mSurfaceViewBackgrounds.get(i); - sc.updateBackgroundVisibility(sc != bottom); - } - } - void resetJustMovedInStack() { for (int i = mChildren.size() - 1; i >= 0; i--) { (mChildren.get(i)).resetJustMovedInStack(); @@ -1329,7 +1304,9 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree } } } else if (w.isDrawnLw()) { - mService.mH.sendEmptyMessage(NOTIFY_STARTING_WINDOW_DRAWN); + if (getController() != null) { + getController().reportStartingWindowDrawn(); + } startingDisplayed = true; } } diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 5486aa88acd6..e5b00f3df79a 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -73,18 +73,24 @@ import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_STACK; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_TOKEN_MOVEMENT; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WALLPAPER; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WALLPAPER_LIGHT; +import static com.android.server.wm.WindowManagerDebugConfig.SHOW_LIGHT_TRANSACTIONS; import static com.android.server.wm.WindowManagerDebugConfig.SHOW_STACK_CRAWLS; import static com.android.server.wm.WindowManagerDebugConfig.SHOW_TRANSACTIONS; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; +import static com.android.server.wm.WindowManagerService.CUSTOM_SCREEN_ROTATION; import static com.android.server.wm.WindowManagerService.H.SEND_NEW_CONFIGURATION; import static com.android.server.wm.WindowManagerService.H.UPDATE_DOCKED_STACK_DIVIDER; import static com.android.server.wm.WindowManagerService.H.WINDOW_HIDE_TIMEOUT; import static com.android.server.wm.WindowManagerService.LAYOUT_REPEAT_THRESHOLD; +import static com.android.server.wm.WindowManagerService.MAX_ANIMATION_DURATION; +import static com.android.server.wm.WindowManagerService.SEAMLESS_ROTATION_TIMEOUT_DURATION; import static com.android.server.wm.WindowManagerService.TYPE_LAYER_MULTIPLIER; import static com.android.server.wm.WindowManagerService.TYPE_LAYER_OFFSET; import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_WILL_PLACE_SURFACES; +import static com.android.server.wm.WindowManagerService.WINDOWS_FREEZING_SCREENS_ACTIVE; import static com.android.server.wm.WindowManagerService.WINDOWS_FREEZING_SCREENS_TIMEOUT; +import static com.android.server.wm.WindowManagerService.WINDOW_FREEZE_TIMEOUT_DURATION; import static com.android.server.wm.WindowManagerService.dipToPixel; import static com.android.server.wm.WindowManagerService.logSurface; import static com.android.server.wm.WindowState.RESIZE_HANDLE_WIDTH_IN_DP; @@ -94,6 +100,7 @@ import static com.android.server.wm.WindowSurfacePlacer.SET_WALLPAPER_MAY_CHANGE import android.annotation.NonNull; import android.app.ActivityManager.StackId; +import android.content.res.CompatibilityInfo; import android.content.res.Configuration; import android.graphics.Bitmap; import android.graphics.GraphicBuffer; @@ -113,6 +120,7 @@ import android.util.MutableBoolean; import android.util.Slog; import android.view.Display; import android.view.DisplayInfo; +import android.view.InputDevice; import android.view.Surface; import android.view.SurfaceControl; import android.view.WindowManagerPolicy; @@ -181,8 +189,59 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo private final DisplayInfo mDisplayInfo = new DisplayInfo(); private final Display mDisplay; private final DisplayMetrics mDisplayMetrics = new DisplayMetrics(); + /** + * For default display it contains real metrics, empty for others. + * @see WindowManagerService#createWatermarkInTransaction() + */ + final DisplayMetrics mRealDisplayMetrics = new DisplayMetrics(); + /** @see #computeCompatSmallestWidth(boolean, int, int, int, int) */ + private final DisplayMetrics mTmpDisplayMetrics = new DisplayMetrics(); + /** + * Compat metrics computed based on {@link #mDisplayMetrics}. + * @see #updateDisplayAndOrientation(int) + */ + private final DisplayMetrics mCompatDisplayMetrics = new DisplayMetrics(); + + /** The desired scaling factor for compatible apps. */ + float mCompatibleScreenScale; - Rect mBaseDisplayRect = new Rect(); + /** + * Current rotation of the display. + * Constants as per {@link android.view.Surface.Rotation}. + * + * @see #updateRotationUnchecked(boolean) + */ + private int mRotation = 0; + /** + * Last applied orientation of the display. + * Constants as per {@link android.content.pm.ActivityInfo.ScreenOrientation}. + * + * @see WindowManagerService#updateOrientationFromAppTokensLocked(boolean, int) + */ + private int mLastOrientation = SCREEN_ORIENTATION_UNSPECIFIED; + /** + * Flag indicating that the application is receiving an orientation that has different metrics + * than it expected. E.g. Portrait instead of Landscape. + * + * @see #updateRotationUnchecked(boolean) + */ + private boolean mAltOrientation = false; + /** + * Orientation forced by some window. If there is no visible window that specifies orientation + * it is set to {@link android.content.pm.ActivityInfo#SCREEN_ORIENTATION_UNSPECIFIED}. + * + * @see NonAppWindowContainers#getOrientation() + */ + private int mLastWindowForcedOrientation = SCREEN_ORIENTATION_UNSPECIFIED; + /** + * Last orientation forced by the keyguard. It is applied when keyguard is shown and is not + * occluded. + * + * @see NonAppWindowContainers#getOrientation() + */ + private int mLastKeyguardForcedOrientation = SCREEN_ORIENTATION_UNSPECIFIED; + + private Rect mBaseDisplayRect = new Rect(); private Rect mContentRect = new Rect(); // Accessed directly by all users. @@ -764,6 +823,542 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo return mDisplayMetrics; } + int getRotation() { + return mRotation; + } + + void setRotation(int newRotation) { + mRotation = newRotation; + } + + int getLastOrientation() { + return mLastOrientation; + } + + void setLastOrientation(int orientation) { + mLastOrientation = orientation; + } + + boolean getAltOrientation() { + return mAltOrientation; + } + + void setAltOrientation(boolean altOrientation) { + mAltOrientation = altOrientation; + } + + int getLastWindowForcedOrientation() { + 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; } @@ -884,14 +1479,14 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo final WindowManagerPolicy policy = mService.mPolicy; if (mService.mDisplayFrozen) { - if (mService.mLastWindowForcedOrientation != SCREEN_ORIENTATION_UNSPECIFIED) { + if (mLastWindowForcedOrientation != SCREEN_ORIENTATION_UNSPECIFIED) { if (DEBUG_ORIENTATION) Slog.v(TAG_WM, - "Display is frozen, return " + mService.mLastWindowForcedOrientation); + "Display is frozen, return " + mLastWindowForcedOrientation); // If the display is frozen, some activities may be in the middle of restarting, and // thus have removed their old window. If the window has the flag to hide the lock // screen, then the lock screen can re-appear and inflict its own orientation on us. // Keep the orientation stable until this all settles down. - return mService.mLastWindowForcedOrientation; + return mLastWindowForcedOrientation; } else if (policy.isKeyguardLocked()) { // Use the last orientation the while the display is frozen with the keyguard // locked. This could be the keyguard forced orientation or from a SHOW_WHEN_LOCKED @@ -899,8 +1494,8 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo // things aren't stable while the display is frozen, for example the window could be // momentarily unavailable due to activity relaunch. if (DEBUG_ORIENTATION) Slog.v(TAG_WM, "Display is frozen while keyguard locked, " - + "return " + mService.mLastOrientation); - return mService.mLastOrientation; + + "return " + mLastOrientation); + return mLastOrientation; } } else { final int orientation = mAboveAppWindowsContainers.getOrientation(); @@ -2051,7 +2646,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo Slog.v(TAG, "performLayout: needed=" + isLayoutNeeded() + " dw=" + dw + " dh=" + dh); } - mService.mPolicy.beginLayoutLw(isDefaultDisplay, dw, dh, mService.mRotation, + mService.mPolicy.beginLayoutLw(isDefaultDisplay, dw, dh, mRotation, getConfiguration().uiMode); if (isDefaultDisplay) { // Not needed on non-default displays. @@ -2712,10 +3307,10 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo } if (DEBUG_ORIENTATION) Slog.v(TAG_WM, - "No app is requesting an orientation, return " + mService.mLastOrientation); + "No app is requesting an orientation, return " + mLastOrientation); // The next app has not been requested to be visible, so we keep the current orientation // to prevent freezing/unfreezing the display too early. - return mService.mLastOrientation; + return mLastOrientation; } } @@ -2766,15 +3361,15 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo final int req = win.mAttrs.screenOrientation; if (DEBUG_ORIENTATION) Slog.v(TAG_WM, win + " forcing orientation to " + req); if (policy.isKeyguardHostWindow(win.mAttrs)) { - mService.mLastKeyguardForcedOrientation = req; + mLastKeyguardForcedOrientation = req; } - return (mService.mLastWindowForcedOrientation = req); + return (mLastWindowForcedOrientation = req); } - mService.mLastWindowForcedOrientation = SCREEN_ORIENTATION_UNSPECIFIED; + mLastWindowForcedOrientation = SCREEN_ORIENTATION_UNSPECIFIED; if (policy.isKeyguardShowingAndNotOccluded()) { - return mService.mLastKeyguardForcedOrientation; + return mLastKeyguardForcedOrientation; } return SCREEN_ORIENTATION_UNSET; diff --git a/services/core/java/com/android/server/wm/PinnedStackWindowController.java b/services/core/java/com/android/server/wm/PinnedStackWindowController.java index 71f88de724fb..6a0e353ea385 100644 --- a/services/core/java/com/android/server/wm/PinnedStackWindowController.java +++ b/services/core/java/com/android/server/wm/PinnedStackWindowController.java @@ -109,6 +109,13 @@ public class PinnedStackWindowController extends StackWindowController { } /** + * @return whether the bounds are currently animating to fullscreen. + */ + public boolean isBoundsAnimatingToFullscreen() { + return mContainer.isBoundsAnimatingToFullscreen(); + } + + /** * Checks the {@param bounds} and retirms non-null fullscreen bounds for the pinned stack * animation if necessary. */ diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index 126e0800cf07..68d0f2496d5a 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -55,13 +55,11 @@ import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM; import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT; import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER; -import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ADD_REMOVE; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_DISPLAY; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_KEEP_SCREEN_ON; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYOUT_REPEATS; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ORIENTATION; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_POWER; -import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_TOKEN_MOVEMENT; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_VISIBILITY; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WALLPAPER_LIGHT; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WINDOW_TRACE; @@ -740,7 +738,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent> { if (DEBUG_ORIENTATION) Slog.d(TAG, "Performing post-rotate rotation"); // TODO(multi-display): Update rotation for different displays separately. final int displayId = defaultDisplay.getDisplayId(); - if (mService.updateRotationUncheckedLocked(false, displayId)) { + if (defaultDisplay.updateRotationUnchecked(false /* inTransaction */)) { mService.mH.obtainMessage(SEND_NEW_CONFIGURATION, displayId).sendToTarget(); } else { mUpdateRotation = false; @@ -802,6 +800,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent> { mHoldScreenWindow = null; mObscuringWindow = null; + // TODO(multi-display): Support these features on secondary screens. if (mService.mWatermark != null) { mService.mWatermark.positionSurface(defaultDw, defaultDh); } @@ -809,11 +808,12 @@ class RootWindowContainer extends WindowContainer<DisplayContent> { mService.mStrictModeFlash.positionSurface(defaultDw, defaultDh); } if (mService.mCircularDisplayMask != null) { - mService.mCircularDisplayMask.positionSurface(defaultDw, defaultDh, mService.mRotation); + mService.mCircularDisplayMask.positionSurface(defaultDw, defaultDh, + mService.getDefaultDisplayRotation()); } if (mService.mEmulatorDisplayOverlay != null) { mService.mEmulatorDisplayOverlay.positionSurface(defaultDw, defaultDh, - mService.mRotation); + mService.getDefaultDisplayRotation()); } boolean focusDisplayed = false; diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java index b7a9e663ab0f..4df513eb27f4 100644 --- a/services/core/java/com/android/server/wm/Session.java +++ b/services/core/java/com/android/server/wm/Session.java @@ -208,13 +208,6 @@ public class Session extends IWindowSession.Stub } @Override - public void repositionChild(IWindow window, int left, int top, int right, int bottom, - long deferTransactionUntilFrame, Rect outFrame) { - mService.repositionChild(this, window, left, top, right, bottom, - deferTransactionUntilFrame, outFrame); - } - - @Override public void prepareToReplaceWindows(IBinder appToken, boolean childrenOnly) { mService.setWillReplaceWindows(appToken, childrenOnly); } @@ -235,10 +228,6 @@ public class Session extends IWindowSession.Stub return res; } - public void performDeferredDestroy(IWindow window) { - mService.performDeferredDestroyWindow(this, window); - } - public boolean outOfMemory(IWindow window) { return mService.outOfMemoryWindow(this, window); } diff --git a/services/core/java/com/android/server/wm/TaskSnapshotSurface.java b/services/core/java/com/android/server/wm/TaskSnapshotSurface.java index 76fb52268bb0..c0598ca0238d 100644 --- a/services/core/java/com/android/server/wm/TaskSnapshotSurface.java +++ b/services/core/java/com/android/server/wm/TaskSnapshotSurface.java @@ -152,6 +152,14 @@ class TaskSnapshotSurface implements StartingSurface { private void drawSnapshot(GraphicBuffer snapshot) { mSurface.attachAndQueueBuffer(snapshot); + final boolean reportNextDraw; + synchronized (mService.mWindowMap) { + mHasDrawn = true; + reportNextDraw = mReportNextDraw; + } + if (reportNextDraw) { + reportDrawn(); + } mSurface.release(); } diff --git a/services/core/java/com/android/server/wm/WindowAnimator.java b/services/core/java/com/android/server/wm/WindowAnimator.java index 993a918913ae..57fb81ce0b2d 100644 --- a/services/core/java/com/android/server/wm/WindowAnimator.java +++ b/services/core/java/com/android/server/wm/WindowAnimator.java @@ -161,7 +161,7 @@ public class WindowAnimator { // We just finished rotation animation which means we did not announce // the rotation and waited for it to end, announce now. accessibilityController.onRotationChangedLocked( - mService.getDefaultDisplayContentLocked(), mService.mRotation); + mService.getDefaultDisplayContentLocked()); } } } diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index c0cc923ad831..5551afec76e8 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -16,21 +16,15 @@ package com.android.server.wm; -import static android.Manifest.permission.INTERNAL_SYSTEM_WINDOW; import static android.Manifest.permission.MANAGE_APP_TOKENS; import static android.Manifest.permission.REGISTER_WINDOW_MANAGER_LISTENERS; import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT; import static android.app.ActivityManager.StackId.PINNED_STACK_ID; -import static android.app.AppOpsManager.MODE_IGNORED; import static android.app.AppOpsManager.OP_SYSTEM_ALERT_WINDOW; import static android.app.StatusBarManager.DISABLE_MASK; import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED; import static android.content.Intent.ACTION_USER_REMOVED; -import static android.content.Intent.EXTRA_PACKAGE_NAME; -import static android.content.Intent.EXTRA_UID; import static android.content.Intent.EXTRA_USER_HANDLE; -import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; -import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.os.UserHandle.USER_NULL; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.WindowManager.DOCKED_INVALID; @@ -69,8 +63,6 @@ import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER; import static com.android.server.wm.AppTransition.TRANSIT_UNSET; import static com.android.server.wm.AppWindowAnimator.PROLONG_ANIMATION_AT_END; import static com.android.server.wm.AppWindowAnimator.PROLONG_ANIMATION_AT_START; -import static com.android.server.wm.DragResizeMode.DRAG_RESIZE_MODE_DOCKED_DIVIDER; -import static com.android.server.wm.DragResizeMode.DRAG_RESIZE_MODE_FREEFORM; import static com.android.server.wm.KeyguardDisableHandler.KEYGUARD_POLICY_CHANGED; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ADD_REMOVE; @@ -112,7 +104,6 @@ import android.app.ActivityManager.TaskSnapshot; import android.app.ActivityManagerInternal; import android.app.AppOpsManager; import android.app.IActivityManager; -import android.app.admin.DevicePolicyManager; import android.content.BroadcastReceiver; import android.content.ContentResolver; import android.content.Context; @@ -120,7 +111,6 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; -import android.content.res.CompatibilityInfo; import android.content.res.Configuration; import android.database.ContentObserver; import android.graphics.Bitmap; @@ -222,6 +212,7 @@ import com.android.server.DisplayThread; import com.android.server.EventLogTags; import com.android.server.FgThread; import com.android.server.LocalServices; +import com.android.server.LockGuard; import com.android.server.UiThread; import com.android.server.Watchdog; import com.android.server.input.InputManagerService; @@ -528,11 +519,6 @@ public class WindowManagerService extends IWindowManager.Stub // The root of the device window hierarchy. RootWindowContainer mRoot; - // TODO: Move several of this states to the RootWindowContainer or DisplayContent - int mRotation = 0; - int mLastOrientation = SCREEN_ORIENTATION_UNSPECIFIED; - boolean mAltOrientation = false; - int mDockedStackCreateMode = DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT; Rect mDockedStackCreateBounds; @@ -546,13 +532,17 @@ public class WindowManagerService extends IWindowManager.Stub } class RotationWatcher { - IRotationWatcher watcher; - IBinder.DeathRecipient deathRecipient; - RotationWatcher(IRotationWatcher w, IBinder.DeathRecipient d) { - watcher = w; - deathRecipient = d; + IRotationWatcher mWatcher; + IBinder.DeathRecipient mDeathRecipient; + int mDisplayId; + RotationWatcher(IRotationWatcher watcher, IBinder.DeathRecipient deathRecipient, + int displayId) { + mWatcher = watcher; + mDeathRecipient = deathRecipient; + mDisplayId = displayId; } } + ArrayList<RotationWatcher> mRotationWatchers = new ArrayList<>(); int mDeferredRotationPauseCount; @@ -573,8 +563,6 @@ public class WindowManagerService extends IWindowManager.Stub boolean mClientFreezingScreen = false; int mAppsFreezingScreen = 0; - int mLastWindowForcedOrientation = SCREEN_ORIENTATION_UNSPECIFIED; - int mLastKeyguardForcedOrientation = SCREEN_ORIENTATION_UNSPECIFIED; int mLayoutSeq = 0; @@ -603,11 +591,6 @@ public class WindowManagerService extends IWindowManager.Stub boolean mIsTouchDevice; - final DisplayMetrics mDisplayMetrics = new DisplayMetrics(); - final DisplayMetrics mRealDisplayMetrics = new DisplayMetrics(); - final DisplayMetrics mTmpDisplayMetrics = new DisplayMetrics(); - final DisplayMetrics mCompatDisplayMetrics = new DisplayMetrics(); - final H mH = new H(); final Choreographer mChoreographer = Choreographer.getInstance(); @@ -852,9 +835,6 @@ public class WindowManagerService extends IWindowManager.Stub final Configuration mTempConfiguration = new Configuration(); - // The desired scaling factor for compatible apps. - float mCompatibleScreenScale; - // If true, only the core apps and services are being launched because the device // is in a special boot mode, such as being encrypted or waiting for a decryption password. // For example, when this flag is true, there will be no wallpaper service. @@ -952,6 +932,7 @@ public class WindowManagerService extends IWindowManager.Stub private WindowManagerService(Context context, InputManagerService inputManager, boolean haveInputMethods, boolean showBootMsgs, boolean onlyCore, WindowManagerPolicy policy) { + LockGuard.installLock(this, LockGuard.INDEX_WINDOW); mRoot = new RootWindowContainer(this); mContext = context; mHaveInputMethods = haveInputMethods; @@ -1450,7 +1431,7 @@ public class WindowManagerService extends IWindowManager.Stub } else { taskBounds = null; } - if (mPolicy.getInsetHintLw(win.mAttrs, taskBounds, mRotation, + if (mPolicy.getInsetHintLw(win.mAttrs, taskBounds, displayInfo.rotation, displayInfo.logicalWidth, displayInfo.logicalHeight, outContentInsets, outStableInsets, outOutsets)) { res |= WindowManagerGlobal.ADD_FLAG_ALWAYS_CONSUME_NAV_BAR; @@ -1827,64 +1808,6 @@ public class WindowManagerService extends IWindowManager.Stub } } - void repositionChild(Session session, IWindow client, - int left, int top, int right, int bottom, - long frameNumber, Rect outFrame) { - Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "repositionChild"); - long origId = Binder.clearCallingIdentity(); - - try { - synchronized(mWindowMap) { - WindowState win = windowForClientLocked(session, client, false); - if (win == null) { - return; - } - if (!win.isChildWindow()) { - throw new IllegalArgumentException( - "repositionChild called but window is not" - + "attached to a parent win=" + win); - } - - win.mAttrs.x = left; - win.mAttrs.y = top; - win.mAttrs.width = right - left; - win.mAttrs.height = bottom - top; - win.setWindowScale(win.mRequestedWidth, win.mRequestedHeight); - - if (win.mHasSurface) { - if (SHOW_TRANSACTIONS) { - Slog.i(TAG_WM, ">>> OPEN TRANSACTION repositionChild"); - } - - openSurfaceTransaction(); - - try { - - win.applyGravityAndUpdateFrame(win.mContainingFrame, win.mDisplayFrame); - win.mWinAnimator.computeShownFrameLocked(); - - win.mWinAnimator.setSurfaceBoundariesLocked(false); - - if (frameNumber > 0) { - win.mWinAnimator.deferTransactionUntilParentFrame(frameNumber); - } - - } finally { - closeSurfaceTransaction(); - if (SHOW_TRANSACTIONS) { - Slog.i(TAG_WM, "<<< CLOSE TRANSACTION repositionChild"); - } - } - } - - outFrame = win.mCompatFrame; - } - } finally { - Binder.restoreCallingIdentity(origId); - Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER); - } - } - public int relayoutWindow(Session session, IWindow client, int seq, WindowManager.LayoutParams attrs, int requestedWidth, int requestedHeight, int viewVisibility, int flags, @@ -2229,23 +2152,6 @@ public class WindowManagerService extends IWindowManager.Stub return result; } - public void performDeferredDestroyWindow(Session session, IWindow client) { - long origId = Binder.clearCallingIdentity(); - - try { - synchronized (mWindowMap) { - WindowState win = windowForClientLocked(session, client, false); - if (win == null || win.mWillReplaceWindow) { - return; - } - - win.mWinAnimator.destroyDeferredSurfaceLocked(); - } - } finally { - Binder.restoreCallingIdentity(origId); - } - } - public boolean outOfMemoryWindow(Session session, IWindow client) { final long origId = Binder.clearCallingIdentity(); @@ -2471,10 +2377,10 @@ public class WindowManagerService extends IWindowManager.Stub // to keep override configs clear of non-empty values (e.g. fontSize). mTempConfiguration.unset(); mTempConfiguration.updateFrom(currentConfig); - computeScreenConfigurationLocked(mTempConfiguration, displayId); + final DisplayContent displayContent = mRoot.getDisplayContent(displayId); + displayContent.computeScreenConfiguration(mTempConfiguration); if (currentConfig.diff(mTempConfiguration) != 0) { mWaitingForConfig = true; - final DisplayContent displayContent = mRoot.getDisplayContent(displayId); displayContent.setLayoutNeeded(); int anim[] = new int[2]; if (displayContent.isDimming()) { @@ -2505,13 +2411,17 @@ public class WindowManagerService extends IWindowManager.Stub boolean updateOrientationFromAppTokensLocked(boolean inTransaction, int displayId) { long ident = Binder.clearCallingIdentity(); try { - final int req = mRoot.getDisplayContent(displayId).getOrientation(); - if (req != mLastOrientation) { - mLastOrientation = req; + final DisplayContent dc = mRoot.getDisplayContent(displayId); + final int req = dc.getOrientation(); + if (req != dc.getLastOrientation()) { + dc.setLastOrientation(req); //send a message to Policy indicating orientation change to take //action like disabling/enabling sensors etc., - mPolicy.setCurrentOrientationLw(req); - if (updateRotationUncheckedLocked(inTransaction, displayId)) { + // TODO(multi-display): Implement policy for secondary displays. + if (dc.isDefaultDisplay) { + mPolicy.setCurrentOrientationLw(req); + } + if (dc.updateRotationUnchecked(inTransaction)) { // changed return true; } @@ -2527,10 +2437,18 @@ public class WindowManagerService extends IWindowManager.Stub // changed the real orientation our applied our screen rotation animation. // For example, because a previous screen rotation was in progress. boolean rotationNeedsUpdateLocked() { - int rotation = mPolicy.rotationForOrientationLw(mLastOrientation, mRotation); + // TODO(multi-display): Check for updates on all displays. Need to have per-display policy + // to implement WindowManagerPolicy#rotationForOrientationLw() correctly. + final DisplayContent defaultDisplayContent = getDefaultDisplayContentLocked(); + final int lastOrientation = defaultDisplayContent.getLastOrientation(); + final int oldRotation = defaultDisplayContent.getRotation(); + final boolean oldAltOrientation = defaultDisplayContent.getAltOrientation(); + + final int rotation = mPolicy.rotationForOrientationLw(lastOrientation, + oldRotation); boolean altOrientation = !mPolicy.rotationHasCompatibleMetricsLw( - mLastOrientation, rotation); - if (mRotation == rotation && mAltOrientation == altOrientation) { + lastOrientation, rotation); + if (oldRotation == rotation && oldAltOrientation == altOrientation) { return false; } return true; @@ -3758,6 +3676,7 @@ public class WindowManagerService extends IWindowManager.Stub */ @Override public void freezeRotation(int rotation) { + // TODO(multi-display): Track which display is rotated. if (!checkCallingPermission(android.Manifest.permission.SET_ORIENTATION, "freezeRotation()")) { throw new SecurityException("Requires SET_ORIENTATION permission"); @@ -3767,12 +3686,14 @@ public class WindowManagerService extends IWindowManager.Stub + "rotation constant."); } - if (DEBUG_ORIENTATION) Slog.v(TAG_WM, "freezeRotation: mRotation=" + mRotation); + final int defaultDisplayRotation = getDefaultDisplayRotation(); + if (DEBUG_ORIENTATION) Slog.v(TAG_WM, "freezeRotation: mRotation=" + + defaultDisplayRotation); long origId = Binder.clearCallingIdentity(); try { mPolicy.setUserRotationMode(WindowManagerPolicy.USER_ROTATION_LOCKED, - rotation == -1 ? mRotation : rotation); + rotation == -1 ? defaultDisplayRotation : rotation); } finally { Binder.restoreCallingIdentity(origId); } @@ -3791,7 +3712,8 @@ public class WindowManagerService extends IWindowManager.Stub throw new SecurityException("Requires SET_ORIENTATION permission"); } - if (DEBUG_ORIENTATION) Slog.v(TAG_WM, "thawRotation: mRotation=" + mRotation); + if (DEBUG_ORIENTATION) Slog.v(TAG_WM, "thawRotation: mRotation=" + + getDefaultDisplayRotation()); long origId = Binder.clearCallingIdentity(); try { @@ -3837,10 +3759,12 @@ public class WindowManagerService extends IWindowManager.Stub mDeferredRotationPauseCount -= 1; if (mDeferredRotationPauseCount == 0) { // TODO(multi-display): Update rotation for different displays separately. - final int displayId = DEFAULT_DISPLAY; - final boolean changed = updateRotationUncheckedLocked(false, displayId); + final DisplayContent displayContent = getDefaultDisplayContentLocked(); + final boolean changed = displayContent.updateRotationUnchecked( + false /* inTransaction */); if (changed) { - mH.obtainMessage(H.SEND_NEW_CONFIGURATION, displayId).sendToTarget(); + mH.obtainMessage(H.SEND_NEW_CONFIGURATION, displayContent.getDisplayId()) + .sendToTarget(); } } } @@ -3856,9 +3780,10 @@ public class WindowManagerService extends IWindowManager.Stub try { final boolean rotationChanged; // TODO(multi-display): Update rotation for different displays separately. - int displayId = DEFAULT_DISPLAY; + final DisplayContent displayContent = getDefaultDisplayContentLocked(); synchronized (mWindowMap) { - rotationChanged = updateRotationUncheckedLocked(false, displayId); + rotationChanged = displayContent.updateRotationUnchecked( + false /* inTransaction */); if (!rotationChanged || forceRelayout) { getDefaultDisplayContentLocked().setLayoutNeeded(); mWindowPlacerLocked.performSurfacePlacement(); @@ -3866,232 +3791,18 @@ public class WindowManagerService extends IWindowManager.Stub } if (rotationChanged || alwaysSendConfiguration) { - sendNewConfiguration(displayId); + sendNewConfiguration(displayContent.getDisplayId()); } } finally { Binder.restoreCallingIdentity(origId); } } - - // TODO(multidisplay): Rotate any display? Move to DisplayContent - /** - * Updates the current rotation of the specified display. - * - * Returns true if the rotation has been changed. In this case YOU MUST CALL - * {@link #sendNewConfiguration(int)} TO UNFREEZE THE SCREEN. - */ - boolean updateRotationUncheckedLocked(boolean inTransaction, int displayId) { - if (mDeferredRotationPauseCount > 0) { - // Rotation updates have been paused temporarily. Defer the update until - // updates have been resumed. - if (DEBUG_ORIENTATION) Slog.v(TAG_WM, "Deferring rotation, rotation is paused."); - return false; - } - - ScreenRotationAnimation screenRotationAnimation = - mAnimator.getScreenRotationAnimationLocked(displayId); - if (screenRotationAnimation != null && screenRotationAnimation.isAnimating()) { - // Rotation updates cannot be performed while the previous rotation change - // animation is still in progress. Skip this update. We will try updating - // again after the animation is finished and the display is unfrozen. - if (DEBUG_ORIENTATION) Slog.v(TAG_WM, "Deferring rotation, animation in progress."); - return false; - } - if (mDisplayFrozen) { - // Even if the screen rotation animation has finished (e.g. isAnimating - // returns false), there is still some time where we haven't yet unfrozen - // the display. We also need to abort rotation here. - if (DEBUG_ORIENTATION) Slog.v(TAG_WM, - "Deferring rotation, still finishing previous rotation"); - return false; - } - - if (!mDisplayEnabled) { - // No point choosing a rotation if the display is not enabled. - if (DEBUG_ORIENTATION) Slog.v(TAG_WM, "Deferring rotation, display is not enabled."); - return false; - } - - final DisplayContent dc = mRoot.getDisplayContent(displayId); - - final int oldRotation = mRotation; - int rotation = mPolicy.rotationForOrientationLw(mLastOrientation, mRotation); - 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( - mLastOrientation, rotation); - - if (DEBUG_ORIENTATION) Slog.v(TAG_WM, "Selected orientation " + mLastOrientation - + ", got rotation " + rotation + " which has " - + (altOrientation ? "incompatible" : "compatible") + " metrics"); - - if (mRotation == rotation && mAltOrientation == altOrientation) { - // No change. - return false; - } - - if (DEBUG_ORIENTATION) Slog.v(TAG_WM, "Rotation changed to " + rotation - + (altOrientation ? " (alt)" : "") + " from " + mRotation - + (mAltOrientation ? " (alt)" : "") + ", lastOrientation=" + mLastOrientation); - - if (DisplayContent.deltaRotation(rotation, mRotation) != 2) { - mWaitingForConfig = true; - } - - mRotation = rotation; - mAltOrientation = altOrientation; - mPolicy.setRotationLw(mRotation); - - 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, mRotation); - }, 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--) { - try { - mRotationWatchers.get(i).watcher.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(), - rotation); - } - - return true; - } - @Override - public int getRotation() { - return mRotation; + public int getDefaultDisplayRotation() { + synchronized (mWindowMap) { + return getDefaultDisplayContentLocked().getRotation(); + } } @Override @@ -4100,16 +3811,16 @@ public class WindowManagerService extends IWindowManager.Stub } @Override - public int watchRotation(IRotationWatcher watcher) { + public int watchRotation(IRotationWatcher watcher, int displayId) { final IBinder watcherBinder = watcher.asBinder(); IBinder.DeathRecipient dr = new IBinder.DeathRecipient() { @Override public void binderDied() { synchronized (mWindowMap) { for (int i=0; i<mRotationWatchers.size(); i++) { - if (watcherBinder == mRotationWatchers.get(i).watcher.asBinder()) { + if (watcherBinder == mRotationWatchers.get(i).mWatcher.asBinder()) { RotationWatcher removed = mRotationWatchers.remove(i); - IBinder binder = removed.watcher.asBinder(); + IBinder binder = removed.mWatcher.asBinder(); if (binder != null) { binder.unlinkToDeath(this, 0); } @@ -4123,12 +3834,12 @@ public class WindowManagerService extends IWindowManager.Stub synchronized (mWindowMap) { try { watcher.asBinder().linkToDeath(dr, 0); - mRotationWatchers.add(new RotationWatcher(watcher, dr)); + mRotationWatchers.add(new RotationWatcher(watcher, dr, displayId)); } catch (RemoteException e) { // Client died, no cleanup needed. } - return mRotation; + return getDefaultDisplayRotation(); } } @@ -4138,11 +3849,11 @@ public class WindowManagerService extends IWindowManager.Stub synchronized (mWindowMap) { for (int i=0; i<mRotationWatchers.size(); i++) { RotationWatcher rotationWatcher = mRotationWatchers.get(i); - if (watcherBinder == rotationWatcher.watcher.asBinder()) { + if (watcherBinder == rotationWatcher.mWatcher.asBinder()) { RotationWatcher removed = mRotationWatchers.remove(i); - IBinder binder = removed.watcher.asBinder(); + IBinder binder = removed.mWatcher.asBinder(); if (binder != null) { - binder.unlinkToDeath(removed.deathRecipient, 0); + binder.unlinkToDeath(removed.mDeathRecipient, 0); } i--; } @@ -4170,10 +3881,9 @@ public class WindowManagerService extends IWindowManager.Stub @Override public int getPreferredOptionsPanelGravity() { synchronized (mWindowMap) { - final int rotation = getRotation(); - // TODO(multidisplay): Assume that such devices physical keys are on the main screen. final DisplayContent displayContent = getDefaultDisplayContentLocked(); + final int rotation = displayContent.getRotation(); if (displayContent.mInitialDisplayWidth < displayContent.mInitialDisplayHeight) { // On devices with a natural orientation of portrait switch (rotation) { @@ -4560,7 +4270,7 @@ public class WindowManagerService extends IWindowManager.Stub // Something changed (E.g. device rotation), but no configuration update is needed. // E.g. changing device rotation by 180 degrees. Go ahead and perform surface // placement to unfreeze the display since we froze it when the rotation was updated - // in updateRotationUncheckedLocked. + // in DisplayContent#updateRotationUnchecked. synchronized (mWindowMap) { if (mWaitingForConfig) { mWaitingForConfig = false; @@ -4585,287 +4295,9 @@ public class WindowManagerService extends IWindowManager.Stub return null; } final Configuration config = new Configuration(); - computeScreenConfigurationLocked(config, displayId); - return config; - } - - private void adjustDisplaySizeRanges(DisplayInfo displayInfo, int displayId, int rotation, - int uiMode, int dw, int dh) { - final int width = mPolicy.getConfigDisplayWidth(dw, dh, rotation, uiMode, displayId); - if (width < displayInfo.smallestNominalAppWidth) { - displayInfo.smallestNominalAppWidth = width; - } - if (width > displayInfo.largestNominalAppWidth) { - displayInfo.largestNominalAppWidth = width; - } - final int height = mPolicy.getConfigDisplayHeight(dw, dh, rotation, uiMode, displayId); - if (height < displayInfo.smallestNominalAppHeight) { - displayInfo.smallestNominalAppHeight = height; - } - if (height > displayInfo.largestNominalAppHeight) { - displayInfo.largestNominalAppHeight = height; - } - } - - private int reduceConfigLayout(int curLayout, int rotation, float density, - int dw, int dh, int uiMode, int displayId) { - // Get the app screen size at this rotation. - int w = mPolicy.getNonDecorDisplayWidth(dw, dh, rotation, uiMode, displayId); - int h = mPolicy.getNonDecorDisplayHeight(dw, dh, rotation, uiMode, displayId); - - // Compute the screen layout size class for this rotation. - int longSize = w; - int shortSize = h; - if (longSize < shortSize) { - int tmp = longSize; - longSize = shortSize; - shortSize = tmp; - } - longSize = (int)(longSize/density); - shortSize = (int)(shortSize/density); - return Configuration.reduceScreenLayout(curLayout, longSize, shortSize); - } - - private void computeSizeRangesAndScreenLayout(DisplayInfo displayInfo, int displayId, - boolean rotated, int uiMode, int dw, int dh, float density, Configuration outConfig) { - - // We need to determine the smallest width that will occur under normal - // operation. To this, start with the base screen size and compute the - // width under the different possible rotations. We need to un-rotate - // the current screen dimensions before doing this. - int unrotDw, unrotDh; - if (rotated) { - unrotDw = dh; - unrotDh = dw; - } else { - unrotDw = dw; - unrotDh = dh; - } - displayInfo.smallestNominalAppWidth = 1<<30; - displayInfo.smallestNominalAppHeight = 1<<30; - displayInfo.largestNominalAppWidth = 0; - displayInfo.largestNominalAppHeight = 0; - adjustDisplaySizeRanges(displayInfo, displayId, Surface.ROTATION_0, uiMode, unrotDw, - unrotDh); - adjustDisplaySizeRanges(displayInfo, displayId, Surface.ROTATION_90, uiMode, unrotDh, - unrotDw); - adjustDisplaySizeRanges(displayInfo, displayId, Surface.ROTATION_180, uiMode, unrotDw, - unrotDh); - adjustDisplaySizeRanges(displayInfo, displayId, Surface.ROTATION_270, uiMode, unrotDh, - unrotDw); - int sl = Configuration.resetScreenLayout(outConfig.screenLayout); - sl = reduceConfigLayout(sl, Surface.ROTATION_0, density, unrotDw, unrotDh, uiMode, - displayId); - sl = reduceConfigLayout(sl, Surface.ROTATION_90, density, unrotDh, unrotDw, uiMode, - displayId); - sl = reduceConfigLayout(sl, Surface.ROTATION_180, density, unrotDw, unrotDh, uiMode, - displayId); - sl = reduceConfigLayout(sl, Surface.ROTATION_270, density, unrotDh, unrotDw, uiMode, - displayId); - outConfig.smallestScreenWidthDp = (int)(displayInfo.smallestNominalAppWidth / density); - outConfig.screenLayout = sl; - } - - private int reduceCompatConfigWidthSize(int curSize, int rotation, int uiMode, - DisplayMetrics dm, int dw, int dh, int displayId) { - dm.noncompatWidthPixels = mPolicy.getNonDecorDisplayWidth(dw, dh, rotation, uiMode, - displayId); - dm.noncompatHeightPixels = mPolicy.getNonDecorDisplayHeight(dw, dh, rotation, uiMode, - displayId); - float scale = CompatibilityInfo.computeCompatibleScaling(dm, null); - int size = (int)(((dm.noncompatWidthPixels / scale) / dm.density) + .5f); - if (curSize == 0 || size < curSize) { - curSize = size; - } - return curSize; - } - - private int computeCompatSmallestWidth(boolean rotated, int uiMode, DisplayMetrics dm, int dw, - int dh, int displayId) { - mTmpDisplayMetrics.setTo(dm); - final DisplayMetrics tmpDm = mTmpDisplayMetrics; - final int unrotDw, unrotDh; - if (rotated) { - unrotDw = dh; - unrotDh = dw; - } else { - unrotDw = dw; - unrotDh = dh; - } - int sw = reduceCompatConfigWidthSize(0, Surface.ROTATION_0, uiMode, tmpDm, unrotDw, unrotDh, - displayId); - sw = reduceCompatConfigWidthSize(sw, Surface.ROTATION_90, uiMode, tmpDm, unrotDh, unrotDw, - displayId); - sw = reduceCompatConfigWidthSize(sw, Surface.ROTATION_180, uiMode, tmpDm, unrotDw, unrotDh, - displayId); - sw = reduceCompatConfigWidthSize(sw, Surface.ROTATION_270, uiMode, tmpDm, unrotDh, unrotDw, - displayId); - return sw; - } - - /** Do not call if mDisplayReady == false */ - private DisplayInfo updateDisplayAndOrientationLocked(int uiMode, int displayId) { final DisplayContent displayContent = mRoot.getDisplayContent(displayId); - - // Use the effective "visual" dimensions based on current rotation - final boolean rotated = (mRotation == Surface.ROTATION_90 - || mRotation == 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 (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 = mPolicy.getNonDecorDisplayWidth(dw, dh, mRotation, uiMode, displayId); - final int appHeight = mPolicy.getNonDecorDisplayHeight(dw, dh, mRotation, uiMode, - displayId); - final DisplayInfo displayInfo = displayContent.getDisplayInfo(); - displayInfo.rotation = mRotation; - displayInfo.logicalWidth = dw; - displayInfo.logicalHeight = dh; - displayInfo.logicalDensityDpi = displayContent.mBaseDisplayDensity; - displayInfo.appWidth = appWidth; - displayInfo.appHeight = appHeight; - 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); - } - - 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, mRotation, config.uiMode, displayId) / - mDisplayMetrics.density); - config.screenHeightDp = - (int)(mPolicy.getConfigDisplayHeight(dw, dh, mRotation, config.uiMode, displayId) / - mDisplayMetrics.density); - final boolean rotated = (mRotation == Surface.ROTATION_90 - || mRotation == 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() { @@ -5231,7 +4663,6 @@ public class WindowManagerService extends IWindowManager.Stub public static final int NOTIFY_APP_TRANSITION_STARTING = 47; public static final int NOTIFY_APP_TRANSITION_CANCELLED = 48; public static final int NOTIFY_APP_TRANSITION_FINISHED = 49; - public static final int NOTIFY_STARTING_WINDOW_DRAWN = 50; public static final int UPDATE_ANIMATION_SCALE = 51; public static final int WINDOW_HIDE_TIMEOUT = 52; public static final int NOTIFY_DOCKED_STACK_MINIMIZED_CHANGED = 53; @@ -5660,7 +5091,7 @@ public class WindowManagerService extends IWindowManager.Stub } } case NOTIFY_APP_TRANSITION_STARTING: { - mAmInternal.notifyAppTransitionStarting(msg.arg1); + mAmInternal.notifyAppTransitionStarting((SparseIntArray) msg.obj); } break; case NOTIFY_APP_TRANSITION_CANCELLED: { @@ -5671,10 +5102,6 @@ public class WindowManagerService extends IWindowManager.Stub mAmInternal.notifyAppTransitionFinished(); } break; - case NOTIFY_STARTING_WINDOW_DRAWN: { - mAmInternal.notifyStartingWindowDrawn(); - } - break; case WINDOW_HIDE_TIMEOUT: { final WindowState window = (WindowState) msg.obj; synchronized(mWindowMap) { @@ -6082,7 +5509,7 @@ public class WindowManagerService extends IWindowManager.Stub displayId); final Configuration currentDisplayConfig = displayContent.getConfiguration(); mTempConfiguration.setTo(currentDisplayConfig); - computeScreenConfigurationLocked(mTempConfiguration, displayId); + displayContent.computeScreenConfiguration(mTempConfiguration); configChanged |= currentDisplayConfig.diff(mTempConfiguration) != 0; if (configChanged) { @@ -6545,7 +5972,8 @@ public class WindowManagerService extends IWindowManager.Stub if (updateRotation) { if (DEBUG_ORIENTATION) Slog.d(TAG_WM, "Performing post-rotate rotation"); - configChanged |= updateRotationUncheckedLocked(false, displayId); + configChanged |= displayContent.updateRotationUnchecked( + false /* inTransaction */); } if (configChanged) { @@ -6588,8 +6016,9 @@ public class WindowManagerService extends IWindowManager.Stub String[] toks = line.split("%"); if (toks != null && toks.length > 0) { // TODO(multi-display): Show watermarks on secondary displays. - mWatermark = new Watermark(getDefaultDisplayContentLocked().getDisplay(), - mRealDisplayMetrics, mFxSession, toks); + final DisplayContent displayContent = getDefaultDisplayContentLocked(); + mWatermark = new Watermark(displayContent.getDisplay(), + displayContent.mRealDisplayMetrics, mFxSession, toks); } } } catch (FileNotFoundException e) { @@ -7002,10 +6431,14 @@ public class WindowManagerService extends IWindowManager.Stub pw.print(" client="); pw.print(mClientFreezingScreen); pw.print(" apps="); pw.print(mAppsFreezingScreen); pw.print(" waitingForConfig="); pw.println(mWaitingForConfig); - pw.print(" mRotation="); pw.print(mRotation); - pw.print(" mAltOrientation="); pw.println(mAltOrientation); - pw.print(" mLastWindowForcedOrientation="); pw.print(mLastWindowForcedOrientation); - pw.print(" mLastOrientation="); pw.println(mLastOrientation); + final DisplayContent defaultDisplayContent = getDefaultDisplayContentLocked(); + pw.print(" mRotation="); pw.print(defaultDisplayContent.getRotation()); + pw.print(" mAltOrientation="); + pw.println(defaultDisplayContent.getAltOrientation()); + pw.print(" mLastWindowForcedOrientation="); + pw.print(defaultDisplayContent.getLastWindowForcedOrientation()); + pw.print(" mLastOrientation="); + pw.println(defaultDisplayContent.getLastOrientation()); pw.print(" mDeferredRotationPauseCount="); pw.println(mDeferredRotationPauseCount); pw.print(" Animation settings: disabled="); pw.print(mAnimationsDisabled); pw.print(" window="); pw.print(mWindowAnimationScaleSetting); @@ -7621,9 +7054,10 @@ public class WindowManagerService extends IWindowManager.Stub if (DEBUG_ORIENTATION) { Slog.i(TAG, "Performing post-rotate rotation after seamless rotation"); } - final int displayId = w.getDisplayId(); - if (updateRotationUncheckedLocked(false, displayId)) { - mH.obtainMessage(H.SEND_NEW_CONFIGURATION, displayId).sendToTarget(); + final DisplayContent displayContent = w.getDisplayContent(); + if (displayContent.updateRotationUnchecked(false /* inTransaction */)) { + mH.obtainMessage(H.SEND_NEW_CONFIGURATION, displayContent.getDisplayId()) + .sendToTarget(); } } } @@ -7649,6 +7083,17 @@ public class WindowManagerService extends IWindowManager.Stub } @Override + public void setForceShowMagnifiableBounds(boolean show) { + synchronized (mWindowMap) { + if (mAccessibilityController != null) { + mAccessibilityController.setForceShowMagnifiableBoundsLocked(show); + } else { + throw new IllegalStateException("Magnification callbacks not set!"); + } + } + } + + @Override public void getMagnificationRegion(@NonNull Region magnificationRegion) { synchronized (mWindowMap) { if (mAccessibilityController != null) { diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 7825903db4bd..4e593d86f7c0 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 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP void prelayout() { if (mEnforceSizeCompat) { - mGlobalScale = mService.mCompatibleScreenScale; - mInvGlobalScale = 1/mGlobalScale; + mGlobalScale = getDisplayContent().mCompatibleScreenScale; + mInvGlobalScale = 1 / mGlobalScale; } else { mGlobalScale = mInvGlobalScale = 1; } @@ -2835,7 +2835,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP // Sometimes we save surfaces due to layout invisible directly after rotation occurs. // However this means the surface was never laid out in the new orientation. // We can only restore to the last rotation we were laid out as visible in. - if (mLastVisibleLayoutRotation != mService.mRotation) { + if (mLastVisibleLayoutRotation != getDisplayContent().getRotation()) { destroySavedSurface(); return false; } @@ -4359,7 +4359,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP mWinAnimator.mEnterAnimationPending = true; } - mLastVisibleLayoutRotation = mService.mRotation; + mLastVisibleLayoutRotation = getDisplayContent().getRotation(); mWinAnimator.mEnteringAnimation = true; if ((result & RELAYOUT_RES_FIRST_TIME) != 0) { diff --git a/services/core/java/com/android/server/wm/WindowSurfaceController.java b/services/core/java/com/android/server/wm/WindowSurfaceController.java index f7d3343831bf..b08bb70ca0b9 100644 --- a/services/core/java/com/android/server/wm/WindowSurfaceController.java +++ b/services/core/java/com/android/server/wm/WindowSurfaceController.java @@ -97,13 +97,7 @@ class WindowSurfaceController { mWindowType = windowType; mWindowSession = win.mSession; - // For opaque child windows placed under parent windows, we use a special SurfaceControl - // which mirrors commands to a black-out layer placed one Z-layer below the surface. - // This prevents holes to whatever app/wallpaper is underneath. - if (win.isChildWindow() && win.mSubLayer < 0 && win.mAppToken != null) { - mSurfaceControl = new SurfaceControlWithBackground( - s, name, w, h, format, flags, win.mAppToken, windowType, ownerUid); - } else if (DEBUG_SURFACE_TRACE) { + if (DEBUG_SURFACE_TRACE) { mSurfaceControl = new SurfaceTrace( s, name, w, h, format, flags, windowType, ownerUid); } else { @@ -566,7 +560,10 @@ class WindowSurfaceController { pw.print(" rect=("); pw.print(mSurfaceX); pw.print(","); pw.print(mSurfaceY); pw.print(") "); pw.print(mSurfaceW); - pw.print(" x "); pw.println(mSurfaceH); + pw.print(" x "); pw.print(mSurfaceH); + pw.print(" transform=("); pw.print(mLastDsdx); pw.print(", "); + pw.print(mLastDtdx); pw.print(", "); pw.print(mLastDsdy); + pw.print(", "); pw.print(mLastDtdy); pw.println(")"); } @Override @@ -831,141 +828,4 @@ class WindowSurfaceController { + " (" + mDsdx + "," + mDtdx + "," + mDsdy + "," + mDtdy + ")"; } } - - class SurfaceControlWithBackground extends SurfaceControl { - private SurfaceControl mBackgroundControl; - private boolean mOpaque = true; - private boolean mAppForcedInvisible = false; - private AppWindowToken mAppToken; - public boolean mVisible = false; - public int mLayer = -1; - - public SurfaceControlWithBackground(SurfaceSession s, String name, int w, int h, int format, - int flags, AppWindowToken token, int windowType, int ownerUid) - throws OutOfResourcesException { - super(s, name, w, h, format, flags, windowType, ownerUid); - mBackgroundControl = new SurfaceControl(s, name, w, h, - PixelFormat.OPAQUE, flags | SurfaceControl.FX_SURFACE_DIM); - mOpaque = (flags & SurfaceControl.OPAQUE) != 0; - mAppToken = token; - - mAppToken.addSurfaceViewBackground(this); - } - - @Override - public void setAlpha(float alpha) { - super.setAlpha(alpha); - mBackgroundControl.setAlpha(alpha); - } - - @Override - public void setLayer(int zorder) { - super.setLayer(zorder); - mBackgroundControl.setLayer(zorder - 1); - if (mLayer != zorder) { - mLayer = zorder; - mAppToken.updateSurfaceViewBackgroundVisibilities(); - } - } - - @Override - public void setPosition(float x, float y) { - super.setPosition(x, y); - mBackgroundControl.setPosition(x, y); - } - - @Override - public void setSize(int w, int h) { - super.setSize(w, h); - mBackgroundControl.setSize(w, h); - } - - @Override - public void setWindowCrop(Rect crop) { - super.setWindowCrop(crop); - mBackgroundControl.setWindowCrop(crop); - } - - @Override - public void setFinalCrop(Rect crop) { - super.setFinalCrop(crop); - mBackgroundControl.setFinalCrop(crop); - } - - @Override - public void setLayerStack(int layerStack) { - super.setLayerStack(layerStack); - mBackgroundControl.setLayerStack(layerStack); - } - - @Override - public void setOpaque(boolean isOpaque) { - super.setOpaque(isOpaque); - mOpaque = isOpaque; - updateBackgroundVisibility(mAppForcedInvisible); - } - - @Override - public void setSecure(boolean isSecure) { - super.setSecure(isSecure); - } - - @Override - public void setMatrix(float dsdx, float dtdx, float dsdy, float dtdy) { - super.setMatrix(dsdx, dtdx, dsdy, dtdy); - mBackgroundControl.setMatrix(dsdx, dtdx, dsdy, dtdy); - } - - @Override - public void hide() { - super.hide(); - if (mVisible) { - mVisible = false; - mAppToken.updateSurfaceViewBackgroundVisibilities(); - } - } - - @Override - public void show() { - super.show(); - if (!mVisible) { - mVisible = true; - mAppToken.updateSurfaceViewBackgroundVisibilities(); - } - } - - @Override - public void destroy() { - super.destroy(); - mBackgroundControl.destroy(); - mAppToken.removeSurfaceViewBackground(this); - } - - @Override - public void release() { - super.release(); - mBackgroundControl.release(); - } - - @Override - public void setTransparentRegionHint(Region region) { - super.setTransparentRegionHint(region); - mBackgroundControl.setTransparentRegionHint(region); - } - - @Override - public void deferTransactionUntil(IBinder handle, long frame) { - super.deferTransactionUntil(handle, frame); - mBackgroundControl.deferTransactionUntil(handle, frame); - } - - void updateBackgroundVisibility(boolean forcedInvisible) { - mAppForcedInvisible = forcedInvisible; - if (mOpaque && mVisible && !mAppForcedInvisible) { - mBackgroundControl.show(); - } else { - mBackgroundControl.hide(); - } - } - } } diff --git a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java index f247ebe62f8c..3cb96a1145fc 100644 --- a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java +++ b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java @@ -1,9 +1,10 @@ package com.android.server.wm; +import static android.app.ActivityManager.StackId.INVALID_STACK_ID; import static android.app.ActivityManagerInternal.APP_TRANSITION_SAVED_SURFACE; -import static android.app.ActivityManagerInternal.APP_TRANSITION_STARTING_WINDOW; -import static android.app.ActivityManagerInternal.APP_TRANSITION_TIMEOUT; +import static android.app.ActivityManagerInternal.APP_TRANSITION_SNAPSHOT; +import static android.app.ActivityManagerInternal.APP_TRANSITION_SPLASH_SCREEN; import static android.app.ActivityManagerInternal.APP_TRANSITION_WINDOWS_DRAWN; import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_CONFIG; import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT; @@ -45,6 +46,7 @@ import android.os.Debug; import android.os.Trace; import android.util.ArraySet; import android.util.Slog; +import android.util.SparseIntArray; import android.view.Display; import android.view.DisplayInfo; import android.view.Surface; @@ -93,6 +95,7 @@ class WindowSurfacePlacer { private final LayerAndToken mTmpLayerAndToken = new LayerAndToken(); private final ArrayList<SurfaceControl> mPendingDestroyingSurfaces = new ArrayList<>(); + private final SparseIntArray mTempTransitionReasons = new SparseIntArray(); public WindowSurfacePlacer(WindowManagerService service) { mService = service; @@ -495,7 +498,7 @@ class WindowSurfacePlacer { mService.mAnimator.getScreenRotationAnimationLocked( Display.DEFAULT_DISPLAY); - int reason = APP_TRANSITION_TIMEOUT; + final SparseIntArray reasons = mTempTransitionReasons; if (!mService.mAppTransition.isTimeout()) { // Imagine the case where we are changing orientation due to an app transition, but a previous // orientation change is still in progress. We won't process the orientation change @@ -526,11 +529,15 @@ class WindowSurfacePlacer { if (!allDrawn && !wtoken.startingDisplayed && !wtoken.startingMoved) { return false; } + final TaskStack stack = wtoken.getStack(); + final int stackId = stack != null ? stack.mStackId : INVALID_STACK_ID; if (allDrawn) { - reason = drawnBeforeRestoring ? APP_TRANSITION_WINDOWS_DRAWN - : APP_TRANSITION_SAVED_SURFACE; + reasons.put(stackId, drawnBeforeRestoring ? APP_TRANSITION_WINDOWS_DRAWN + : APP_TRANSITION_SAVED_SURFACE); } else { - reason = APP_TRANSITION_STARTING_WINDOW; + reasons.put(stackId, wtoken.startingData instanceof SplashScreenStartingData + ? APP_TRANSITION_SPLASH_SCREEN + : APP_TRANSITION_SNAPSHOT); } } @@ -552,12 +559,13 @@ class WindowSurfacePlacer { boolean wallpaperReady = !mWallpaperControllerLocked.isWallpaperVisible() || mWallpaperControllerLocked.wallpaperTransitionReady(); if (wallpaperReady) { - mService.mH.obtainMessage(NOTIFY_APP_TRANSITION_STARTING, reason, 0).sendToTarget(); + mService.mH.obtainMessage(NOTIFY_APP_TRANSITION_STARTING, reasons.clone()) + .sendToTarget(); return true; } return false; } - mService.mH.obtainMessage(NOTIFY_APP_TRANSITION_STARTING, reason, 0).sendToTarget(); + mService.mH.obtainMessage(NOTIFY_APP_TRANSITION_STARTING, reasons.clone()).sendToTarget(); return true; } diff --git a/services/core/jni/com_android_server_tv_TvInputHal.cpp b/services/core/jni/com_android_server_tv_TvInputHal.cpp index 9f528b1bec6a..b4333508a84a 100644 --- a/services/core/jni/com_android_server_tv_TvInputHal.cpp +++ b/services/core/jni/com_android_server_tv_TvInputHal.cpp @@ -81,6 +81,7 @@ static struct { jmethodID deviceId; jmethodID type; jmethodID hdmiPortId; + jmethodID cableConnectionStatus; jmethodID audioType; jmethodID audioAddress; jmethodID build; @@ -469,6 +470,9 @@ void JTvInputHal::onDeviceAvailable(const TvInputDeviceInfo& info) { builder, gTvInputHardwareInfoBuilderClassInfo.hdmiPortId, info.portId); } env->CallObjectMethod( + builder, gTvInputHardwareInfoBuilderClassInfo.cableConnectionStatus, + info.cableConnectionStatus); + env->CallObjectMethod( builder, gTvInputHardwareInfoBuilderClassInfo.audioType, info.audioType); if (info.audioType != AudioDevice::NONE) { uint8_t buffer[info.audioAddress.size() + 1]; @@ -743,6 +747,10 @@ int register_android_server_tv_TvInputHal(JNIEnv* env) { gTvInputHardwareInfoBuilderClassInfo.clazz, "hdmiPortId", "(I)Landroid/media/tv/TvInputHardwareInfo$Builder;"); GET_METHOD_ID( + gTvInputHardwareInfoBuilderClassInfo.cableConnectionStatus, + gTvInputHardwareInfoBuilderClassInfo.clazz, + "cableConnectionStatus", "(I)Landroid/media/tv/TvInputHardwareInfo$Builder;"); + GET_METHOD_ID( gTvInputHardwareInfoBuilderClassInfo.audioType, gTvInputHardwareInfoBuilderClassInfo.clazz, "audioType", "(I)Landroid/media/tv/TvInputHardwareInfo$Builder;"); diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 7ad02927d7a5..0e6a54217e79 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -3141,6 +3141,10 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { throw new IllegalArgumentException("Only apps in internal storage can be active admin: " + adminReceiver); } + if (info.getActivityInfo().applicationInfo.isInstantApp()) { + throw new IllegalArgumentException("Instant apps cannot be device admins: " + + adminReceiver); + } synchronized (this) { long ident = mInjector.binderClearCallingIdentity(); try { diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 1a0aff79be6e..a8423e2e8a11 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -195,7 +195,7 @@ public final class SystemServer { private static final String WALLPAPER_SERVICE_CLASS = "com.android.server.wallpaper.WallpaperManagerService$Lifecycle"; private static final String AUTO_FILL_MANAGER_SERVICE_CLASS = - "com.android.server.autofill.AutoFillManagerService"; + "com.android.server.autofill.AutofillManagerService"; private static final String PERSISTENT_DATA_BLOCK_PROP = "ro.frp.pst"; diff --git a/services/net/java/android/net/apf/ApfFilter.java b/services/net/java/android/net/apf/ApfFilter.java index 0a907496ebdd..a0a9310af28c 100644 --- a/services/net/java/android/net/apf/ApfFilter.java +++ b/services/net/java/android/net/apf/ApfFilter.java @@ -93,17 +93,10 @@ public class ApfFilter { class ReceiveThread extends Thread { private final byte[] mPacket = new byte[1514]; private final FileDescriptor mSocket; - private volatile boolean mStopped; - - // Starting time of the RA receiver thread. private final long mStart = SystemClock.elapsedRealtime(); + private final ApfStats mStats = new ApfStats(); - private int mReceivedRas; // Number of received RAs - private int mMatchingRas; // Number of received RAs matching a known RA - private int mDroppedRas; // Number of received RAs ignored due to the MAX_RAS limit - private int mParseErrors; // Number of received RAs that could not be parsed - private int mZeroLifetimeRas; // Number of received RAs with a 0 lifetime - private int mProgramUpdates; // Number of APF program updates triggered by receiving a RA + private volatile boolean mStopped; public ReceiveThread(FileDescriptor socket) { mSocket = socket; @@ -134,35 +127,40 @@ public class ApfFilter { } private void updateStats(ProcessRaResult result) { - mReceivedRas++; + mStats.receivedRas++; switch(result) { case MATCH: - mMatchingRas++; + mStats.matchingRas++; return; case DROPPED: - mDroppedRas++; + mStats.droppedRas++; return; case PARSE_ERROR: - mParseErrors++; + mStats.parseErrors++; return; case ZERO_LIFETIME: - mZeroLifetimeRas++; + mStats.zeroLifetimeRas++; return; case UPDATE_EXPIRY: - mMatchingRas++; - mProgramUpdates++; + mStats.matchingRas++; + mStats.programUpdates++; return; case UPDATE_NEW_RA: - mProgramUpdates++; + mStats.programUpdates++; return; } } private void logStats() { - long durationMs = SystemClock.elapsedRealtime() - mStart; - int maxSize = mApfCapabilities.maximumApfProgramSize; - mMetricsLog.log(new ApfStats(durationMs, mReceivedRas, mMatchingRas, mDroppedRas, - mZeroLifetimeRas, mParseErrors, mProgramUpdates, maxSize)); + final long nowMs = SystemClock.elapsedRealtime(); + synchronized (this) { + mStats.durationMs = nowMs - mStart; + mStats.maxProgramSize = mApfCapabilities.maximumApfProgramSize; + mStats.programUpdatesAll = mNumProgramUpdates; + mStats.programUpdatesAllowingMulticast = mNumProgramUpdatesAllowingMulticast; + mMetricsLog.log(mStats); + logApfProgramEventLocked(nowMs / DateUtils.SECOND_IN_MILLIS); + } } } @@ -218,6 +216,8 @@ public class ApfFilter { 4, // Protocol size: 4 }; private static final int ARP_TARGET_IP_ADDRESS_OFFSET = ETH_HEADER_LEN + 24; + // Do not log ApfProgramEvents whose actual lifetimes was less than this. + private static final int APF_PROGRAM_EVENT_LIFETIME_THRESHOLD = 2; private final ApfCapabilities mApfCapabilities; private final IpManager.Callback mIpManagerCallback; @@ -247,6 +247,7 @@ public class ApfFilter { mMulticastFilter = multicastFilter; mMetricsLog = log; + // TODO: ApfFilter should not generate programs until IpManager sends provisioning success. maybeStartFilter(); } @@ -661,14 +662,19 @@ public class ApfFilter { // How long should the last installed filter program live for? In seconds. @GuardedBy("this") private long mLastInstalledProgramMinLifetime; + @GuardedBy("this") + private ApfProgramEvent mLastInstallEvent; // For debugging only. The last program installed. @GuardedBy("this") private byte[] mLastInstalledProgram; - // For debugging only. How many times the program was updated since we started. + // How many times the program was updated since we started. + @GuardedBy("this") + private int mNumProgramUpdates = 0; + // How many times the program was updated since we started for allowing multicast traffic. @GuardedBy("this") - private int mNumProgramUpdates; + private int mNumProgramUpdatesAllowingMulticast = 0; /** * Generate filter code to process ARP packets. Execution of this code ends in either the @@ -947,7 +953,8 @@ public class ApfFilter { Log.e(TAG, "Failed to generate APF program.", e); return; } - mLastTimeInstalledProgram = currentTimeSeconds(); + final long now = currentTimeSeconds(); + mLastTimeInstalledProgram = now; mLastInstalledProgramMinLifetime = programMinLifetime; mLastInstalledProgram = program; mNumProgramUpdates++; @@ -956,9 +963,26 @@ public class ApfFilter { hexDump("Installing filter: ", program, program.length); } mIpManagerCallback.installPacketFilter(program); - int flags = ApfProgramEvent.flagsFor(mIPv4Address != null, mMulticastFilter); - mMetricsLog.log(new ApfProgramEvent( - programMinLifetime, rasToFilter.size(), mRas.size(), program.length, flags)); + logApfProgramEventLocked(now); + mLastInstallEvent = new ApfProgramEvent(); + mLastInstallEvent.lifetime = programMinLifetime; + mLastInstallEvent.filteredRas = rasToFilter.size(); + mLastInstallEvent.currentRas = mRas.size(); + mLastInstallEvent.programLength = program.length; + mLastInstallEvent.flags = ApfProgramEvent.flagsFor(mIPv4Address != null, mMulticastFilter); + } + + private void logApfProgramEventLocked(long now) { + if (mLastInstallEvent == null) { + return; + } + ApfProgramEvent ev = mLastInstallEvent; + mLastInstallEvent = null; + ev.actualLifetime = now - mLastTimeInstalledProgram; + if (ev.actualLifetime < APF_PROGRAM_EVENT_LIFETIME_THRESHOLD) { + return; + } + mMetricsLog.log(ev); } /** @@ -1078,11 +1102,15 @@ public class ApfFilter { mRas.clear(); } - public synchronized void setMulticastFilter(boolean enabled) { - if (mMulticastFilter != enabled) { - mMulticastFilter = enabled; - installNewProgramLocked(); + public synchronized void setMulticastFilter(boolean isEnabled) { + if (mMulticastFilter == isEnabled) { + return; + } + mMulticastFilter = isEnabled; + if (!isEnabled) { + mNumProgramUpdatesAllowingMulticast++; } + installNewProgramLocked(); } /** Find the single IPv4 LinkAddress if there is one, otherwise return null. */ diff --git a/services/net/java/android/net/ip/IpManager.java b/services/net/java/android/net/ip/IpManager.java index 76b1c90642ec..3e3a19b8effd 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.content.Context; import android.net.apf.ApfCapabilities; import android.net.apf.ApfFilter; import android.net.DhcpResults; +import android.net.INetd; import android.net.InterfaceConfiguration; import android.net.LinkAddress; import android.net.LinkProperties; @@ -34,10 +35,12 @@ import android.net.dhcp.DhcpClient; import android.net.metrics.IpConnectivityLog; import android.net.metrics.IpManagerEvent; import android.net.util.MultinetworkPolicyTracker; +import android.net.util.NetdService; import android.os.INetworkManagementService; import android.os.Message; import android.os.RemoteException; import android.os.ServiceManager; +import android.os.ServiceSpecificException; import android.os.SystemClock; import android.text.TextUtils; import android.util.LocalLog; @@ -631,6 +634,13 @@ public class IpManager extends StateMachine { pw.println(); pw.println(mTag + " connectivity packet log:"); + pw.println(); + pw.println("Debug with python and scapy via:"); + pw.println("shell$ python"); + pw.println(">>> from scapy import all as scapy"); + pw.println(">>> scapy.Ether(\"<paste_hex_string>\".decode(\"hex\")).show2()"); + pw.println(); + pw.increaseIndent(); mConnectivityPacketLog.readOnlyLocalLog().dump(fd, pw, args); pw.decreaseIndent(); @@ -1020,14 +1030,16 @@ public class IpManager extends StateMachine { 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 153cb502afca..6e69ff56c723 100644 --- a/services/net/java/android/net/util/NetdService.java +++ b/services/net/java/android/net/util/NetdService.java @@ -17,7 +17,10 @@ package android.net.util; import android.net.INetd; +import android.os.RemoteException; import android.os.ServiceManager; +import android.os.ServiceSpecificException; +import android.os.SystemClock; import android.util.Log; @@ -27,15 +30,24 @@ import android.util.Log; public class NetdService { private static final String TAG = NetdService.class.getSimpleName(); private static final String NETD_SERVICE_NAME = "netd"; + private static final long BASE_TIMEOUT_MS = 100; + private static final long MAX_TIMEOUT_MS = 1000; + /** + * Return an INetd instance, or null if not available. + * * It is the caller's responsibility to check for a null return value * and to handle RemoteException errors from invocations on the returned * interface if, for example, netd dies and is restarted. * + * Returned instances of INetd should not be cached. + * * @return an INetd instance or null. */ public static INetd getInstance() { + // NOTE: ServiceManager does no caching for the netd service, + // because netd is not one of the defined common services. final INetd netdInstance = INetd.Stub.asInterface( ServiceManager.getService(NETD_SERVICE_NAME)); if (netdInstance == null) { @@ -43,4 +55,82 @@ public class NetdService { } return netdInstance; } + + /** + * Blocks for a specified time until an INetd instance is available. + * + * It is the caller's responsibility to handle RemoteException errors + * from invocations on the returned interface if, for example, netd + * dies after this interface was returned. + * + * Returned instances of INetd should not be cached. + * + * Special values of maxTimeoutMs include: 0, meaning try to obtain an + * INetd instance only once, and -1 (or any value less than 0), meaning + * try to obtain an INetd instance indefinitely. + * + * @param maxTimeoutMs the maximum time to spend getting an INetd instance + * @return an INetd instance or null if no instance is available + * within |maxTimeoutMs| milliseconds. + */ + public static INetd get(long maxTimeoutMs) { + if (maxTimeoutMs == 0) return getInstance(); + + final long stop = (maxTimeoutMs > 0) + ? SystemClock.elapsedRealtime() + maxTimeoutMs + : Long.MAX_VALUE; + + long timeoutMs = 0; + while (true) { + final INetd netdInstance = getInstance(); + if (netdInstance != null) { + return netdInstance; + } + + final long remaining = stop - SystemClock.elapsedRealtime(); + if (remaining <= 0) break; + + // No netdInstance was received; sleep and retry. + timeoutMs = Math.min(timeoutMs + BASE_TIMEOUT_MS, MAX_TIMEOUT_MS); + timeoutMs = Math.min(timeoutMs, remaining); + try { + Thread.sleep(timeoutMs); + } catch (InterruptedException e) {} + } + return null; + } + + /** + * Blocks until an INetd instance is available. + * + * It is the caller's responsibility to handle RemoteException errors + * from invocations on the returned interface if, for example, netd + * dies after this interface was returned. + * + * Returned instances of INetd should not be cached. + * + * @return an INetd instance. + */ + public static INetd get() { + return get(-1); + } + + public static interface NetdCommand { + void run(INetd netd) throws RemoteException; + } + + /** + * Blocks until an INetd instance is availabe, and retries until either + * the command succeeds or a runtime exception is thrown. + */ + public static void run(NetdCommand cmd) { + while (true) { + try { + cmd.run(get()); + return; + } catch (RemoteException re) { + Log.e(TAG, "error communicating with netd: " + re); + } + } + } } diff --git a/services/tests/notification/src/com/android/server/notification/RankingHelperTest.java b/services/tests/notification/src/com/android/server/notification/RankingHelperTest.java index 450f9b638c2e..5f215f937a97 100644 --- a/services/tests/notification/src/com/android/server/notification/RankingHelperTest.java +++ b/services/tests/notification/src/com/android/server/notification/RankingHelperTest.java @@ -427,13 +427,8 @@ public class RankingHelperTest { public void testCreateChannel_blocked() throws Exception { mHelper.setImportance(pkg, uid, NotificationManager.IMPORTANCE_NONE); - try { - mHelper.createNotificationChannel(pkg, uid, - new NotificationChannel(pkg, "", IMPORTANCE_LOW), true); - fail("Channel creation should fail"); - } catch (IllegalArgumentException e) { - // pass - } + mHelper.createNotificationChannel(pkg, uid, + new NotificationChannel(pkg, "bananas", IMPORTANCE_LOW), true); } @Test diff --git a/services/tests/servicestests/src/com/android/server/AccessibilityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/AccessibilityManagerServiceTest.java index 60842a6bf727..340c62405814 100644 --- a/services/tests/servicestests/src/com/android/server/AccessibilityManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/AccessibilityManagerServiceTest.java @@ -34,6 +34,8 @@ import android.view.accessibility.AccessibilityManager; import android.view.accessibility.IAccessibilityManager; import android.view.accessibility.IAccessibilityManagerClient; +import com.android.internal.util.IntPair; + /** * This test exercises the * {@link com.android.server.accessibility.AccessibilityManagerService} by mocking the @@ -109,7 +111,7 @@ public class AccessibilityManagerServiceTest extends AndroidTestCase { // invoke the method under test final int stateFlagsDisabled = - mManagerService.addClient(mockClient, UserHandle.USER_CURRENT); + IntPair.first(mManagerService.addClient(mockClient, UserHandle.USER_CURRENT)); boolean enabledAccessibilityDisabled = (stateFlagsDisabled & AccessibilityManager.STATE_FLAG_ACCESSIBILITY_ENABLED) != 0; @@ -122,7 +124,7 @@ public class AccessibilityManagerServiceTest extends AndroidTestCase { // invoke the method under test final int stateFlagsEnabled = - mManagerService.addClient(mockClient, UserHandle.USER_CURRENT); + IntPair.first(mManagerService.addClient(mockClient, UserHandle.USER_CURRENT)); boolean enabledAccessibilityEnabled = (stateFlagsEnabled & AccessibilityManager.STATE_FLAG_ACCESSIBILITY_ENABLED) != 0; @@ -144,7 +146,7 @@ public class AccessibilityManagerServiceTest extends AndroidTestCase { // invoke the method under test final int stateFlagsEnabled = - mManagerService.addClient(mockClient, UserHandle.USER_CURRENT); + IntPair.first(mManagerService.addClient(mockClient, UserHandle.USER_CURRENT)); boolean enabledAccessibilityEnabled = (stateFlagsEnabled & AccessibilityManager.STATE_FLAG_ACCESSIBILITY_ENABLED) != 0; @@ -157,7 +159,7 @@ public class AccessibilityManagerServiceTest extends AndroidTestCase { // invoke the method under test final int stateFlagsDisabled = - mManagerService.addClient(mockClient, UserHandle.USER_CURRENT); + IntPair.first(mManagerService.addClient(mockClient, UserHandle.USER_CURRENT)); boolean enabledAccessibilityDisabled = (stateFlagsDisabled & AccessibilityManager.STATE_FLAG_ACCESSIBILITY_ENABLED) != 0; @@ -572,8 +574,9 @@ public class AccessibilityManagerServiceTest extends AndroidTestCase { public void notifyServicesStateChanged() {} - public void setTouchExplorationEnabled(boolean enabled) { - } + public void setRelevantEventTypes(int eventTypes) {} + + public void setTouchExplorationEnabled(boolean enabled) {} } /** diff --git a/services/tests/servicestests/src/com/android/server/AccessibilityManagerTest.java b/services/tests/servicestests/src/com/android/server/AccessibilityManagerTest.java index 6e3e6c6278a9..92617716e28d 100644 --- a/services/tests/servicestests/src/com/android/server/AccessibilityManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/AccessibilityManagerTest.java @@ -18,7 +18,6 @@ package com.android.server; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyInt; -import static org.mockito.Matchers.eq; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -32,6 +31,8 @@ import android.view.accessibility.AccessibilityManager; import android.view.accessibility.IAccessibilityManager; import android.view.accessibility.IAccessibilityManagerClient; +import com.android.internal.util.IntPair; + import org.mockito.Mock; import org.mockito.MockitoAnnotations; @@ -60,10 +61,12 @@ public class AccessibilityManagerTest extends AndroidTestCase { private AccessibilityManager createManager(boolean enabled) throws Exception { if (enabled) { when(mMockService.addClient(any(IAccessibilityManagerClient.class), anyInt())) - .thenReturn(AccessibilityManager.STATE_FLAG_ACCESSIBILITY_ENABLED); + .thenReturn( + IntPair.of(AccessibilityManager.STATE_FLAG_ACCESSIBILITY_ENABLED, + AccessibilityEvent.TYPES_ALL_MASK)); } else { when(mMockService.addClient(any(IAccessibilityManagerClient.class), anyInt())) - .thenReturn(0); + .thenReturn(IntPair.of(0, AccessibilityEvent.TYPES_ALL_MASK)); } AccessibilityManager manager = diff --git a/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java b/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java index fa0bd392f75d..72fb78e89ea2 100644 --- a/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java +++ b/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java @@ -61,7 +61,6 @@ public class DexManagerTests { @Before public void setup() { - mUser0 = 0; mUser1 = 1; @@ -352,6 +351,15 @@ public class DexManagerTests { assertNull(pui); } + @Test + public void testNotifyFrameworkLoad() { + String frameworkDex = "/system/framework/com.android.location.provider.jar"; + // Load a dex file from framework. + notifyDexLoad(mFooUser0, Arrays.asList(frameworkDex), mUser0); + // The dex file should not be recognized as a package. + assertNull(mDexManager.getPackageUseInfo(frameworkDex)); + } + private void assertSecondaryUse(TestData testData, PackageUseInfo pui, List<String> secondaries, boolean isUsedByOtherApps, int ownerUserId) { for (String dex : secondaries) { diff --git a/services/tests/servicestests/src/com/android/server/policy/AccessibilityShortcutControllerTest.java b/services/tests/servicestests/src/com/android/server/policy/AccessibilityShortcutControllerTest.java index 8329d68bd1b9..4d5f783bc238 100644 --- a/services/tests/servicestests/src/com/android/server/policy/AccessibilityShortcutControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/policy/AccessibilityShortcutControllerTest.java @@ -21,9 +21,11 @@ import android.app.AlertDialog; import android.content.ComponentName; import android.content.Context; import android.content.DialogInterface; +import android.content.pm.ApplicationInfo; import android.content.pm.ResolveInfo; import android.content.res.Resources; import android.os.Handler; +import android.os.Vibrator; import android.provider.Settings; import android.support.test.runner.AndroidJUnit4; @@ -54,6 +56,7 @@ import static android.provider.Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SER import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertTrue; +import static org.mockito.AdditionalMatchers.aryEq; import static org.mockito.Matchers.anyBoolean; import static org.mockito.Matchers.anyInt; import static org.mockito.Matchers.anyObject; @@ -67,6 +70,11 @@ import static org.mockito.Mockito.when; @RunWith(AndroidJUnit4.class) public class AccessibilityShortcutControllerTest { private static final String SERVICE_NAME_STRING = "fake.package/fake.service.name"; + private static final long VIBRATOR_PATTERN_1 = 100L; + private static final long VIBRATOR_PATTERN_2 = 150L; + private static final int[] VIBRATOR_PATTERN_INT = {(int) VIBRATOR_PATTERN_1, + (int) VIBRATOR_PATTERN_2}; + private static final long[] VIBRATOR_PATTERN_LONG = {VIBRATOR_PATTERN_1, VIBRATOR_PATTERN_2}; private @Mock Context mContext; private @Mock FrameworkObjectProvider mFrameworkObjectProvider; @@ -77,6 +85,8 @@ public class AccessibilityShortcutControllerTest { private @Mock AccessibilityServiceInfo mServiceInfo; private @Mock Resources mResources; private @Mock Toast mToast; + private @Mock Vibrator mVibrator; + private @Mock ApplicationInfo mApplicationInfo; private MockContentResolver mContentResolver; private WindowManager.LayoutParams mLayoutParams = new WindowManager.LayoutParams(); @@ -85,10 +95,15 @@ public class AccessibilityShortcutControllerTest { public void setUp() throws Exception { MockitoAnnotations.initMocks(this); + when(mVibrator.hasVibrator()).thenReturn(true); + + when(mContext.getResources()).thenReturn(mResources); + when(mContext.getApplicationInfo()).thenReturn(mApplicationInfo); + when(mContext.getSystemService(Context.VIBRATOR_SERVICE)).thenReturn(mVibrator); + mContentResolver = new MockContentResolver(mContext); mContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider()); when(mContext.getContentResolver()).thenReturn(mContentResolver); - when(mContext.getResources()).thenReturn(mResources); when(mAccessibilityManagerService.getInstalledAccessibilityServiceList(anyInt())) .thenReturn(Collections.singletonList(mServiceInfo)); @@ -104,6 +119,8 @@ public class AccessibilityShortcutControllerTest { .thenReturn(mToast); when(mResources.getString(anyInt())).thenReturn("Howdy %s"); + when(mResources.getIntArray(anyInt())).thenReturn(VIBRATOR_PATTERN_INT); + ResolveInfo resolveInfo = mock(ResolveInfo.class); when(resolveInfo.loadLabel(anyObject())).thenReturn("Service name"); when(mServiceInfo.getResolveInfo()).thenReturn(resolveInfo); @@ -171,6 +188,14 @@ public class AccessibilityShortcutControllerTest { } @Test + public void testOnAccessibilityShortcut_vibrates() { + configureShortcutEnabled(); + AccessibilityShortcutController accessibilityShortcutController = getController(); + accessibilityShortcutController.performAccessibilityShortcut(); + verify(mVibrator).vibrate(aryEq(VIBRATOR_PATTERN_LONG), eq(-1), anyObject()); + } + + @Test public void testOnAccessibilityShortcut_firstTime_showsWarningDialog() throws Exception { configureShortcutEnabled(); diff --git a/services/tests/servicestests/src/com/android/server/wm/AppWindowTokenTests.java b/services/tests/servicestests/src/com/android/server/wm/AppWindowTokenTests.java index e15d40eecbc0..9f50a2c30aae 100644 --- a/services/tests/servicestests/src/com/android/server/wm/AppWindowTokenTests.java +++ b/services/tests/servicestests/src/com/android/server/wm/AppWindowTokenTests.java @@ -112,7 +112,7 @@ public class AppWindowTokenTests extends WindowTestsBase { appWindowToken.setOrientation(SCREEN_ORIENTATION_LANDSCAPE); sWm.updateOrientationFromAppTokens(sDisplayContent.getOverrideConfiguration(), null, sDisplayContent.getDisplayId()); - assertEquals(SCREEN_ORIENTATION_LANDSCAPE, sWm.mLastOrientation); + assertEquals(SCREEN_ORIENTATION_LANDSCAPE, sDisplayContent.getLastOrientation()); appWindow.resizeReported = false; // Update the orientation to perform 180 degree rotation and check that resize was reported. @@ -120,7 +120,7 @@ public class AppWindowTokenTests extends WindowTestsBase { sWm.updateOrientationFromAppTokens(sDisplayContent.getOverrideConfiguration(), null, sDisplayContent.getDisplayId()); sWm.mRoot.performSurfacePlacement(false /* recoveringMemory */); - assertEquals(SCREEN_ORIENTATION_REVERSE_LANDSCAPE, sWm.mLastOrientation); + assertEquals(SCREEN_ORIENTATION_REVERSE_LANDSCAPE, sDisplayContent.getLastOrientation()); assertTrue(appWindow.resizeReported); appWindow.removeImmediately(); } diff --git a/services/usage/java/com/android/server/usage/StorageStatsService.java b/services/usage/java/com/android/server/usage/StorageStatsService.java index 8166c154493a..169cf4d750d5 100644 --- a/services/usage/java/com/android/server/usage/StorageStatsService.java +++ b/services/usage/java/com/android/server/usage/StorageStatsService.java @@ -28,6 +28,7 @@ import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.PackageStats; import android.content.pm.UserInfo; +import android.net.TrafficStats; import android.os.Binder; import android.os.Environment; import android.os.FileUtils; @@ -43,6 +44,7 @@ import android.os.storage.StorageManager; import android.os.storage.VolumeInfo; import android.provider.Settings; import android.text.format.DateUtils; +import android.util.ArrayMap; import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; @@ -56,6 +58,7 @@ import com.android.server.pm.Installer.InstallerException; import com.android.server.storage.CacheQuotaStrategy; import java.io.IOException; +import java.util.Map; public class StorageStatsService extends IStorageStatsManager.Stub { private static final String TAG = "StorageStatsService"; @@ -84,6 +87,7 @@ public class StorageStatsService extends IStorageStatsManager.Stub { private final UserManager mUser; private final PackageManager mPackage; private final StorageManager mStorage; + private final Map<String, Map<Integer, Long>> mCacheQuotas; private final Installer mInstaller; private final H mHandler; @@ -94,13 +98,14 @@ public class StorageStatsService extends IStorageStatsManager.Stub { mUser = Preconditions.checkNotNull(context.getSystemService(UserManager.class)); mPackage = Preconditions.checkNotNull(context.getPackageManager()); mStorage = Preconditions.checkNotNull(context.getSystemService(StorageManager.class)); + mCacheQuotas = new ArrayMap<>(); mInstaller = new Installer(context); mInstaller.onStart(); 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 @@ -132,7 +137,8 @@ public class StorageStatsService extends IStorageStatsManager.Stub { android.Manifest.permission.PACKAGE_USAGE_STATS, TAG); return; default: - throw new SecurityException("Blocked by mode " + mode); + throw new SecurityException("Package " + callingPackage + " from UID " + callingUid + + " blocked by mode " + mode); } } @@ -164,17 +170,37 @@ public class StorageStatsService extends IStorageStatsManager.Stub { enforcePermission(Binder.getCallingUid(), callingPackage); long cacheBytes = 0; - for (UserInfo user : mUser.getUsers()) { - final StorageStats stats = queryStatsForUser(volumeUuid, user.id, null); - cacheBytes += stats.cacheBytes; + final long token = Binder.clearCallingIdentity(); + try { + for (UserInfo user : mUser.getUsers()) { + final StorageStats stats = queryStatsForUser(volumeUuid, user.id, null); + cacheBytes += stats.cacheBytes; + } + } finally { + Binder.restoreCallingIdentity(token); } if (volumeUuid == StorageManager.UUID_PRIVATE_INTERNAL) { - return Environment.getDataDirectory().getFreeSpace() + cacheBytes; + return Environment.getDataDirectory().getUsableSpace() + cacheBytes; } else { final VolumeInfo vol = mStorage.findVolumeByUuid(volumeUuid); - return vol.getPath().getFreeSpace() + cacheBytes; + return vol.getPath().getUsableSpace() + cacheBytes; + } + } + + @Override + public long getCacheQuotaBytes(String volumeUuid, int uid, String callingPackage) { + enforcePermission(Binder.getCallingUid(), callingPackage); + + if (mCacheQuotas.containsKey(volumeUuid)) { + // TODO: Change to SparseLongArray. + Map<Integer, Long> uidMap = mCacheQuotas.get(volumeUuid); + if (uidMap.containsKey(uid)) { + return uidMap.get(uid); + } } + + return 64 * TrafficStats.MB_IN_BYTES; } @Override @@ -188,7 +214,8 @@ public class StorageStatsService extends IStorageStatsManager.Stub { final ApplicationInfo appInfo; try { - appInfo = mPackage.getApplicationInfoAsUser(packageName, 0, userId); + appInfo = mPackage.getApplicationInfoAsUser(packageName, + PackageManager.MATCH_UNINSTALLED_PACKAGES, userId); } catch (NameNotFoundException e) { throw new IllegalStateException(e); } @@ -231,8 +258,8 @@ public class StorageStatsService extends IStorageStatsManager.Stub { for (int i = 0; i < packageNames.length; i++) { try { - codePaths[i] = mPackage.getApplicationInfoAsUser(packageNames[i], 0, - userId).getCodePath(); + codePaths[i] = mPackage.getApplicationInfoAsUser(packageNames[i], + PackageManager.MATCH_UNINSTALLED_PACKAGES, userId).getCodePath(); } catch (NameNotFoundException e) { throw new IllegalStateException(e); } @@ -264,7 +291,8 @@ public class StorageStatsService extends IStorageStatsManager.Stub { } int[] appIds = null; - for (ApplicationInfo app : mPackage.getInstalledApplicationsAsUser(0, userId)) { + for (ApplicationInfo app : mPackage.getInstalledApplicationsAsUser( + PackageManager.MATCH_UNINSTALLED_PACKAGES, userId)) { final int appId = UserHandle.getAppId(app.uid); if (!ArrayUtils.contains(appIds, appId)) { appIds = ArrayUtils.appendInt(appIds, appId); @@ -433,7 +461,7 @@ public class StorageStatsService extends IStorageStatsManager.Stub { private CacheQuotaStrategy getInitializedStrategy() { UsageStatsManagerInternal usageStatsManager = LocalServices.getService(UsageStatsManagerInternal.class); - return new CacheQuotaStrategy(mContext, usageStatsManager, mInstaller); + return new CacheQuotaStrategy(mContext, usageStatsManager, mInstaller, mCacheQuotas); } } diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index 632a1d63d99e..7a226a04d7eb 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -1312,6 +1312,84 @@ public class CarrierConfigManager { public static final String KEY_CALL_FORWARDING_BLOCKS_WHILE_ROAMING_STRING_ARRAY = "call_forwarding_blocks_while_roaming_string_array"; + /** + * The day of the month (1-31) on which the data cycle rolls over. + * <p> + * If the current month does not have this day, the cycle will roll over at the start of the + * next month. + * <p> + * This setting may be still overridden by explicit user choice. By default, the platform value + * will be used. + */ + public static final String KEY_MONTHLY_DATA_CYCLE_DAY_INT = + "monthly_data_cycle_day_int"; + + /** + * When {@link #KEY_MONTHLY_DATA_CYCLE_DAY_INT}, {@link #KEY_DATA_LIMIT_THRESHOLD_BYTES_LONG}, + * or {@link #KEY_DATA_WARNING_THRESHOLD_BYTES_LONG} are set to this value, the platform default + * value will be used for that key. + * + * @hide + */ + public static final int DATA_CYCLE_USE_PLATFORM_DEFAULT = -1; + + /** + * Flag indicating that a data cycle threshold should be disabled. + * <p> + * If {@link #KEY_DATA_WARNING_THRESHOLD_BYTES_LONG} is set to this value, the platform's + * default data warning, if one exists, will be disabled. A user selected data warning will not + * be overridden. + * <p> + * If {@link #KEY_DATA_LIMIT_THRESHOLD_BYTES_LONG} is set to this value, the platform's + * default data limit, if one exists, will be disabled. A user selected data limit will not be + * overridden. + */ + public static final int DATA_CYCLE_THRESHOLD_DISABLED = -2; + + /** + * Controls the data usage warning. + * <p> + * If the user uses more than this amount of data in their billing cycle, as defined by + * {@link #KEY_MONTHLY_DATA_CYCLE_DAY_INT}, the user will be alerted about the usage. + * If the value is set to {@link #DATA_CYCLE_THRESHOLD_DISABLED}, the data usage warning will + * be disabled. + * <p> + * This setting may be overridden by explicit user choice. By default, the platform value + * will be used. + */ + public static final String KEY_DATA_WARNING_THRESHOLD_BYTES_LONG = + "data_warning_threshold_bytes_long"; + + /** + * Controls the cellular data limit. + * <p> + * If the user uses more than this amount of data in their billing cycle, as defined by + * {@link #KEY_MONTHLY_DATA_CYCLE_DAY_INT}, cellular data will be turned off by the user's + * phone. If the value is set to {@link #DATA_CYCLE_THRESHOLD_DISABLED}, the data limit will be + * disabled. + * <p> + * This setting may be overridden by explicit user choice. By default, the platform value + * will be used. + */ + public static final String KEY_DATA_LIMIT_THRESHOLD_BYTES_LONG = + "data_limit_threshold_bytes_long"; + + /** + * Offset to be reduced from rsrp threshold while calculating signal strength level. + * @hide + */ + public static final String KEY_LTE_EARFCNS_RSRP_BOOST_INT = "lte_earfcns_rsrp_boost_int"; + + /** + * List of EARFCN (E-UTRA Absolute Radio Frequency Channel Number, + * Reference: 3GPP TS 36.104 5.4.3) inclusive ranges on which lte_rsrp_boost_int + * will be applied. Format of the String array is expected to be {"erafcn1_start-earfcn1_end", + * "earfcn2_start-earfcn2_end" ... } + * @hide + */ + public static final String KEY_BOOSTED_LTE_EARFCNS_STRING_ARRAY = + "boosted_lte_earfcns_string_array"; + /** The default value for every variable. */ private final static PersistableBundle sDefaults; @@ -1515,6 +1593,10 @@ public class CarrierConfigManager { }); sDefaults.putStringArray(KEY_CARRIER_DEFAULT_REDIRECTION_URL_STRING_ARRAY, null); + sDefaults.putInt(KEY_MONTHLY_DATA_CYCLE_DAY_INT, DATA_CYCLE_USE_PLATFORM_DEFAULT); + sDefaults.putLong(KEY_DATA_WARNING_THRESHOLD_BYTES_LONG, DATA_CYCLE_USE_PLATFORM_DEFAULT); + sDefaults.putLong(KEY_DATA_LIMIT_THRESHOLD_BYTES_LONG, DATA_CYCLE_USE_PLATFORM_DEFAULT); + // Rat families: {GPRS, EDGE}, {EVDO, EVDO_A, EVDO_B}, {UMTS, HSPA, HSDPA, HSUPA, HSPAP}, // {LTE, LTE_CA} // Order is important - lowest precidence first @@ -1541,6 +1623,8 @@ public class CarrierConfigManager { sDefaults.putBoolean(KEY_EDITABLE_TETHER_APN_BOOL, false); sDefaults.putStringArray(KEY_CALL_FORWARDING_BLOCKS_WHILE_ROAMING_STRING_ARRAY, null); + sDefaults.putInt(KEY_LTE_EARFCNS_RSRP_BOOST_INT, 0); + sDefaults.putStringArray(KEY_BOOSTED_LTE_EARFCNS_STRING_ARRAY, null); } /** diff --git a/telephony/java/android/telephony/PhoneNumberUtils.java b/telephony/java/android/telephony/PhoneNumberUtils.java index a3e11c8c99f8..199a12ae70df 100644 --- a/telephony/java/android/telephony/PhoneNumberUtils.java +++ b/telephony/java/android/telephony/PhoneNumberUtils.java @@ -1620,7 +1620,7 @@ public class PhoneNumberUtils // // Australia: Short codes are six or eight digits in length, starting with the prefix "19" // followed by an additional four or six digits and two. - // Czech Republic: Codes are seven digits in length for MO and five (not billed) or + // Czechia: Codes are seven digits in length for MO and five (not billed) or // eight (billed) for MT direction // // see http://en.wikipedia.org/wiki/Short_code#Regional_differences for reference diff --git a/telephony/java/android/telephony/ServiceState.java b/telephony/java/android/telephony/ServiceState.java index 7a83979ef61a..5fb83ab42866 100644 --- a/telephony/java/android/telephony/ServiceState.java +++ b/telephony/java/android/telephony/ServiceState.java @@ -243,6 +243,10 @@ public class ServiceState implements Parcelable { private boolean mIsUsingCarrierAggregation; + /* EARFCN stands for E-UTRA Absolute Radio Frequency Channel Number, + * Reference: 3GPP TS 36.104 5.4.3 */ + private int mLteEarfcnRsrpBoost = 0; + /** * get String description of roaming type * @hide @@ -322,6 +326,7 @@ public class ServiceState implements Parcelable { mIsEmergencyOnly = s.mIsEmergencyOnly; mIsDataRoamingFromRegistration = s.mIsDataRoamingFromRegistration; mIsUsingCarrierAggregation = s.mIsUsingCarrierAggregation; + mLteEarfcnRsrpBoost = s.mLteEarfcnRsrpBoost; } /** @@ -351,6 +356,7 @@ public class ServiceState implements Parcelable { mIsEmergencyOnly = in.readInt() != 0; mIsDataRoamingFromRegistration = in.readInt() != 0; mIsUsingCarrierAggregation = in.readInt() != 0; + mLteEarfcnRsrpBoost = in.readInt(); } public void writeToParcel(Parcel out, int flags) { @@ -377,6 +383,7 @@ public class ServiceState implements Parcelable { out.writeInt(mIsEmergencyOnly ? 1 : 0); out.writeInt(mIsDataRoamingFromRegistration ? 1 : 0); out.writeInt(mIsUsingCarrierAggregation ? 1 : 0); + out.writeInt(mLteEarfcnRsrpBoost); } public int describeContents() { @@ -814,7 +821,8 @@ public class ServiceState implements Parcelable { + " DefRoamInd=" + mCdmaDefaultRoamingIndicator + " EmergOnly=" + mIsEmergencyOnly + " IsDataRoamingFromRegistration=" + mIsDataRoamingFromRegistration - + " IsUsingCarrierAggregation=" + mIsUsingCarrierAggregation); + + " IsUsingCarrierAggregation=" + mIsUsingCarrierAggregation + + " mLteEarfcnRsrpBoost=" + mLteEarfcnRsrpBoost); } private void setNullState(int state) { @@ -842,6 +850,7 @@ public class ServiceState implements Parcelable { mIsEmergencyOnly = false; mIsDataRoamingFromRegistration = false; mIsUsingCarrierAggregation = false; + mLteEarfcnRsrpBoost = 0; } public void setStateOutOfService() { @@ -1016,6 +1025,7 @@ public class ServiceState implements Parcelable { mIsEmergencyOnly = m.getBoolean("emergencyOnly"); mIsDataRoamingFromRegistration = m.getBoolean("isDataRoamingFromRegistration"); mIsUsingCarrierAggregation = m.getBoolean("isUsingCarrierAggregation"); + mLteEarfcnRsrpBoost = m.getInt("LteEarfcnRsrpBoost"); } /** @@ -1046,6 +1056,7 @@ public class ServiceState implements Parcelable { m.putBoolean("emergencyOnly", mIsEmergencyOnly); m.putBoolean("isDataRoamingFromRegistration", mIsDataRoamingFromRegistration); m.putBoolean("isUsingCarrierAggregation", mIsUsingCarrierAggregation); + m.putInt("LteEarfcnRsrpBoost", mLteEarfcnRsrpBoost); } /** @hide */ @@ -1081,6 +1092,16 @@ public class ServiceState implements Parcelable { } /** @hide */ + public int getLteEarfcnRsrpBoost() { + return mLteEarfcnRsrpBoost; + } + + /** @hide */ + public void setLteEarfcnRsrpBoost(int LteEarfcnRsrpBoost) { + mLteEarfcnRsrpBoost = LteEarfcnRsrpBoost; + } + + /** @hide */ public void setCssIndicator(int css) { this.mCssIndicator = (css != 0); } diff --git a/telephony/java/android/telephony/SignalStrength.java b/telephony/java/android/telephony/SignalStrength.java index c484fd31c467..9e02399338fc 100644 --- a/telephony/java/android/telephony/SignalStrength.java +++ b/telephony/java/android/telephony/SignalStrength.java @@ -64,6 +64,8 @@ public class SignalStrength implements Parcelable { private int mLteRsrq; private int mLteRssnr; private int mLteCqi; + private int mLteRsrpBoost; // offset to be reduced from the rsrp threshold while calculating + // signal strength level private int mTdScdmaRscp; private boolean isGsm; // This value is set by the ServiceStateTracker onSignalStrengthResult @@ -104,6 +106,7 @@ public class SignalStrength implements Parcelable { mLteRsrq = INVALID; mLteRssnr = INVALID; mLteCqi = INVALID; + mLteRsrpBoost = 0; mTdScdmaRscp = INVALID; isGsm = true; } @@ -129,6 +132,7 @@ public class SignalStrength implements Parcelable { mLteRsrq = INVALID; mLteRssnr = INVALID; mLteCqi = INVALID; + mLteRsrpBoost = 0; mTdScdmaRscp = INVALID; isGsm = gsmFlag; } @@ -142,10 +146,26 @@ public class SignalStrength implements Parcelable { int cdmaDbm, int cdmaEcio, int evdoDbm, int evdoEcio, int evdoSnr, int lteSignalStrength, int lteRsrp, int lteRsrq, int lteRssnr, int lteCqi, + int lteRsrpBoost, int tdScdmaRscp, boolean gsmFlag) { + initialize(gsmSignalStrength, gsmBitErrorRate, cdmaDbm, cdmaEcio, + evdoDbm, evdoEcio, evdoSnr, lteSignalStrength, lteRsrp, + lteRsrq, lteRssnr, lteCqi, lteRsrpBoost, gsmFlag); + mTdScdmaRscp = tdScdmaRscp; + } + + /** + * Constructor + * + * @hide + */ + public SignalStrength(int gsmSignalStrength, int gsmBitErrorRate, + int cdmaDbm, int cdmaEcio, + int evdoDbm, int evdoEcio, int evdoSnr, + int lteSignalStrength, int lteRsrp, int lteRsrq, int lteRssnr, int lteCqi, int tdScdmaRscp, boolean gsmFlag) { initialize(gsmSignalStrength, gsmBitErrorRate, cdmaDbm, cdmaEcio, evdoDbm, evdoEcio, evdoSnr, lteSignalStrength, lteRsrp, - lteRsrq, lteRssnr, lteCqi, gsmFlag); + lteRsrq, lteRssnr, lteCqi, 0, gsmFlag); mTdScdmaRscp = tdScdmaRscp; } @@ -161,7 +181,7 @@ public class SignalStrength implements Parcelable { boolean gsmFlag) { initialize(gsmSignalStrength, gsmBitErrorRate, cdmaDbm, cdmaEcio, evdoDbm, evdoEcio, evdoSnr, lteSignalStrength, lteRsrp, - lteRsrq, lteRssnr, lteCqi, gsmFlag); + lteRsrq, lteRssnr, lteCqi, 0, gsmFlag); } /** @@ -175,7 +195,7 @@ public class SignalStrength implements Parcelable { boolean gsmFlag) { initialize(gsmSignalStrength, gsmBitErrorRate, cdmaDbm, cdmaEcio, evdoDbm, evdoEcio, evdoSnr, 99, INVALID, - INVALID, INVALID, INVALID, gsmFlag); + INVALID, INVALID, INVALID, 0, gsmFlag); } /** @@ -209,7 +229,7 @@ public class SignalStrength implements Parcelable { boolean gsm) { initialize(gsmSignalStrength, gsmBitErrorRate, cdmaDbm, cdmaEcio, evdoDbm, evdoEcio, evdoSnr, 99, INVALID, - INVALID, INVALID, INVALID, gsm); + INVALID, INVALID, INVALID, 0, gsm); } /** @@ -227,6 +247,7 @@ public class SignalStrength implements Parcelable { * @param lteRsrq * @param lteRssnr * @param lteCqi + * @param lteRsrpBoost * @param gsm * * @hide @@ -235,7 +256,7 @@ public class SignalStrength implements Parcelable { int cdmaDbm, int cdmaEcio, int evdoDbm, int evdoEcio, int evdoSnr, int lteSignalStrength, int lteRsrp, int lteRsrq, int lteRssnr, int lteCqi, - boolean gsm) { + int lteRsrpBoost, boolean gsm) { mGsmSignalStrength = gsmSignalStrength; mGsmBitErrorRate = gsmBitErrorRate; mCdmaDbm = cdmaDbm; @@ -248,6 +269,7 @@ public class SignalStrength implements Parcelable { mLteRsrq = lteRsrq; mLteRssnr = lteRssnr; mLteCqi = lteCqi; + mLteRsrpBoost = lteRsrpBoost; mTdScdmaRscp = INVALID; isGsm = gsm; if (DBG) log("initialize: " + toString()); @@ -269,6 +291,7 @@ public class SignalStrength implements Parcelable { mLteRsrq = s.mLteRsrq; mLteRssnr = s.mLteRssnr; mLteCqi = s.mLteCqi; + mLteRsrpBoost = s.mLteRsrpBoost; mTdScdmaRscp = s.mTdScdmaRscp; isGsm = s.isGsm; } @@ -293,6 +316,7 @@ public class SignalStrength implements Parcelable { mLteRsrq = in.readInt(); mLteRssnr = in.readInt(); mLteCqi = in.readInt(); + mLteRsrpBoost = in.readInt(); mTdScdmaRscp = in.readInt(); isGsm = (in.readInt() != 0); } @@ -340,6 +364,7 @@ public class SignalStrength implements Parcelable { out.writeInt(mLteRsrq); out.writeInt(mLteRssnr); out.writeInt(mLteCqi); + out.writeInt(mLteRsrpBoost); out.writeInt(mTdScdmaRscp); out.writeInt(isGsm ? 1 : 0); } @@ -416,6 +441,18 @@ public class SignalStrength implements Parcelable { } /** + * @param lteRsrpBoost - signal strength offset + * + * Used by phone to set the lte signal strength offset which will be + * reduced from rsrp threshold while calculating signal strength level + * + * @hide + */ + public void setLteRsrpBoost(int lteRsrpBoost) { + mLteRsrpBoost = lteRsrpBoost; + } + + /** * Get the GSM Signal Strength, valid values are (0-31, 99) as defined in TS * 27.007 8.5 */ @@ -490,6 +527,11 @@ public class SignalStrength implements Parcelable { return mLteCqi; } + /** @hide */ + public int getLteRsrpBoost() { + return mLteRsrpBoost; + } + /** * Retrieve an abstract level value for the overall signal strength. * @@ -793,12 +835,19 @@ public class SignalStrength implements Parcelable { Log.wtf(LOG_TAG, "getLteLevel - config_lteDbmThresholds has invalid num of elements." + " Cannot evaluate RSRP signal."); } else { - if (mLteRsrp > threshRsrp[5]) rsrpIconLevel = -1; - else if (mLteRsrp >= threshRsrp[4]) rsrpIconLevel = SIGNAL_STRENGTH_GREAT; - else if (mLteRsrp >= threshRsrp[3]) rsrpIconLevel = SIGNAL_STRENGTH_GOOD; - else if (mLteRsrp >= threshRsrp[2]) rsrpIconLevel = SIGNAL_STRENGTH_MODERATE; - else if (mLteRsrp >= threshRsrp[1]) rsrpIconLevel = SIGNAL_STRENGTH_POOR; - else if (mLteRsrp >= threshRsrp[0]) rsrpIconLevel = SIGNAL_STRENGTH_NONE_OR_UNKNOWN; + if (mLteRsrp > threshRsrp[5]) { + rsrpIconLevel = -1; + } else if (mLteRsrp >= (threshRsrp[4] - mLteRsrpBoost)) { + rsrpIconLevel = SIGNAL_STRENGTH_GREAT; + } else if (mLteRsrp >= (threshRsrp[3] - mLteRsrpBoost)) { + rsrpIconLevel = SIGNAL_STRENGTH_GOOD; + } else if (mLteRsrp >= (threshRsrp[2] - mLteRsrpBoost)) { + rsrpIconLevel = SIGNAL_STRENGTH_MODERATE; + } else if (mLteRsrp >= (threshRsrp[1] - mLteRsrpBoost)) { + rsrpIconLevel = SIGNAL_STRENGTH_POOR; + } else if (mLteRsrp >= threshRsrp[0]) { + rsrpIconLevel = SIGNAL_STRENGTH_NONE_OR_UNKNOWN; + } } /* @@ -816,7 +865,8 @@ public class SignalStrength implements Parcelable { snrIconLevel = SIGNAL_STRENGTH_NONE_OR_UNKNOWN; if (DBG) log("getLTELevel - rsrp:" + mLteRsrp + " snr:" + mLteRssnr + " rsrpIconLevel:" - + rsrpIconLevel + " snrIconLevel:" + snrIconLevel); + + rsrpIconLevel + " snrIconLevel:" + snrIconLevel + + " lteRsrpBoost:" + mLteRsrpBoost); /* Choose a measurement type to use for notification */ if (snrIconLevel != -1 && rsrpIconLevel != -1) { @@ -939,7 +989,7 @@ public class SignalStrength implements Parcelable { + (mEvdoDbm * primeNum) + (mEvdoEcio * primeNum) + (mEvdoSnr * primeNum) + (mLteSignalStrength * primeNum) + (mLteRsrp * primeNum) + (mLteRsrq * primeNum) + (mLteRssnr * primeNum) + (mLteCqi * primeNum) - + (mTdScdmaRscp * primeNum) + (isGsm ? 1 : 0)); + + (mLteRsrpBoost * primeNum) + (mTdScdmaRscp * primeNum) + (isGsm ? 1 : 0)); } /** @@ -971,6 +1021,7 @@ public class SignalStrength implements Parcelable { && mLteRsrq == s.mLteRsrq && mLteRssnr == s.mLteRssnr && mLteCqi == s.mLteCqi + && mLteRsrpBoost == s.mLteRsrpBoost && mTdScdmaRscp == s.mTdScdmaRscp && isGsm == s.isGsm); } @@ -993,6 +1044,7 @@ public class SignalStrength implements Parcelable { + " " + mLteRsrq + " " + mLteRssnr + " " + mLteCqi + + " " + mLteRsrpBoost + " " + mTdScdmaRscp + " " + (isGsm ? "gsm|lte" : "cdma")); } @@ -1016,6 +1068,7 @@ public class SignalStrength implements Parcelable { mLteRsrq = m.getInt("LteRsrq"); mLteRssnr = m.getInt("LteRssnr"); mLteCqi = m.getInt("LteCqi"); + mLteRsrpBoost = m.getInt("lteRsrpBoost"); mTdScdmaRscp = m.getInt("TdScdma"); isGsm = m.getBoolean("isGsm"); } @@ -1039,6 +1092,7 @@ public class SignalStrength implements Parcelable { m.putInt("LteRsrq", mLteRsrq); m.putInt("LteRssnr", mLteRssnr); m.putInt("LteCqi", mLteCqi); + m.putInt("lteRsrpBoost", mLteRsrpBoost); m.putInt("TdScdma", mTdScdmaRscp); m.putBoolean("isGsm", isGsm); } diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index 51b91f4c7999..786252318023 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -5276,23 +5276,42 @@ public class TelephonyManager { } } + + /** + * @deprecated use {@link #isDataEnabled()} instead. + * @hide + */ + @SystemApi + @Deprecated + public boolean getDataEnabled() { + return isDataEnabled(); + } + /** * Returns whether mobile data is enabled or not. * - * <p>Requires Permission: - * {@link android.Manifest.permission#ACCESS_NETWORK_STATE ACCESS_NETWORK_STATE}, - * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}, or that the - * calling app has carrier privileges. + * <p>Requires one of the following permissions: + * {@link android.Manifest.permission#ACCESS_NETWORK_STATE ACCESS_NETWORK_STATE}, + * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}, or that the + * calling app has carrier privileges. + * + * <p>Note that this does not take into account any data restrictions that may be present on the + * calling app. Such restrictions may be inspected with + * {@link ConnectivityManager#getRestrictBackgroundStatus}. * * @return true if mobile data is enabled. * * @see #hasCarrierPrivileges */ - public boolean getDataEnabled() { + @SuppressWarnings("deprecation") + public boolean isDataEnabled() { return getDataEnabled(getSubId()); } - /** @hide */ + /** + * @deprecated use {@link #isDataEnabled(int)} instead. + * @hide + */ @SystemApi public boolean getDataEnabled(int subId) { boolean retVal = false; diff --git a/test-runner/src/android/test/mock/MockContext.java b/test-runner/src/android/test/mock/MockContext.java index 3f5ca84c7cbe..da1d9984eea5 100644 --- a/test-runner/src/android/test/mock/MockContext.java +++ b/test-runner/src/android/test/mock/MockContext.java @@ -492,10 +492,22 @@ public class MockContext extends Context { @Override public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter, + boolean visibleToInstantApps) { + throw new UnsupportedOperationException(); + } + + @Override + public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter, String broadcastPermission, Handler scheduler) { throw new UnsupportedOperationException(); } + @Override + public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter, + String broadcastPermission, Handler scheduler, boolean visibleToInstantApps) { + throw new UnsupportedOperationException(); + } + /** @hide */ @Override public Intent registerReceiverAsUser(BroadcastReceiver receiver, UserHandle user, diff --git a/test-runner/src/android/test/mock/MockPackageManager.java b/test-runner/src/android/test/mock/MockPackageManager.java index 29ba77663fd0..506f406c333f 100644 --- a/test-runner/src/android/test/mock/MockPackageManager.java +++ b/test-runner/src/android/test/mock/MockPackageManager.java @@ -643,6 +643,12 @@ public class MockPackageManager extends PackageManager { throw new UnsupportedOperationException(); } + /** @hide */ + @Override + public void setUpdateAvailable(String packageName, boolean updateAvailable) { + throw new UnsupportedOperationException(); + } + @Override public String getInstallerPackageName(String packageName) { throw new UnsupportedOperationException(); diff --git a/tests/net/java/android/net/ip/IpManagerTest.java b/tests/net/java/android/net/ip/IpManagerTest.java new file mode 100644 index 000000000000..025b01740aad --- /dev/null +++ b/tests/net/java/android/net/ip/IpManagerTest.java @@ -0,0 +1,74 @@ +/* + * 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.net.ip; + +import static org.mockito.Mockito.when; + +import android.content.ContentResolver; +import android.content.Context; +import android.content.res.Resources; +import android.os.INetworkManagementService; +import android.provider.Settings; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; +import android.test.mock.MockContentResolver; + +import com.android.internal.util.test.FakeSettingsProvider; +import com.android.internal.R; + +import org.junit.Before; +import org.junit.runner.RunWith; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** + * Tests for IpManager. + */ +@RunWith(AndroidJUnit4.class) +@SmallTest +public class IpManagerTest { + private static final int DEFAULT_AVOIDBADWIFI_CONFIG_VALUE = 1; + + @Mock private Context mContext; + @Mock private INetworkManagementService mNMService; + @Mock private Resources mResources; + private MockContentResolver mContentResolver; + + @Before public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + + when(mContext.getResources()).thenReturn(mResources); + when(mResources.getInteger(R.integer.config_networkAvoidBadWifi)) + .thenReturn(DEFAULT_AVOIDBADWIFI_CONFIG_VALUE); + + mContentResolver = new MockContentResolver(); + mContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider()); + when(mContext.getContentResolver()).thenReturn(mContentResolver); + } + + @Test + public void testNullCallbackDoesNotThrow() throws Exception { + final IpManager ipm = new IpManager(mContext, "lo", null, mNMService); + } + + @Test + public void testInvalidInterfaceDoesNotThrow() throws Exception { + final IpManager.Callback cb = new IpManager.Callback(); + final IpManager ipm = new IpManager(mContext, "test_wlan0", cb, mNMService); + } +} diff --git a/tests/net/java/com/android/server/connectivity/IpConnectivityEventBuilderTest.java b/tests/net/java/com/android/server/connectivity/IpConnectivityEventBuilderTest.java index 11105d6de75b..48861bde77b3 100644 --- a/tests/net/java/com/android/server/connectivity/IpConnectivityEventBuilderTest.java +++ b/tests/net/java/com/android/server/connectivity/IpConnectivityEventBuilderTest.java @@ -304,6 +304,7 @@ public class IpConnectivityEventBuilderTest extends TestCase { ConnectivityMetricsEvent ev = describeIpEvent( aType(ApfProgramEvent.class), aLong(200), + aLong(18), anInt(7), anInt(9), anInt(2048), @@ -320,7 +321,7 @@ public class IpConnectivityEventBuilderTest extends TestCase { " apf_program_event <", " current_ras: 9", " drop_multicast: true", - " effective_lifetime: 0", + " effective_lifetime: 18", " filtered_ras: 7", " has_ipv4_addr: true", " lifetime: 200", @@ -343,6 +344,8 @@ public class IpConnectivityEventBuilderTest extends TestCase { anInt(1), anInt(2), anInt(4), + anInt(7), + anInt(3), anInt(2048)); String want = joinLines( @@ -360,8 +363,8 @@ public class IpConnectivityEventBuilderTest extends TestCase { " max_program_size: 2048", " parse_errors: 2", " program_updates: 4", - " program_updates_all: 0", - " program_updates_allowing_multicast: 0", + " program_updates_all: 7", + " program_updates_allowing_multicast: 3", " received_ras: 10", " zero_lifetime_ras: 1", " >", diff --git a/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java b/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java index 1f7c5f418d74..785e1ce20ab5 100644 --- a/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java +++ b/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java @@ -85,7 +85,7 @@ public class IpConnectivityMetricsTest extends TestCase { new Thread() { public void run() { for (int j = 0; j < nEvents; j++) { - assertTrue(logger.log(i * 100 + j, FAKE_EV)); + assertTrue(logger.log(1 + i * 100 + j, FAKE_EV)); } } }.start(); @@ -96,7 +96,7 @@ public class IpConnectivityMetricsTest extends TestCase { Iterator<ConnectivityMetricsEvent> iter = got.iterator(); for (int i = 0; i < nCallers; i++) { for (int j = 0; j < nEvents; j++) { - int expectedTimestamp = i * 100 + j; + int expectedTimestamp = 1 + i * 100 + j; assertEventsEqual(expectedEvent(expectedTimestamp), iter.next()); } } @@ -118,7 +118,7 @@ public class IpConnectivityMetricsTest extends TestCase { @SmallTest public void testRateLimiting() { final IpConnectivityLog logger = new IpConnectivityLog(mService.impl); - final ApfProgramEvent ev = new ApfProgramEvent(0, 0, 0, 0, 0); + final ApfProgramEvent ev = new ApfProgramEvent(); final long fakeTimestamp = 1; int attempt = 100; // More than burst quota, but less than buffer size. @@ -142,13 +142,24 @@ public class IpConnectivityMetricsTest extends TestCase { // TODO: instead of comparing textpb to textpb, parse textpb and compare proto to proto. IpConnectivityLog logger = new IpConnectivityLog(mService.impl); + ApfStats apfStats = new ApfStats(); + apfStats.durationMs = 45000; + apfStats.receivedRas = 10; + apfStats.matchingRas = 2; + apfStats.droppedRas = 2; + apfStats.parseErrors = 2; + apfStats.zeroLifetimeRas = 1; + apfStats.programUpdates = 4; + apfStats.programUpdatesAll = 7; + apfStats.programUpdatesAllowingMulticast = 3; + apfStats.maxProgramSize = 2048; Parcelable[] events = { new IpReachabilityEvent("wlan0", IpReachabilityEvent.NUD_FAILED), new DhcpClientEvent("wlan0", "SomeState", 192), new DefaultNetworkEvent(102, new int[]{1,2,3}, 101, true, false), new IpManagerEvent("wlan0", IpManagerEvent.PROVISIONING_OK, 5678), new ValidationProbeEvent(120, 40730, ValidationProbeEvent.PROBE_HTTP, 204), - new ApfStats(45000, 10, 2, 2, 1, 2, 4, 2048), + apfStats, new RaEvent(2000, 400, 300, -1, 1000, -1) }; @@ -240,8 +251,8 @@ public class IpConnectivityMetricsTest extends TestCase { " max_program_size: 2048", " parse_errors: 2", " program_updates: 4", - " program_updates_all: 0", - " program_updates_allowing_multicast: 0", + " program_updates_all: 7", + " program_updates_allowing_multicast: 3", " received_ras: 10", " zero_lifetime_ras: 1", " >", @@ -304,14 +315,15 @@ public class IpConnectivityMetricsTest extends TestCase { } static ConnectivityMetricsEvent expectedEvent(int timestamp) { - return new ConnectivityMetricsEvent((long)timestamp, 0, 0, FAKE_EV); + ConnectivityMetricsEvent ev = new ConnectivityMetricsEvent(); + ev.timestamp = timestamp; + ev.data = FAKE_EV; + return ev; } /** Outer equality for ConnectivityMetricsEvent to avoid overriding equals() and hashCode(). */ static void assertEventsEqual(ConnectivityMetricsEvent expected, ConnectivityMetricsEvent got) { assertEquals(expected.timestamp, got.timestamp); - assertEquals(expected.componentTag, got.componentTag); - assertEquals(expected.eventTag, got.eventTag); assertEquals(expected.data, got.data); } diff --git a/tests/net/java/com/android/server/connectivity/MetricsTestUtil.java b/tests/net/java/com/android/server/connectivity/MetricsTestUtil.java index c5965e8ea833..5064b9bd91b9 100644 --- a/tests/net/java/com/android/server/connectivity/MetricsTestUtil.java +++ b/tests/net/java/com/android/server/connectivity/MetricsTestUtil.java @@ -28,7 +28,10 @@ abstract public class MetricsTestUtil { } static ConnectivityMetricsEvent ev(Parcelable p) { - return new ConnectivityMetricsEvent(1L, 0, 0, p); + ConnectivityMetricsEvent ev = new ConnectivityMetricsEvent(); + ev.timestamp = 1L; + ev.data = p; + return ev; } static ConnectivityMetricsEvent describeIpEvent(Consumer<Parcel>... fs) { diff --git a/tests/net/java/com/android/server/connectivity/NetdEventListenerServiceTest.java b/tests/net/java/com/android/server/connectivity/NetdEventListenerServiceTest.java index 9e0f32137e89..0ab440641d53 100644 --- a/tests/net/java/com/android/server/connectivity/NetdEventListenerServiceTest.java +++ b/tests/net/java/com/android/server/connectivity/NetdEventListenerServiceTest.java @@ -23,6 +23,7 @@ import android.net.metrics.ConnectStats; import android.net.metrics.DnsEvent; import android.net.metrics.INetdEventListener; import android.net.metrics.IpConnectivityLog; +import android.os.Parcelable; import android.os.RemoteException; import android.system.OsConstants; import android.test.suitebuilder.annotation.SmallTest; @@ -165,8 +166,8 @@ public class NetdEventListenerServiceTest extends TestCase { // call onLost() asynchronously to logDnsAsync's onDnsEvent() calls. mCallbackCaptor.getValue().onLost(new Network(105)); - // do not verify unpredictable batch - verify(mLog, timeout(500).times(1)).log(any()); + // do not verify batch with unpredictable length + verify(mLog, timeout(500).times(1)).log(any(Parcelable.class)); } @SmallTest @@ -279,11 +280,7 @@ public class NetdEventListenerServiceTest extends TestCase { } void logDnsAsync(int netId, int[] latencies) { - new Thread() { - public void run() { - log(netId, latencies); - } - }.start(); + new Thread(() -> log(netId, latencies)).start(); } void verifyLoggedDnsEvents(DnsEvent... expected) { diff --git a/tests/testables/Android.mk b/tests/testables/Android.mk new file mode 100644 index 000000000000..58399fd5bc6c --- /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 index fd99d1d46db3..816ed033a3e2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/SysUIRunner.java +++ b/tests/testables/src/android/testing/AndroidTestingRunner.java @@ -12,14 +12,14 @@ * permissions and limitations under the License. */ -package com.android.systemui; +package android.testing; import android.support.test.internal.runner.junit4.statement.RunAfters; import android.support.test.internal.runner.junit4.statement.RunBefores; import android.support.test.internal.runner.junit4.statement.UiThreadStatement; -import com.android.systemui.utils.TestableLooper.LooperStatement; -import com.android.systemui.utils.TestableLooper.RunWithLooper; +import android.testing.TestableLooper.LooperStatement; +import android.testing.TestableLooper.RunWithLooper; import org.junit.After; import org.junit.Before; @@ -32,12 +32,15 @@ import org.junit.runners.model.Statement; import java.util.List; -public class SysUIRunner extends BlockJUnit4ClassRunner { +/** + * A runner with support for extra annotations provided by the Testables library. + */ +public class AndroidTestingRunner extends BlockJUnit4ClassRunner { private final long mTimeout; private final Class<?> mKlass; - public SysUIRunner(Class<?> klass) throws InitializationError { + public AndroidTestingRunner(Class<?> klass) throws InitializationError { super(klass); mKlass = klass; // Can't seem to get reference to timeout parameter from here, so set default to 10 mins. diff --git a/packages/SystemUI/tests/src/com/android/systemui/FragmentTestCase.java b/tests/testables/src/android/testing/BaseFragmentTest.java index 1678d9232652..53841d597e26 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/FragmentTestCase.java +++ b/tests/testables/src/android/testing/BaseFragmentTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016 The Android Open Source Project + * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of the License at @@ -12,7 +12,9 @@ * permissions and limitations under the License. */ -package com.android.systemui; +package android.testing; + +import static org.junit.Assert.assertNotNull; import android.annotation.Nullable; import android.app.Fragment; @@ -21,20 +23,17 @@ import android.app.FragmentHostCallback; import android.app.FragmentManagerNonConfig; import android.graphics.PixelFormat; import android.os.Handler; -import android.os.Looper; import android.os.Parcelable; +import android.support.test.InstrumentationRegistry; import android.view.LayoutInflater; import android.view.View; import android.view.WindowManager; import android.view.WindowManager.LayoutParams; import android.widget.FrameLayout; -import com.android.systemui.utils.TestableLooper; -import com.android.systemui.utils.ViewUtils; -import com.android.systemui.utils.leaks.LeakCheckedTest; - import org.junit.After; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import java.io.FileDescriptor; @@ -46,7 +45,7 @@ import java.io.PrintWriter; * the host for subclasses, so they can push it into desired states and do any unit testing * required. */ -public abstract class FragmentTestCase extends LeakCheckedTest { +public abstract class BaseFragmentTest { private static final int VIEW_ID = 42; private final Class<? extends Fragment> mCls; @@ -55,7 +54,10 @@ public abstract class FragmentTestCase extends LeakCheckedTest { protected FragmentController mFragments; protected Fragment mFragment; - public FragmentTestCase(Class<? extends Fragment> cls) { + @Rule + public final TestableContext mContext = getContext(); + + public BaseFragmentTest(Class<? extends Fragment> cls) { mCls = cls; } @@ -64,6 +66,8 @@ public abstract class FragmentTestCase extends LeakCheckedTest { mView = new FrameLayout(mContext); mView.setId(VIEW_ID); + assertNotNull("BaseFragmentTest must be tagged with @RunWithLooper", + TestableLooper.get(this)); TestableLooper.get(this).runWithLooper(() -> { mHandler = new Handler(); @@ -76,8 +80,8 @@ public abstract class FragmentTestCase extends LeakCheckedTest { }); } - private String hex(Looper looper) { - return Integer.toHexString(System.identityHashCode(looper)); + protected TestableContext getContext() { + return new TestableContext(InstrumentationRegistry.getContext()); } @After @@ -174,14 +178,14 @@ public abstract class FragmentTestCase extends LeakCheckedTest { return mView.findViewById(id); } - private class HostCallbacks extends FragmentHostCallback<FragmentTestCase> { + private class HostCallbacks extends FragmentHostCallback<BaseFragmentTest> { public HostCallbacks() { - super(mContext, FragmentTestCase.this.mHandler, 0); + super(mContext, BaseFragmentTest.this.mHandler, 0); } @Override - public FragmentTestCase onGetHost() { - return FragmentTestCase.this; + public BaseFragmentTest onGetHost() { + return BaseFragmentTest.this; } @Override @@ -220,7 +224,7 @@ public abstract class FragmentTestCase extends LeakCheckedTest { @Nullable @Override public View onFindViewById(int id) { - return FragmentTestCase.this.findViewById(id); + return BaseFragmentTest.this.findViewById(id); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/util/LayoutInflaterBuilder.java b/tests/testables/src/android/testing/LayoutInflaterBuilder.java index 5cfe677efd57..098302e1a0a1 100644 --- a/packages/SystemUI/src/com/android/systemui/util/LayoutInflaterBuilder.java +++ b/tests/testables/src/android/testing/LayoutInflaterBuilder.java @@ -1,20 +1,18 @@ /* - * Copyright (C) 2016 The Android Open Source Project + * Copyright (C) 2017 The Android Open Source Project * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the specific language governing + * permissions and limitations under the License. */ -package com.android.systemui.util; +package android.testing; import android.annotation.NonNull; import android.content.Context; diff --git a/tests/testables/src/android/testing/LeakCheck.java b/tests/testables/src/android/testing/LeakCheck.java new file mode 100644 index 000000000000..8daaa8f16664 --- /dev/null +++ b/tests/testables/src/android/testing/LeakCheck.java @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package android.testing; + +import android.util.ArrayMap; +import android.util.Log; + +import org.junit.Assert; +import org.junit.rules.TestWatcher; +import org.junit.runner.Description; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class LeakCheck extends TestWatcher { + + private final Map<String, Tracker> mTrackers = new HashMap<>(); + + public LeakCheck() { + } + + @Override + protected void succeeded(Description description) { + verify(); + } + + public Tracker getTracker(String tag) { + Tracker t = mTrackers.get(tag); + if (t == null) { + t = new Tracker(); + mTrackers.put(tag, t); + } + return t; + } + + public void verify() { + mTrackers.values().forEach(Tracker::verify); + } + + public static class LeakInfo { + private static final String TAG = "LeakInfo"; + private List<Throwable> mThrowables = new ArrayList<>(); + + LeakInfo() { + } + + public void addAllocation(Throwable t) { + // TODO: Drop off the first element in the stack trace here to have a cleaner stack. + mThrowables.add(t); + } + + public void clearAllocations() { + mThrowables.clear(); + } + + void verify() { + if (mThrowables.size() == 0) return; + Log.e(TAG, "Listener or binding not properly released"); + for (Throwable t : mThrowables) { + Log.e(TAG, "Allocation found", t); + } + StringWriter writer = new StringWriter(); + mThrowables.get(0).printStackTrace(new PrintWriter(writer)); + Assert.fail("Listener or binding not properly released\n" + + writer.toString()); + } + } + + public static class Tracker { + private Map<Object, LeakInfo> mObjects = new ArrayMap<>(); + + public LeakInfo getLeakInfo(Object object) { + LeakInfo leakInfo = mObjects.get(object); + if (leakInfo == null) { + leakInfo = new LeakInfo(); + mObjects.put(object, leakInfo); + } + return leakInfo; + } + + void verify() { + mObjects.values().forEach(LeakInfo::verify); + } + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/FakeContentResolver.java b/tests/testables/src/android/testing/TestableContentResolver.java index 34f2e019761d..bfafbe043ffb 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/utils/FakeContentResolver.java +++ b/tests/testables/src/android/testing/TestableContentResolver.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016 The Android Open Source Project + * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of the License at @@ -12,7 +12,7 @@ * permissions and limitations under the License. */ -package com.android.systemui.utils; +package android.testing; import android.content.ContentProvider; import android.content.ContentResolver; @@ -29,14 +29,14 @@ import java.util.Map; /** * Alternative to a MockContentResolver that falls back to real providers. */ -public class FakeContentResolver extends ContentResolver { +public class TestableContentResolver extends ContentResolver { private final Map<String, ContentProvider> mProviders = Maps.newHashMap(); private final ContentResolver mParent; private final ArraySet<ContentProvider> mInUse = new ArraySet<>(); private boolean mFallbackToExisting; - public FakeContentResolver(Context context) { + public TestableContentResolver(Context context) { super(context); mParent = context.getContentResolver(); mFallbackToExisting = true; diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/TestableContext.java b/tests/testables/src/android/testing/TestableContext.java index 1429390d193a..cb5d4cb8242f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/utils/TestableContext.java +++ b/tests/testables/src/android/testing/TestableContext.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016 The Android Open Source Project + * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of the License at @@ -12,7 +12,7 @@ * permissions and limitations under the License. */ -package com.android.systemui.utils; +package android.testing; import android.content.BroadcastReceiver; import android.content.ComponentCallbacks; @@ -32,36 +32,59 @@ import android.provider.Settings; import android.util.ArrayMap; import android.view.LayoutInflater; -import com.android.systemui.SysUiServiceProvider; -import com.android.systemui.SysuiTestCase; -import com.android.systemui.utils.leaks.Tracker; +import org.junit.rules.TestRule; +import org.junit.rules.TestWatcher; +import org.junit.runner.Description; +import org.junit.runners.model.Statement; -public class TestableContext extends ContextWrapper implements SysUiServiceProvider { +/** + * A ContextWrapper with utilities specifically designed to make Testing easier. + * + * <ul> + * <li>System services can be mocked out with {@link #addMockSystemService}</li> + * <li>Service binding can be mocked out with {@link #addMockService}</li> + * <li>Settings support {@link TestableSettings}</li> + * <li>Has support for {@link LeakCheck} for services and receivers</li> + * </ul> + * + * <p>TestableContext should be defined as a rule on your test so it can clean up after itself. + * Like the following:</p> + * <pre class="prettyprint"> + * {@literal + * @Rule + * private final TestableContext mContext = new TestableContext(InstrumentationRegister.getContext()); + * } + * </pre> + */ +public class TestableContext extends ContextWrapper implements TestRule { - private final FakeContentResolver mFakeContentResolver; - private final FakeSettingsProvider mSettingsProvider; + private final TestableContentResolver mTestableContentResolver; + private final TestableSettings mSettingsProvider; private ArrayMap<String, Object> mMockSystemServices; private ArrayMap<ComponentName, IBinder> mMockServices; private ArrayMap<ServiceConnection, ComponentName> mActiveServices; - private ArrayMap<Class<?>, Object> mComponents; private PackageManager mMockPackageManager; - private Tracker mReceiver; - private Tracker mService; - private Tracker mComponent; + private LeakCheck.Tracker mReceiver; + private LeakCheck.Tracker mService; + private LeakCheck.Tracker mComponent; + + public TestableContext(Context base) { + this(base, null); + } - public TestableContext(Context base, SysuiTestCase test) { + public TestableContext(Context base, LeakCheck check) { super(base); - mFakeContentResolver = new FakeContentResolver(base); + mTestableContentResolver = new TestableContentResolver(base); ContentProviderClient settings = base.getContentResolver() .acquireContentProviderClient(Settings.AUTHORITY); - mSettingsProvider = FakeSettingsProvider.getFakeSettingsProvider(settings, - mFakeContentResolver); - mFakeContentResolver.addProvider(Settings.AUTHORITY, mSettingsProvider); - mReceiver = test.getTracker("receiver"); - mService = test.getTracker("service"); - mComponent = test.getTracker("component"); + mSettingsProvider = TestableSettings.getFakeSettingsProvider(settings, + mTestableContentResolver); + mTestableContentResolver.addProvider(Settings.AUTHORITY, mSettingsProvider.getProvider()); + mReceiver = check != null ? check.getTracker("receiver") : null; + mService = check != null ? check.getTracker("service") : null; + mComponent = check != null ? check.getTracker("component") : null; } public void setMockPackageManager(PackageManager mock) { @@ -86,19 +109,15 @@ public class TestableContext extends ContextWrapper implements SysUiServiceProvi } public void addMockSystemService(String name, Object service) { - mMockSystemServices = lazyInit(mMockSystemServices); + if (mMockSystemServices == null) mMockSystemServices = new ArrayMap<>(); mMockSystemServices.put(name, service); } public void addMockService(ComponentName component, IBinder service) { - mMockServices = lazyInit(mMockServices); + if (mMockServices == null) mMockServices = new ArrayMap<>(); mMockServices.put(component, service); } - private <T, V> ArrayMap<T, V> lazyInit(ArrayMap<T, V> services) { - return services != null ? services : new ArrayMap<T, V>(); - } - @Override public Object getSystemService(String name) { if (mMockSystemServices != null && mMockSystemServices.containsKey(name)) { @@ -110,13 +129,13 @@ public class TestableContext extends ContextWrapper implements SysUiServiceProvi return super.getSystemService(name); } - public FakeSettingsProvider getSettingsProvider() { + public TestableSettings getSettingsProvider() { return mSettingsProvider; } @Override - public FakeContentResolver getContentResolver() { - return mFakeContentResolver; + public TestableContentResolver getContentResolver() { + return mTestableContentResolver; } @Override @@ -177,7 +196,7 @@ public class TestableContext extends ContextWrapper implements SysUiServiceProvi private boolean checkMocks(ComponentName component, ServiceConnection conn) { if (mMockServices != null && component != null && mMockServices.containsKey(component)) { - mActiveServices = lazyInit(mActiveServices); + if (mActiveServices == null) mActiveServices = new ArrayMap<>(); mActiveServices.put(conn, component); conn.onServiceConnected(component, mMockServices.get(component)); return true; @@ -212,13 +231,18 @@ public class TestableContext extends ContextWrapper implements SysUiServiceProvi super.unregisterComponentCallbacks(callback); } - @SuppressWarnings("unchecked") - public <T> T getComponent(Class<T> interfaceType) { - return (T) (mComponents != null ? mComponents.get(interfaceType) : null); - } - - public <T, C extends T> void putComponent(Class<T> interfaceType, C component) { - mComponents = lazyInit(mComponents); - mComponents.put(interfaceType, component); + @Override + public Statement apply(Statement base, Description description) { + return new TestWatcher() { + @Override + protected void succeeded(Description description) { + mSettingsProvider.clearOverrides(); + } + + @Override + protected void failed(Throwable e, Description description) { + mSettingsProvider.clearOverrides(); + } + }.apply(base, description); } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/TestableImageView.java b/tests/testables/src/android/testing/TestableImageView.java index b131460e1eff..901e25b143b8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/utils/TestableImageView.java +++ b/tests/testables/src/android/testing/TestableImageView.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016 The Android Open Source Project + * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of the License at @@ -12,7 +12,7 @@ * permissions and limitations under the License. */ -package com.android.systemui.utils; +package android.testing; import android.annotation.DrawableRes; import android.annotation.Nullable; diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/TestableLooper.java b/tests/testables/src/android/testing/TestableLooper.java index 8902e0c23b5f..8a33cf918646 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/utils/TestableLooper.java +++ b/tests/testables/src/android/testing/TestableLooper.java @@ -12,7 +12,7 @@ * permissions and limitations under the License. */ -package com.android.systemui.utils; +package android.testing; import android.os.Handler; import android.os.Looper; diff --git a/tests/testables/src/android/testing/TestableSettings.java b/tests/testables/src/android/testing/TestableSettings.java new file mode 100644 index 000000000000..d19f1ef60b2e --- /dev/null +++ b/tests/testables/src/android/testing/TestableSettings.java @@ -0,0 +1,318 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package android.testing; + +import android.content.ContentProvider; +import android.content.ContentProviderClient; +import android.content.ContentResolver; +import android.os.Bundle; +import android.os.RemoteException; +import android.provider.Settings; +import android.support.annotation.VisibleForTesting; +import android.test.mock.MockContentProvider; +import android.testing.TestableSettings.SettingOverrider.Builder; +import android.util.ArrayMap; +import android.util.ArraySet; +import android.util.Log; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * Allows calls to android.provider.Settings to be tested easier. A SettingOverride + * can be acquired and a set of specific settings can be set to a value (and not changed + * in the system when set), so that they can be tested without breaking the test device. + * <p> + * To use, in the before method acquire the override add all settings that will affect if + * your test passes or not. + * + * <pre class="prettyprint"> + * {@literal + * mSettingOverride = mTestableContext.getSettingsProvider().acquireOverridesBuilder() + * .addSetting("secure", Secure.USER_SETUP_COMPLETE, "0") + * .build(); + * } + * </pre> + * + * Then in the after free up the settings. + * + * <pre class="prettyprint"> + * {@literal + * mSettingOverride.release(); + * } + * </pre> + */ +public class TestableSettings { + + private static final String TAG = "TestableSettings"; + private static final boolean DEBUG = false; + + // Number of times to try to acquire a setting if in use. + private static final int MAX_TRIES = 10; + // Time to wait for each setting. WAIT_TIMEOUT * MAX_TRIES will be the maximum wait time + // for a setting. + private static final long WAIT_TIMEOUT = 1000; + + private static TestableSettingsProvider sInstance; + + private final TestableSettingsProvider mProvider; + + private TestableSettings(TestableSettingsProvider provider) { + mProvider = provider; + } + + public Builder acquireOverridesBuilder() { + return new Builder(this); + } + + public void clearOverrides() { + List<SettingOverrider> overrides = mProvider.mOwners.remove(this); + if (overrides != null) { + overrides.forEach(override -> override.ensureReleased()); + } + } + + private void acquireSettings(SettingOverrider overridder, Set<String> keys) + throws AcquireTimeoutException { + mProvider.acquireSettings(overridder, keys, this); + } + + ContentProvider getProvider() { + return mProvider; + } + + @VisibleForTesting + Object getLock() { + return mProvider.mOverrideMap; + } + + public static class SettingOverrider { + private final Set<String> mValidKeys; + private final Map<String, String> mValueMap = new ArrayMap<>(); + private final TestableSettings mSettings; + private boolean mReleased; + public Throwable mObtain; + + private SettingOverrider(Set<String> keys, TestableSettings provider) { + mValidKeys = new ArraySet<>(keys); + mSettings = provider; + } + + private void ensureReleased() { + if (!mReleased) { + release(); + } + } + + public void release() { + mSettings.mProvider.releaseSettings(mValidKeys); + mReleased = true; + } + + private void putDirect(String key, String value) { + mValueMap.put(key, value); + } + + public void put(String table, String key, String value) { + if (!mValidKeys.contains(key(table, key))) { + throw new IllegalArgumentException("Key " + table + " " + key + + " not acquired for this overrider"); + } + mValueMap.put(key(table, key), value); + } + + public void remove(String table, String key) { + if (!mValidKeys.contains(key(table, key))) { + throw new IllegalArgumentException("Key " + table + " " + key + + " not acquired for this overrider"); + } + mValueMap.remove(key(table, key)); + } + + public String get(String table, String key) { + if (!mValidKeys.contains(key(table, key))) { + throw new IllegalArgumentException("Key " + table + " " + key + + " not acquired for this overrider"); + } + Log.d(TAG, "Get " + table + " " + key + " " + mValueMap.get(key(table, key))); + return mValueMap.get(key(table, key)); + } + + public static class Builder { + private final TestableSettings mProvider; + private Set<String> mKeys = new ArraySet<>(); + private Map<String, String> mValues = new ArrayMap<>(); + + private Builder(TestableSettings provider) { + mProvider = provider; + } + + public Builder addSetting(String table, String key) { + mKeys.add(key(table, key)); + return this; + } + + public Builder addSetting(String table, String key, String value) { + addSetting(table, key); + mValues.put(key(table, key), value); + return this; + } + + public SettingOverrider build() throws AcquireTimeoutException { + SettingOverrider overrider = new SettingOverrider(mKeys, mProvider); + mProvider.acquireSettings(overrider, mKeys); + mValues.forEach((key, value) -> overrider.putDirect(key, value)); + return overrider; + } + } + } + + private static class TestableSettingsProvider extends MockContentProvider { + + private final Map<String, SettingOverrider> mOverrideMap = new ArrayMap<>(); + private final Map<Object, List<SettingOverrider>> mOwners = new ArrayMap<>(); + + private final ContentProviderClient mSettings; + private final ContentResolver mResolver; + + public TestableSettingsProvider(ContentProviderClient settings, ContentResolver resolver) { + mSettings = settings; + mResolver = resolver; + } + + private void releaseSettings(Set<String> keys) { + synchronized (mOverrideMap) { + for (String key : keys) { + if (DEBUG) Log.d(TAG, "Releasing " + key); + mOverrideMap.remove(key); + } + if (DEBUG) Log.d(TAG, "Notifying"); + mOverrideMap.notify(); + } + } + + private boolean checkKeysLocked(Set<String> keys, boolean shouldThrow) + throws AcquireTimeoutException { + for (String key : keys) { + if (mOverrideMap.containsKey(key)) { + if (shouldThrow) { + if (DEBUG) Log.e(TAG, "Lock obtained at", + mOverrideMap.get(key).mObtain); + throw new AcquireTimeoutException("Could not acquire " + key, + mOverrideMap.get(key).mObtain); + } + return false; + } + } + return true; + } + + private void acquireSettings(SettingOverrider overridder, Set<String> keys, + Object owner) throws AcquireTimeoutException { + synchronized (mOwners) { + List<SettingOverrider> list = mOwners.get(owner); + if (list == null) { + list = new ArrayList<>(); + mOwners.put(owner, list); + } + list.add(overridder); + } + synchronized (mOverrideMap) { + for (int i = 0; i < MAX_TRIES; i++) { + if (checkKeysLocked(keys, false)) break; + try { + if (DEBUG) Log.d(TAG, "Waiting for contention to finish"); + mOverrideMap.wait(WAIT_TIMEOUT); + } catch (InterruptedException e) { + } + } + overridder.mObtain = new Throwable(); + checkKeysLocked(keys, true); + for (String key : keys) { + if (DEBUG) Log.d(TAG, "Acquiring " + key); + mOverrideMap.put(key, overridder); + } + } + } + + public Bundle call(String method, String arg, Bundle extras) { + // Methods are "GET_system", "GET_global", "PUT_secure", etc. + final String[] commands = method.split("_", 2); + final String op = commands[0]; + final String table = commands[1]; + + synchronized (mOverrideMap) { + SettingOverrider overrider = mOverrideMap.get(key(table, arg)); + if (overrider == null) { + // Fall through to real settings. + try { + if (DEBUG) Log.d(TAG, "Falling through to real settings " + method); + // TODO: Add our own version of caching to handle this. + Bundle call = mSettings.call(method, arg, extras); + call.remove(Settings.CALL_METHOD_TRACK_GENERATION_KEY); + return call; + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + String value; + Bundle out = new Bundle(); + switch (op) { + case "GET": + value = overrider.get(table, arg); + if (value != null) { + out.putString(Settings.NameValueTable.VALUE, value); + } + break; + case "PUT": + value = extras.getString(Settings.NameValueTable.VALUE, null); + if (value != null) { + overrider.put(table, arg, value); + } else { + overrider.remove(table, arg); + } + break; + default: + throw new UnsupportedOperationException("Unknown command " + method); + } + return out; + } + } + } + + public static class AcquireTimeoutException extends Exception { + public AcquireTimeoutException(String str, Throwable cause) { + super(str, cause); + } + } + + private static String key(String table, String key) { + return table + "_" + key; + } + + /** + * Since the settings provider is cached inside android.provider.Settings, this must + * be gotten statically to ensure there is only one instance referenced. + */ + public static TestableSettings getFakeSettingsProvider(ContentProviderClient settings, + ContentResolver resolver) { + if (sInstance == null) { + sInstance = new TestableSettingsProvider(settings, resolver); + } + return new TestableSettings(sInstance); + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/UiThreadTest.java b/tests/testables/src/android/testing/UiThreadTest.java index 58369b119c49..e40e1d741f28 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/UiThreadTest.java +++ b/tests/testables/src/android/testing/UiThreadTest.java @@ -12,7 +12,7 @@ * permissions and limitations under the License. */ -package com.android.systemui; +package android.testing; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/ViewUtils.java b/tests/testables/src/android/testing/ViewUtils.java index 678b9f407bcd..5a651aacc74f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/utils/ViewUtils.java +++ b/tests/testables/src/android/testing/ViewUtils.java @@ -12,19 +12,14 @@ * permissions and limitations under the License. */ -package com.android.systemui.utils; +package android.testing; import android.content.pm.ApplicationInfo; import android.graphics.PixelFormat; -import android.os.Handler; -import android.os.Looper; -import android.util.Log; +import android.support.test.InstrumentationRegistry; import android.view.View; import android.view.WindowManager; import android.view.WindowManager.LayoutParams; -import android.support.test.InstrumentationRegistry; - -import com.android.systemui.SysuiTestCase; public class ViewUtils { diff --git a/tests/testables/tests/Android.mk b/tests/testables/tests/Android.mk new file mode 100644 index 000000000000..752d536ae4c6 --- /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 000000000000..f6006b0a7fa9 --- /dev/null +++ b/tests/testables/tests/AndroidManifest.xml @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2017 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.testables"> + + <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" /> + + <application> + <uses-library android:name="android.test.runner" /> + </application> + + <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner" + android:targetPackage="com.android.testables" + android:label="Tests for Testables"> + </instrumentation> +</manifest> diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/TestableLooperTest.java b/tests/testables/tests/src/android/testing/TestableLooperTest.java index 2416e1d8438f..18e5fffef992 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/utils/TestableLooperTest.java +++ b/tests/testables/tests/src/android/testing/TestableLooperTest.java @@ -12,7 +12,7 @@ * permissions and limitations under the License. */ -package com.android.systemui.utils; +package android.testing; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; @@ -27,20 +27,17 @@ import static org.mockito.Mockito.when; import android.os.Handler; import android.os.Looper; import android.os.Message; - -import com.android.systemui.SysUIRunner; -import com.android.systemui.SysuiTestCase; -import com.android.systemui.utils.TestableLooper.MessageHandler; -import com.android.systemui.utils.TestableLooper.RunWithLooper; +import android.testing.TestableLooper.MessageHandler; +import android.testing.TestableLooper.RunWithLooper; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -@RunWith(SysUIRunner.class) +@RunWith(AndroidTestingRunner.class) @RunWithLooper -public class TestableLooperTest extends SysuiTestCase { +public class TestableLooperTest { private TestableLooper mTestableLooper; diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/FakeSettingsProviderTest.java b/tests/testables/tests/src/android/testing/TestableSettingsTest.java index 63bb5e7f0e4a..1b01542a8a64 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/utils/FakeSettingsProviderTest.java +++ b/tests/testables/tests/src/android/testing/TestableSettingsTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016 The Android Open Source Project + * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of the License at @@ -12,7 +12,7 @@ * permissions and limitations under the License. */ -package com.android.systemui.utils; +package android.testing; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; @@ -25,29 +25,32 @@ import android.os.HandlerThread; import android.provider.Settings; import android.provider.Settings.Global; import android.provider.Settings.Secure; +import android.support.test.InstrumentationRegistry; import android.support.test.runner.AndroidJUnit4; +import android.testing.TestableSettings.AcquireTimeoutException; +import android.testing.TestableSettings.SettingOverrider; import android.util.Log; -import com.android.systemui.SysuiTestCase; -import com.android.systemui.utils.FakeSettingsProvider.AcquireTimeoutException; -import com.android.systemui.utils.FakeSettingsProvider.SettingOverrider; - import org.junit.After; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @RunWith(AndroidJUnit4.class) -public class FakeSettingsProviderTest extends SysuiTestCase { +public class TestableSettingsTest { public static final String NONEXISTENT_SETTING = "nonexistent_setting"; - private static final String TAG = "FakeSettingsProviderTest"; + private static final String TAG = "TestableSettingsTest"; private SettingOverrider mOverrider; private ContentResolver mContentResolver; + @Rule + public final TestableContext mContext = + new TestableContext(InstrumentationRegistry.getContext()); @Before public void setup() throws AcquireTimeoutException { - mOverrider = mContext.getSettingsProvider().acquireOverridesBuilder(this) + mOverrider = mContext.getSettingsProvider().acquireOverridesBuilder() .addSetting("secure", NONEXISTENT_SETTING) .addSetting("global", NONEXISTENT_SETTING, "initial value") .addSetting("global", Global.DEVICE_PROVISIONED) @@ -55,13 +58,6 @@ public class FakeSettingsProviderTest extends SysuiTestCase { mContentResolver = mContext.getContentResolver(); } - @After - public void teardown() { - if (mOverrider != null) { - mOverrider.release(); - } - } - @Test public void testInitialValueSecure() { String value = Secure.getString(mContentResolver, NONEXISTENT_SETTING); @@ -109,8 +105,9 @@ public class FakeSettingsProviderTest extends SysuiTestCase { @Test public void testAutoRelease() throws Exception { - super.cleanup(); - mContext.getSettingsProvider().acquireOverridesBuilder(this) + mOverrider.release(); + mOverrider = null; + mContext.getSettingsProvider().acquireOverridesBuilder() .addSetting("global", Global.DEVICE_PROVISIONED) .build(); } @@ -122,7 +119,7 @@ public class FakeSettingsProviderTest extends SysuiTestCase { String secure = "secure"; String key = "something shared"; String[] result = new String[1]; - overriders[0] = mContext.getSettingsProvider().acquireOverridesBuilder(this) + overriders[0] = mContext.getSettingsProvider().acquireOverridesBuilder() .addSetting(secure, key, "Some craziness") .build(); synchronized (lock) { @@ -137,7 +134,7 @@ public class FakeSettingsProviderTest extends SysuiTestCase { lock.notify(); } overriders[1] = mContext.getSettingsProvider() - .acquireOverridesBuilder(FakeSettingsProviderTest.this) + .acquireOverridesBuilder() .addSetting(secure, key, "default value") .build(); // Ensure that the default is the one we set, and not left over from diff --git a/tools/aapt2/integration-tests/AppOne/Android.mk b/tools/aapt2/integration-tests/AppOne/Android.mk index a6f32d4b9fdc..38bd5b5e3275 100644 --- a/tools/aapt2/integration-tests/AppOne/Android.mk +++ b/tools/aapt2/integration-tests/AppOne/Android.mk @@ -21,6 +21,7 @@ LOCAL_USE_AAPT2 := true LOCAL_PACKAGE_NAME := AaptTestAppOne LOCAL_MODULE_TAGS := tests LOCAL_SRC_FILES := $(call all-java-files-under,src) +LOCAL_ASSET_DIR := $(LOCAL_PATH)/assets $(LOCAL_PATH)/assets2 LOCAL_STATIC_ANDROID_LIBRARIES := \ AaptTestStaticLibOne \ AaptTestStaticLibTwo diff --git a/tools/aapt2/integration-tests/AppOne/assets/subdir/subsubdir/test.txt b/tools/aapt2/integration-tests/AppOne/assets/subdir/subsubdir/test.txt new file mode 100644 index 000000000000..125194943ec8 --- /dev/null +++ b/tools/aapt2/integration-tests/AppOne/assets/subdir/subsubdir/test.txt @@ -0,0 +1 @@ +subdir/subsubdir/test.txt comes from assets diff --git a/tools/aapt2/integration-tests/AppOne/assets/test.txt b/tools/aapt2/integration-tests/AppOne/assets/test.txt new file mode 100644 index 000000000000..88266de2b4d4 --- /dev/null +++ b/tools/aapt2/integration-tests/AppOne/assets/test.txt @@ -0,0 +1 @@ +test.txt came from assets diff --git a/tools/aapt2/integration-tests/AppOne/assets2/new.txt b/tools/aapt2/integration-tests/AppOne/assets2/new.txt new file mode 100644 index 000000000000..f4963a95503a --- /dev/null +++ b/tools/aapt2/integration-tests/AppOne/assets2/new.txt @@ -0,0 +1 @@ +new.txt came from assets2 diff --git a/tools/aapt2/integration-tests/AppOne/assets2/test.txt b/tools/aapt2/integration-tests/AppOne/assets2/test.txt new file mode 100644 index 000000000000..5d8b36c0d52d --- /dev/null +++ b/tools/aapt2/integration-tests/AppOne/assets2/test.txt @@ -0,0 +1 @@ +test.txt came from assets2 diff --git a/tools/aapt2/link/Link.cpp b/tools/aapt2/link/Link.cpp index c8f02171bfd3..10421115b629 100644 --- a/tools/aapt2/link/Link.cpp +++ b/tools/aapt2/link/Link.cpp @@ -77,6 +77,7 @@ struct LinkOptions { std::string manifest_path; std::vector<std::string> include_paths; std::vector<std::string> overlay_files; + std::vector<std::string> assets_dirs; bool output_to_directory = false; bool auto_add_overlay = false; @@ -1412,6 +1413,46 @@ class LinkCommand { return doc; } + bool CopyAssetsDirsToApk(IArchiveWriter* writer) { + std::map<std::string, std::unique_ptr<io::RegularFile>> merged_assets; + for (const std::string& assets_dir : options_.assets_dirs) { + Maybe<std::vector<std::string>> files = + file::FindFiles(assets_dir, context_->GetDiagnostics(), nullptr); + if (!files) { + return false; + } + + for (const std::string& file : files.value()) { + std::string full_key = "assets/" + file; + std::string full_path = assets_dir; + file::AppendPath(&full_path, file); + + auto iter = merged_assets.find(full_key); + if (iter == merged_assets.end()) { + merged_assets.emplace(std::move(full_key), + util::make_unique<io::RegularFile>(Source(std::move(full_path)))); + } else if (context_->IsVerbose()) { + context_->GetDiagnostics()->Warn(DiagMessage(iter->second->GetSource()) + << "asset file overrides '" << full_path << "'"); + } + } + } + + for (auto& entry : merged_assets) { + uint32_t compression_flags = ArchiveEntry::kCompress; + std::string extension = file::GetExtension(entry.first).to_string(); + if (options_.extensions_to_not_compress.count(extension) > 0) { + compression_flags = 0u; + } + + if (!CopyFileToArchive(entry.second.get(), entry.first, compression_flags, writer, + context_)) { + return false; + } + } + return true; + } + /** * Writes the AndroidManifest, ResourceTable, and all XML files referenced by * the ResourceTable to the IArchiveWriter. @@ -1724,11 +1765,9 @@ class LinkCommand { } // Start writing the base APK. - std::unique_ptr<IArchiveWriter> archive_writer = - MakeArchiveWriter(options_.output_path); + std::unique_ptr<IArchiveWriter> archive_writer = MakeArchiveWriter(options_.output_path); if (!archive_writer) { - context_->GetDiagnostics()->Error(DiagMessage() - << "failed to create archive"); + context_->GetDiagnostics()->Error(DiagMessage() << "failed to create archive"); return 1; } @@ -1743,16 +1782,15 @@ class LinkCommand { XmlReferenceLinker manifest_linker; if (manifest_linker.Consume(context_, manifest_xml.get())) { if (options_.generate_proguard_rules_path && - !proguard::CollectProguardRulesForManifest( - Source(options_.manifest_path), manifest_xml.get(), - &proguard_keep_set)) { + !proguard::CollectProguardRulesForManifest(Source(options_.manifest_path), + manifest_xml.get(), &proguard_keep_set)) { error = true; } if (options_.generate_main_dex_proguard_rules_path && - !proguard::CollectProguardRulesForManifest( - Source(options_.manifest_path), manifest_xml.get(), - &proguard_main_dex_keep_set, true)) { + !proguard::CollectProguardRulesForManifest(Source(options_.manifest_path), + manifest_xml.get(), + &proguard_main_dex_keep_set, true)) { error = true; } @@ -1776,13 +1814,15 @@ class LinkCommand { } if (error) { - context_->GetDiagnostics()->Error(DiagMessage() - << "failed processing manifest"); + context_->GetDiagnostics()->Error(DiagMessage() << "failed processing manifest"); return 1; } - if (!WriteApk(archive_writer.get(), &proguard_keep_set, manifest_xml.get(), - &final_table_)) { + if (!WriteApk(archive_writer.get(), &proguard_keep_set, manifest_xml.get(), &final_table_)) { + return 1; + } + + if (!CopyAssetsDirsToApk(archive_writer.get())) { return 1; } @@ -1863,12 +1903,6 @@ class LinkCommand { proguard_main_dex_keep_set)) { return 1; } - - if (context_->IsVerbose()) { - DebugPrintTableOptions debug_print_table_options; - debug_print_table_options.show_sources = true; - Debug::PrintTable(&final_table_, debug_print_table_options); - } return 0; } @@ -1916,6 +1950,9 @@ int Link(const std::vector<StringPiece>& args) { .RequiredFlag("--manifest", "Path to the Android manifest to build", &options.manifest_path) .OptionalFlagList("-I", "Adds an Android APK to link against", &options.include_paths) + .OptionalFlagList("-A", + "An assets directory to include in the APK. These are unprocessed.", + &options.assets_dirs) .OptionalFlagList("-R", "Compilation unit to link, using `overlay` semantics.\n" "The last conflicting resource given takes precedence.", diff --git a/tools/aapt2/util/Files.cpp b/tools/aapt2/util/Files.cpp index aa840e2e3c8d..d10351b48eb3 100644 --- a/tools/aapt2/util/Files.cpp +++ b/tools/aapt2/util/Files.cpp @@ -264,5 +264,57 @@ bool FileFilter::operator()(const std::string& filename, FileType type) const { return true; } +Maybe<std::vector<std::string>> FindFiles(const android::StringPiece& path, IDiagnostics* diag, + const FileFilter* filter) { + const std::string root_dir = path.to_string(); + std::unique_ptr<DIR, decltype(closedir)*> d(opendir(root_dir.data()), closedir); + if (!d) { + diag->Error(DiagMessage() << android::base::SystemErrorCodeToString(errno)); + return {}; + } + + std::vector<std::string> files; + std::vector<std::string> subdirs; + while (struct dirent* entry = readdir(d.get())) { + if (util::StartsWith(entry->d_name, ".")) { + continue; + } + + std::string file_name = entry->d_name; + std::string full_path = root_dir; + AppendPath(&full_path, file_name); + const FileType file_type = GetFileType(full_path); + + if (filter != nullptr) { + if (!(*filter)(file_name, file_type)) { + continue; + } + } + + if (file_type == file::FileType::kDirectory) { + subdirs.push_back(std::move(file_name)); + } else { + files.push_back(std::move(file_name)); + } + } + + // Now process subdirs. + for (const std::string& subdir : subdirs) { + std::string full_subdir = root_dir; + AppendPath(&full_subdir, subdir); + Maybe<std::vector<std::string>> subfiles = FindFiles(full_subdir, diag, filter); + if (!subfiles) { + return {}; + } + + for (const std::string& subfile : subfiles.value()) { + std::string new_file = subdir; + AppendPath(&new_file, subfile); + files.push_back(new_file); + } + } + return files; +} + } // namespace file } // namespace aapt diff --git a/tools/aapt2/util/Files.h b/tools/aapt2/util/Files.h index 95c492f3b3af..b3b1e484d27b 100644 --- a/tools/aapt2/util/Files.h +++ b/tools/aapt2/util/Files.h @@ -132,6 +132,11 @@ class FileFilter { std::vector<std::string> pattern_tokens_; }; +// Returns a list of files relative to the directory identified by `path`. +// An optional FileFilter filters out any files that don't pass. +Maybe<std::vector<std::string>> FindFiles(const android::StringPiece& path, IDiagnostics* diag, + const FileFilter* filter = nullptr); + } // namespace file } // namespace aapt diff --git a/tools/layoutlib/bridge/src/android/graphics/drawable/AdaptiveIconDrawable_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/drawable/AdaptiveIconDrawable_Delegate.java new file mode 100644 index 000000000000..7e9432dd71ef --- /dev/null +++ b/tools/layoutlib/bridge/src/android/graphics/drawable/AdaptiveIconDrawable_Delegate.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.graphics.drawable; + +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.PorterDuff.Mode; +import android.graphics.PorterDuffXfermode; +import android.graphics.Rect; +import android.graphics.drawable.AdaptiveIconDrawable.LayerState; + +/** + * Delegate used to provide new implementation of a select few methods of {@link + * AdaptiveIconDrawable} + * <p> + * Through the layoutlib_create tool, the original methods of AdaptiveIconDrawable have been + * replaced by calls to methods of the same name in this delegate class. + */ +@SuppressWarnings("unused") +public class AdaptiveIconDrawable_Delegate { + @LayoutlibDelegate + /*package*/ static void draw(AdaptiveIconDrawable thisDrawable, Canvas canvas) { + // This is a workaround for the broken BitmapShader in layoutlib. This new draw methods + // avoids the use of the shader. + + for (int i = 0; i < LayerState.N_CHILDREN; i++) { + if (thisDrawable.mLayerState.mChildren[i] == null) { + continue; + } + final Drawable dr = thisDrawable.mLayerState.mChildren[i].mDrawable; + if (dr != null) { + dr.draw(canvas); + } + } + + if (thisDrawable.mMaskBitmap != null) { + Rect bounds = thisDrawable.getBounds(); + Paint paint = new Paint(); + paint.setXfermode(new PorterDuffXfermode(Mode.DST_IN)); + canvas.drawBitmap(thisDrawable.mMaskBitmap, bounds.left, bounds.top, paint); + } + } +} diff --git a/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java b/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java index 253ea6f27201..1282349dc40f 100644 --- a/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java +++ b/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java @@ -62,7 +62,7 @@ public class IWindowManagerImpl implements IWindowManager { // ---- implementation of IWindowManager that we care about ---- @Override - public int getRotation() throws RemoteException { + public int getDefaultDisplayRotation() throws RemoteException { return mRotation; } @@ -399,7 +399,7 @@ public class IWindowManagerImpl implements IWindowManager { } @Override - public int watchRotation(IRotationWatcher arg0) throws RemoteException { + public int watchRotation(IRotationWatcher arg0, int arg1) throws RemoteException { // TODO Auto-generated method stub return 0; } diff --git a/tools/layoutlib/bridge/src/android/view/accessibility/AccessibilityManager.java b/tools/layoutlib/bridge/src/android/view/accessibility/AccessibilityManager.java index 3ce7cab192e0..672ff6d684c6 100644 --- a/tools/layoutlib/bridge/src/android/view/accessibility/AccessibilityManager.java +++ b/tools/layoutlib/bridge/src/android/view/accessibility/AccessibilityManager.java @@ -95,6 +95,9 @@ public final class AccessibilityManager { public void notifyServicesStateChanged() { } + + public void setRelevantEventTypes(int eventTypes) { + } }; /** diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java index 327662898a72..a38584740e80 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java @@ -1607,6 +1607,12 @@ public class BridgeContext extends Context { } @Override + public Intent registerReceiver(BroadcastReceiver arg0, IntentFilter arg1, boolean arg2) { + // pass + return null; + } + + @Override public Intent registerReceiver(BroadcastReceiver arg0, IntentFilter arg1, String arg2, Handler arg3) { // pass @@ -1614,6 +1620,13 @@ public class BridgeContext extends Context { } @Override + public Intent registerReceiver(BroadcastReceiver arg0, IntentFilter arg1, + String arg2, Handler arg3, boolean arg4) { + // pass + return null; + } + + @Override public Intent registerReceiverAsUser(BroadcastReceiver arg0, UserHandle arg0p5, IntentFilter arg1, String arg2, Handler arg3) { // pass diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgePackageManager.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgePackageManager.java index 2274b90da49e..906ebb1fe39e 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgePackageManager.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgePackageManager.java @@ -657,6 +657,10 @@ public class BridgePackageManager extends PackageManager { } @Override + public void setUpdateAvailable(String packageName, boolean updateAvailable) { + } + + @Override public void deletePackage(String packageName, IPackageDeleteObserver observer, int flags) { } diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java index 7582fda6a0fb..4dfe47be0ef6 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java @@ -96,18 +96,6 @@ public final class BridgeWindowSession implements IWindowSession { } @Override - public void repositionChild(IWindow window, int left, int top, int right, int bottom, - long deferTransactionUntilFrame, Rect outFrame) { - // pass for now. - return; - } - - @Override - public void performDeferredDestroy(IWindow window) { - // pass for now. - } - - @Override public boolean outOfMemory(IWindow window) throws RemoteException { return false; } diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/ResourceHelper.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/ResourceHelper.java index f1e7b5177e13..45337742998f 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/ResourceHelper.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/ResourceHelper.java @@ -415,9 +415,9 @@ public final class ResourceHelper { BridgeXmlBlockParser blockParser = new BridgeXmlBlockParser( parser, context, isFramework); try { - FontConfig config = FontResourcesParser.parse(blockParser, context - .getResources()); - typeface = Typeface.createFromResources(config, context.getAssets(), + FontResourcesParser.FamilyResourceEntry entry = + FontResourcesParser.parse(blockParser, context.getResources()); + typeface = Typeface.createFromResources(entry, context.getAssets(), fontName); } catch (XmlPullParserException | IOException e) { Bridge.getLog().error(null, "Failed to parse file " + fontName, diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/adaptive_icon.png b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/adaptive_icon.png Binary files differnew file mode 100644 index 000000000000..7014ddbbb7da --- /dev/null +++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/adaptive_icon.png diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/drawable/adaptive.xml b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/drawable/adaptive.xml new file mode 100644 index 000000000000..8f862c86873c --- /dev/null +++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/drawable/adaptive.xml @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="utf-8"?> + +<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> + <background android:drawable="@android:color/red" /> + <foreground android:drawable="@drawable/headset" /> +</adaptive-icon>
\ No newline at end of file diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/adaptive_icon.xml b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/adaptive_icon.xml new file mode 100644 index 000000000000..ca9fa55d5424 --- /dev/null +++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/adaptive_icon.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="utf-8"?> + +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:padding="16dp" + android:orientation="horizontal" + android:layout_width="fill_parent" + android:layout_height="fill_parent"> + <ImageView + android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:src="@drawable/adaptive" /> + +</LinearLayout> + diff --git a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/RenderTests.java b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/RenderTests.java index 7199781349a6..2b5e0f9dd5fa 100644 --- a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/RenderTests.java +++ b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/RenderTests.java @@ -396,6 +396,22 @@ public class RenderTests extends RenderTestBase { } @Test + public void testAdaptiveIcon() throws ClassNotFoundException { + // Create the layout pull parser. + LayoutPullParser parser = createLayoutPullParser("adaptive_icon.xml"); + // Create LayoutLibCallback. + LayoutLibTestCallback layoutLibCallback = + new LayoutLibTestCallback(getLogger(), mDefaultClassLoader); + layoutLibCallback.initResources(); + + SessionParams params = getSessionParams(parser, ConfigGenerator.NEXUS_5, + layoutLibCallback, "Theme.Material.NoActionBar.Fullscreen", false, + RenderingMode.V_SCROLL, 22); + + renderAndVerify(params, "adaptive_icon.png"); + } + + @Test public void testColorTypedValue() throws Exception { // Setup // Create the layout pull parser for our resources (empty.xml can not be part of the test diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java index b0aa3c2989a5..4f226cbad01f 100644 --- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java +++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java @@ -163,6 +163,7 @@ public final class CreateInfo implements ICreateInfo { "android.content.res.TypedArray#obtain", "android.graphics.BitmapFactory#finishDecode", "android.graphics.BitmapFactory#setDensityFromOptions", + "android.graphics.drawable.AdaptiveIconDrawable#draw", "android.graphics.drawable.AnimatedVectorDrawable$VectorDrawableAnimatorRT#useLastSeenTarget", "android.graphics.drawable.AnimatedVectorDrawable$VectorDrawableAnimatorRT#onDraw", "android.graphics.drawable.GradientDrawable#buildRing", @@ -332,6 +333,8 @@ public final class CreateInfo implements ICreateInfo { * needed when access from the delegate classes is needed. */ private final static String[] PROMOTED_FIELDS = new String[] { + "android.graphics.drawable.AdaptiveIconDrawable#mMaskBitmap", + "android.graphics.drawable.AdaptiveIconDrawable#mPaint", "android.graphics.drawable.VectorDrawable#mVectorState", "android.view.Choreographer#mLastFrameTimeNanos", "android.graphics.FontFamily#mBuilderPtr" diff --git a/wifi/java/android/net/wifi/IWifiManager.aidl b/wifi/java/android/net/wifi/IWifiManager.aidl index 18c124503136..af48d0a2b6ca 100644 --- a/wifi/java/android/net/wifi/IWifiManager.aidl +++ b/wifi/java/android/net/wifi/IWifiManager.aidl @@ -16,7 +16,11 @@ package android.net.wifi; + +import android.content.pm.ParceledListSlice; + import android.net.wifi.hotspot2.PasspointConfiguration; + import android.net.wifi.WifiConfiguration; import android.net.wifi.WifiInfo; import android.net.wifi.ScanSettings; @@ -51,9 +55,9 @@ interface IWifiManager */ oneway void requestActivityInfo(in ResultReceiver result); - List<WifiConfiguration> getConfiguredNetworks(); + ParceledListSlice getConfiguredNetworks(); - List<WifiConfiguration> getPrivilegedConfiguredNetworks(); + ParceledListSlice getPrivilegedConfiguredNetworks(); WifiConfiguration getMatchingWifiConfig(in ScanResult scanResult); diff --git a/wifi/java/android/net/wifi/IconInfo.aidl b/wifi/java/android/net/wifi/IconInfo.aidl new file mode 100644 index 000000000000..a7bb2ef8bc3f --- /dev/null +++ b/wifi/java/android/net/wifi/IconInfo.aidl @@ -0,0 +1,19 @@ +/** + * Copyright (c) 2017, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net.wifi; + +parcelable IconInfo; diff --git a/wifi/java/android/net/wifi/IconInfo.java b/wifi/java/android/net/wifi/IconInfo.java new file mode 100644 index 000000000000..0eae363861f8 --- /dev/null +++ b/wifi/java/android/net/wifi/IconInfo.java @@ -0,0 +1,111 @@ +/* + * 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.net.wifi; + +import android.os.Parcelable; +import android.text.TextUtils; +import android.os.Parcel; + +import java.util.Arrays; +import java.util.Objects; + +/** + * A class representing icon information. + */ +public final class IconInfo implements Parcelable { + /** + * Name of the icon file. + */ + private final String mFilename; + + /** + * Raw binary data of the icon. + */ + private final byte[] mData; + + public IconInfo(String filename, byte[] data) { + mFilename = filename; + mData = data; + } + + public IconInfo(IconInfo source) { + if (source == null) { + mFilename = null; + mData = null; + return; + } + + mFilename = source.mFilename; + if (source.mData != null) { + mData = Arrays.copyOf(source.mData, source.mData.length); + } else { + mData = null; + } + } + + public String getFilename() { + return mFilename; + } + + public byte[] getData() { + return mData; + } + + @Override + public boolean equals(Object thatObject) { + if (this == thatObject) { + return true; + } + if (!(thatObject instanceof IconInfo)) { + return false; + } + IconInfo that = (IconInfo) thatObject; + return TextUtils.equals(mFilename, that.mFilename) + && Arrays.equals(mData, that.mData); + } + + @Override + public int hashCode() { + return Objects.hash(mFilename, mData); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(mFilename); + dest.writeByteArray(mData); + } + + public static final Creator<IconInfo> CREATOR = + new Creator<IconInfo>() { + @Override + public IconInfo createFromParcel(Parcel in) { + String filename = in.readString(); + byte[] data = in.createByteArray(); + return new IconInfo(filename, data); + } + + @Override + public IconInfo[] newArray(int size) { + return new IconInfo[size]; + } + }; +} diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java index ed6a166d3fc6..4f2881b87616 100644 --- a/wifi/java/android/net/wifi/WifiManager.java +++ b/wifi/java/android/net/wifi/WifiManager.java @@ -21,6 +21,7 @@ import android.annotation.SdkConstant.SdkConstantType; import android.annotation.SystemApi; import android.bluetooth.BluetoothAdapter; import android.content.Context; +import android.content.pm.ParceledListSlice; import android.net.ConnectivityManager; import android.net.DhcpInfo; import android.net.Network; @@ -46,6 +47,7 @@ import com.android.server.net.NetworkPinner; import java.net.InetAddress; import java.util.List; import java.util.concurrent.CountDownLatch; +import java.util.Collections; /** * This class provides the primary API for managing all aspects of Wi-Fi @@ -114,166 +116,118 @@ public class WifiManager { public static final int WIFI_CREDENTIAL_FORGOT = 1; /** - * Broadcast intent action indicating that the a Passpoint release 2 icon has been received. - * @hide - */ - public static final String PASSPOINT_ICON_RECEIVED_ACTION = - "android.net.wifi.PASSPOINT_ICON_RECEIVED"; - /** @hide */ - public static final String EXTRA_PASSPOINT_ICON_FILE = "file"; - - /** - * Broadcast intent action indicating that the a Passpoint release - * 2 WNM frame has been received. - * @hide - */ - public static final String PASSPOINT_WNM_FRAME_RECEIVED_ACTION = - "android.net.wifi.PASSPOINT_WNM_FRAME_RECEIVED"; - /** - * Originating BSS - * @hide */ - public static final String EXTRA_PASSPOINT_WNM_BSSID = "bssid"; - /** - * SOAP-XML or OMA-DM - * @hide */ - public static final String EXTRA_PASSPOINT_WNM_METHOD = "method"; - /** - * Type of Passpoint match - * @hide */ - public static final String EXTRA_PASSPOINT_WNM_PPOINT_MATCH = "match"; - /** - * String - * @hide */ - public static final String EXTRA_PASSPOINT_WNM_URL = "url"; - /** - * Boolean true=ess, false=bss - * @hide */ - public static final String EXTRA_PASSPOINT_WNM_ESS = "ess"; - /** - * Delay in seconds - * @hide */ - public static final String EXTRA_PASSPOINT_WNM_DELAY = "delay"; - - /** * Broadcast intent action indicating that a Passpoint provider icon has been received. * + * Included extras: + * {@link #EXTRA_BSSID_LONG} + * {@link #EXTRA_ICON_INFO} + * * Receiver Required Permission: android.Manifest.permission.ACCESS_WIFI_STATE - */ - public static final String ACTION_PASSPOINT_ICON = - "android.net.wifi.action.PASSPOINT_ICON"; - /** - * BSSID of the sender. * - * Type: long + * <p>Note: The broadcast is only delivered to registered receivers - no manifest registered + * components will be launched. */ - public static final String EXTRA_PASSPOINT_ICON_BSSID = - "android.net.wifi.extra.PASSPOINT_ICON_BSSID"; + public static final String ACTION_PASSPOINT_ICON = "android.net.wifi.action.PASSPOINT_ICON"; /** - * Filename of the icon. + * BSSID of an AP in long representation. The {@link #EXTRA_BSSID} contains BSSID in + * String representation. * - * Type: String + * Retrieve with {@link android.content.Intent#getLongExtra(String, long)}. */ - public static final String EXTRA_PASSPOINT_ICON_FILENAME = - "android.net.wifi.extra.PASSPOINT_ICON_FILENAME"; + public static final String EXTRA_BSSID_LONG = "android.net.wifi.extra.BSSID_LONG"; /** - * Binary blob of the icon. + * Icon information. * - * Type: byte[] + * Retrieve with {@link android.content.Intent#getParcelableExtra(String)} and cast into + * {@link IconInfo}. */ - public static final String EXTRA_PASSPOINT_ICON_DATA = - "android.net.wifi.extra.PASSPOINT_ICON_DATA"; + public static final String EXTRA_ICON_INFO = "android.net.wifi.extra.ICON_INFO"; /** * Broadcast intent action indicating a Passpoint OSU Providers List element has been received. * + * Included extras: + * {@link #EXTRA_BSSID_LONG} + * {@link #EXTRA_ANQP_ELEMENT_DATA} + * * Receiver Required Permission: android.Manifest.permission.ACCESS_WIFI_STATE + * + * <p>Note: The broadcast is only delivered to registered receivers - no manifest registered + * components will be launched. + * */ public static final String ACTION_PASSPOINT_OSU_PROVIDERS_LIST = "android.net.wifi.action.PASSPOINT_OSU_PROVIDERS_LIST"; /** - * BSSID of the sender. - * - * Type: long - */ - public static final String EXTRA_PASSPOINT_OSU_PROVIDERS_LIST_BSSID = - "android.net.wifi.extra.PASSPOINT_OSU_PROVIDERS_LIST_BSSID"; - /** - * Raw data of OSU Providers List ANQP element. Refer to Section 4.8 of Hotspot 2.0 Release 2 - * Technical Specification for the exact data format. + * Raw binary data of an ANQP (Access Network Query Protocol) element. * - * Type: byte[] + * Retrieve with {@link android.content.Intent#getByteArrayExtra(String)}. */ - public static final String EXTRA_PASSPOINT_OSU_PROVIDERS_LIST_DATA = - "android.net.wifi.extra.PASSPOINT_OSU_PROVIDERS_LIST_DATA"; + public static final String EXTRA_ANQP_ELEMENT_DATA = + "android.net.wifi.extra.ANQP_ELEMENT_DATA"; /** * Broadcast intent action indicating that a Passpoint Deauth Imminent frame has been received. * + * Included extras: + * {@link #EXTRA_BSSID_LONG} + * {@link #EXTRA_ESS} + * {@link #EXTRA_DELAY} + * {@link #EXTRA_URL} + * * Receiver Required Permission: android.Manifest.permission.ACCESS_WIFI_STATE + * + * <p>Note: The broadcast is only delivered to registered receivers - no manifest registered + * components will be launched. + * */ public static final String ACTION_PASSPOINT_DEAUTH_IMMINENT = "android.net.wifi.action.PASSPOINT_DEAUTH_IMMINENT"; /** - * The BSSID of the sender. + * Flag indicating BSS (Basic Service Set) or ESS (Extended Service Set). This will be set to + * {@code true} for ESS. * - * Type: long + * Retrieve with {@link android.content.Intent#getBooleanExtra(String, boolean)}. */ - public static final String EXTRA_PASSPOINT_DEAUTH_IMMINENT_BSSID = - "android.net.wifi.extra.PASSPOINT_DEAUTH_IMMINENT_BSSID"; + public static final String EXTRA_ESS = "android.net.wifi.extra.ESS"; /** - * Flag indicating failure at BSS (Basic Service Set) or ESS (Extended Service Set) level. + * Delay in seconds. * - * Type: boolean + * Retrieve with {@link android.content.Intent#getIntExtra(String, int)}. */ - public static final String EXTRA_PASSPOINT_DEAUTH_IMMINENT_ESS = - "android.net.wifi.extra.PASSPOINT_DEAUTH_IMMINENT_ESS"; + public static final String EXTRA_DELAY = "android.net.wifi.extra.DELAY"; /** - * Delay in seconds that a device shall wait before attempting re-association to the same BSS - * or ESS (as indicated by {@link #EXTRA_PASSPOINT_DEAUTH_IMMINENT_ESS}. + * String representation of an URL. * - * Type: int + * Retrieve with {@link android.content.Intent#getStringExtra(String)}. */ - public static final String EXTRA_PASSPOINT_DEAUTH_IMMINENT_REAUTH_DELAY = - "android.net.wifi.extra.PASSPOINT_DEAUTH_IMMINENT_REAUTH_DELAY"; - /** - * URL that provides a webpage explaining the deauth reason. - * - * Type: String - */ - public static final String EXTRA_PASSPOINT_DEAUTH_IMMINENT_REASON_URL = - "android.net.wifi.extra.PASSPOINT_DEAUTH_IMMINENT_REASON_URL"; + public static final String EXTRA_URL = "android.net.wifi.extra.URL"; /** * Broadcast intent action indicating a Passpoint subscription remediation frame has been * received. * + * Included extras: + * {@link #EXTRA_BSSID_LONG} + * {@link #EXTRA_SUBSCRIPTION_REMEDIATION_METHOD} + * {@link #EXTRA_URL} + * * Receiver Required Permission: android.Manifest.permission.ACCESS_WIFI_STATE + * + ** <p>Note: The broadcast is only delivered to registered receivers - no manifest registered + * components will be launched. */ public static final String ACTION_PASSPOINT_SUBSCRIPTION_REMEDIATION = "android.net.wifi.action.PASSPOINT_SUBSCRIPTION_REMEDIATION"; /** - * The BSSID of the sender. - * - * Type: long - */ - public static final String EXTRA_PASSPOINT_SUBSCRIPTION_REMEDIATION_BSSID = - "android.net.wifi.extra.PASSPOINT_SUBSCRIPTION_REMEDIATION_BSSID"; - /** * The protocol supported by the subscription remediation server. The possible values are: * 0 - OMA DM * 1 - SOAP XML SPP * - * Type: int - */ - public static final String EXTRA_PASSPOINT_SUBSCRIPTION_REMEDIATION_SERVER_METHOD = - "android.net.wifi.extra.PASSPOINT_SUBSCRIPTION_REMEDIATION_SERVER_METHOD"; - /** - * URL of the subscription remediation server. - * - * Type: String + * Retrieve with {@link android.content.Intent#getIntExtra(String, int)}. */ - public static final String EXTRA_PASSPOINT_SUBSCRIPTION_REMEDIATION_SERVER_URL = - "android.net.wifi.extra.PASSPOINT_SUBSCRIPTION_REMEDIATION_SERVER_URL"; + public static final String EXTRA_SUBSCRIPTION_REMEDIATION_METHOD = + "android.net.wifi.extra.SUBSCRIPTION_REMEDIATION_METHOD"; /** * Broadcast intent action indicating that Wi-Fi has been enabled, disabled, @@ -859,7 +813,12 @@ public class WifiManager { */ public List<WifiConfiguration> getConfiguredNetworks() { try { - return mService.getConfiguredNetworks(); + ParceledListSlice<WifiConfiguration> parceledList = + mService.getConfiguredNetworks(); + if (parceledList == null) { + return Collections.emptyList(); + } + return parceledList.getList(); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -869,7 +828,12 @@ public class WifiManager { @SystemApi public List<WifiConfiguration> getPrivilegedConfiguredNetworks() { try { - return mService.getPrivilegedConfiguredNetworks(); + ParceledListSlice<WifiConfiguration> parceledList = + mService.getPrivilegedConfiguredNetworks(); + if (parceledList == null) { + return Collections.emptyList(); + } + return parceledList.getList(); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -972,12 +936,15 @@ public class WifiManager { * Name). In the case when there is an existing configuration with the same * FQDN, the new configuration will replace the existing configuration. * + * An {@link IllegalArgumentException} will be thrown on failure. + * * @param config The Passpoint configuration to be added - * @return true on success */ - public boolean addOrUpdatePasspointConfiguration(PasspointConfiguration config) { + public void addOrUpdatePasspointConfiguration(PasspointConfiguration config) { try { - return mService.addOrUpdatePasspointConfiguration(config); + if (!mService.addOrUpdatePasspointConfiguration(config)) { + throw new IllegalArgumentException(); + } } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -986,12 +953,15 @@ public class WifiManager { /** * Remove the Passpoint configuration identified by its FQDN (Fully Qualified Domain Name). * + * An {@link IllegalArgumentException} will be thrown on failure. + * * @param fqdn The FQDN of the passpoint configuration to be removed - * @return true on success */ - public boolean removePasspointConfiguration(String fqdn) { + public void removePasspointConfiguration(String fqdn) { try { - return mService.removePasspointConfiguration(fqdn); + if (!mService.removePasspointConfiguration(fqdn)) { + throw new IllegalArgumentException(); + } } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -1013,10 +983,13 @@ public class WifiManager { } /** - * Query for a Hotspot 2.0 release 2 OSU icon file. + * Query for a Hotspot 2.0 release 2 OSU icon file. An {@link #ACTION_PASSPOINT_ICON} intent + * will be broadcasted once the request is completed. The return value of + * {@link IconInfo#getData} from the intent extra will indicate the result of the request. + * A value of {@code null} will indicate a failure. * * @param bssid The BSSID of the AP - * @param fileName File name of the icon to query + * @param fileName Name of the icon file (remote file) to query from the AP */ public void queryPasspointIcon(long bssid, String fileName) { try { diff --git a/wifi/java/android/net/wifi/hotspot2/PasspointConfiguration.java b/wifi/java/android/net/wifi/hotspot2/PasspointConfiguration.java index 1f661c4580af..7de55aa5ffb2 100644 --- a/wifi/java/android/net/wifi/hotspot2/PasspointConfiguration.java +++ b/wifi/java/android/net/wifi/hotspot2/PasspointConfiguration.java @@ -333,6 +333,7 @@ public final class PasspointConfiguration implements Parcelable { * Validate the configuration data. * * @return true on success or false on failure + * @hide */ public boolean validate() { if (mHomeSp == null || !mHomeSp.validate()) { diff --git a/wifi/java/android/net/wifi/hotspot2/pps/Credential.java b/wifi/java/android/net/wifi/hotspot2/pps/Credential.java index 23888411c1b2..d8da84f28df6 100644 --- a/wifi/java/android/net/wifi/hotspot2/pps/Credential.java +++ b/wifi/java/android/net/wifi/hotspot2/pps/Credential.java @@ -286,6 +286,7 @@ public final class Credential implements Parcelable { * Validate the configuration data. * * @return true on success or false on failure + * @hide */ public boolean validate() { if (TextUtils.isEmpty(mUsername)) { @@ -443,6 +444,7 @@ public final class Credential implements Parcelable { * Validate the configuration data. * * @return true on success or false on failure + * @hide */ public boolean validate() { if (!TextUtils.equals(CERT_TYPE_X509V3, mCertType)) { @@ -569,6 +571,7 @@ public final class Credential implements Parcelable { * Validate the configuration data. * * @return true on success or false on failure + * @hide */ public boolean validate() { // Note: this only validate the format of IMSI string itself. Additional verification @@ -768,6 +771,7 @@ public final class Credential implements Parcelable { * Validate the configuration data. * * @return true on success or false on failure + * @hide */ public boolean validate() { if (TextUtils.isEmpty(mRealm)) { diff --git a/wifi/java/android/net/wifi/hotspot2/pps/HomeSp.java b/wifi/java/android/net/wifi/hotspot2/pps/HomeSp.java index 8ec40c00304f..68bdf37b1815 100644 --- a/wifi/java/android/net/wifi/hotspot2/pps/HomeSp.java +++ b/wifi/java/android/net/wifi/hotspot2/pps/HomeSp.java @@ -245,6 +245,7 @@ public final class HomeSp implements Parcelable { * Validate HomeSp data. * * @return true on success or false on failure + * @hide */ public boolean validate() { if (TextUtils.isEmpty(mFqdn)) { diff --git a/wifi/java/android/net/wifi/hotspot2/pps/Policy.java b/wifi/java/android/net/wifi/hotspot2/pps/Policy.java index 63238e86c0fd..da36a116fbbe 100644 --- a/wifi/java/android/net/wifi/hotspot2/pps/Policy.java +++ b/wifi/java/android/net/wifi/hotspot2/pps/Policy.java @@ -253,6 +253,7 @@ public final class Policy implements Parcelable { * Validate RoamingParnter data. * * @return true on success + * @hide */ public boolean validate() { if (TextUtils.isEmpty(mFqdn)) { @@ -393,6 +394,7 @@ public final class Policy implements Parcelable { * Validate Policy data. * * @return true on success + * @hide */ public boolean validate() { if (mPolicyUpdate == null) { diff --git a/wifi/java/android/net/wifi/hotspot2/pps/UpdateParameter.java b/wifi/java/android/net/wifi/hotspot2/pps/UpdateParameter.java index 70264b0e625a..ae051b0a6e3f 100644 --- a/wifi/java/android/net/wifi/hotspot2/pps/UpdateParameter.java +++ b/wifi/java/android/net/wifi/hotspot2/pps/UpdateParameter.java @@ -251,6 +251,7 @@ public final class UpdateParameter implements Parcelable { * Validate UpdateParameter data. * * @return true on success + * @hide */ public boolean validate() { if (mUpdateIntervalInMinutes == Long.MIN_VALUE) { diff --git a/wifi/tests/src/android/net/wifi/IconInfoTest.java b/wifi/tests/src/android/net/wifi/IconInfoTest.java new file mode 100644 index 000000000000..2fdb484fce19 --- /dev/null +++ b/wifi/tests/src/android/net/wifi/IconInfoTest.java @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package android.net.wifi; + +import static org.junit.Assert.assertEquals; + +import android.net.wifi.IconInfo; +import android.os.Parcel; +import android.test.suitebuilder.annotation.SmallTest; + +import org.junit.Test; + +/** + * Unit tests for {@link android.net.wifi.IconInfo}. + */ +@SmallTest +public class IconInfoTest { + private static final String TEST_FILENAME = "testIcon"; + private static final byte[] TEST_DATA = new byte[] {0x12, 0x23, 0x34, 0x45, 0x56, 0x67}; + + /** + * Verify parcel write and read consistency for the given {@link IconInfo} + * + * @param writeIcon the {@link IconInfo} to write and verify + * @throws Exception + */ + private static void verifyParcel(IconInfo writeIcon) throws Exception { + Parcel parcel = Parcel.obtain(); + writeIcon.writeToParcel(parcel, 0); + + parcel.setDataPosition(0); // Rewind data position back to the beginning for read. + IconInfo readIcon = IconInfo.CREATOR.createFromParcel(parcel); + assertEquals(writeIcon, readIcon); + } + + /** + * Verify parcel serialization for a {@link IconInfo} with null data. + * + * @throws Exception + */ + @Test + public void verifyParcelWithNullData() throws Exception { + verifyParcel(new IconInfo(TEST_FILENAME, (byte[]) null)); + } + + /** + * Verify parcel serialization for a {@link IconInfo} with zero length data. + * + * @throws Exception + */ + @Test + public void verifyParcelWithZeroLengthData() throws Exception { + verifyParcel(new IconInfo(TEST_FILENAME, new byte[0])); + } + + /** + * Verify parcel serialization for a {@link IconInfo} with non-zero length data. + * + * @throws Exception + */ + @Test + public void verifyParcelWithNonZeroLengthData() throws Exception { + verifyParcel(new IconInfo(TEST_FILENAME, TEST_DATA)); + } + + /** + * Verify parcel serialization for a {@link IconInfo} with a null filename. + * + * @throws Exception + */ + @Test + public void verifyParcelWithNullFilename() throws Exception { + verifyParcel(new IconInfo(null, TEST_DATA)); + } + + /** + * Verify the copy constructor with non-null filename and data. + * + * @throws Exception + */ + @Test + public void verifyCopyConstructor() throws Exception { + IconInfo source = new IconInfo(TEST_FILENAME, TEST_DATA); + assertEquals(source, new IconInfo(source)); + } + + /** + * Verify the copy constructor with null data. + * + * @throws Exception + */ + @Test + public void verifyCopyConstructorWithNullData() throws Exception { + IconInfo source = new IconInfo(TEST_FILENAME, (byte[]) null); + assertEquals(source, new IconInfo(source)); + } + + /** + * Verify the copy constructor with null file name. + * + * @throws Exception + */ + @Test + public void verifyCopyConstructorWithNullFilename() throws Exception { + IconInfo source = new IconInfo(null, TEST_DATA); + assertEquals(source, new IconInfo(source)); + } +} |