diff options
71 files changed, 3441 insertions, 1332 deletions
diff --git a/Android.bp b/Android.bp index f9b60e67e659..52e2508099ba 100644 --- a/Android.bp +++ b/Android.bp @@ -1220,11 +1220,6 @@ stubs_defaults { srcs_lib_whitelist_dirs: frameworks_base_subdirs, srcs_lib_whitelist_pkgs: packages_to_document, libs: [ - "core-oj", - "core-libart", - "conscrypt", - "bouncycastle", - "okhttp", "ext", "framework", "voip-common", diff --git a/api/current.txt b/api/current.txt index 07a2560b7868..a52c1cc1cd41 100755 --- a/api/current.txt +++ b/api/current.txt @@ -1403,6 +1403,7 @@ package android { field public static final int textFilterEnabled = 16843007; // 0x10100ff field public static final int textFontWeight = 16844165; // 0x1010585 field public static final int textIsSelectable = 16843542; // 0x1010316 + field public static final int textLocale = 16844178; // 0x1010592 field public static final int textOff = 16843045; // 0x1010125 field public static final int textOn = 16843044; // 0x1010124 field public static final int textScaleX = 16843089; // 0x1010151 @@ -9316,6 +9317,7 @@ package android.content { method public final android.net.Uri insert(android.net.Uri, android.content.ContentValues); method public static boolean isSyncActive(android.accounts.Account, java.lang.String); method public static boolean isSyncPending(android.accounts.Account, java.lang.String); + method public android.graphics.Bitmap loadThumbnail(android.net.Uri, android.util.Size, android.os.CancellationSignal) throws java.io.IOException; method public void notifyChange(android.net.Uri, android.database.ContentObserver); method public void notifyChange(android.net.Uri, android.database.ContentObserver, boolean); method public void notifyChange(android.net.Uri, android.database.ContentObserver, int); @@ -27446,6 +27448,7 @@ package android.net { public static class ConnectivityManager.NetworkCallback { ctor public ConnectivityManager.NetworkCallback(); method public void onAvailable(android.net.Network); + method public void onBlockedStatusChanged(android.net.Network, boolean); method public void onCapabilitiesChanged(android.net.Network, android.net.NetworkCapabilities); method public void onLinkPropertiesChanged(android.net.Network, android.net.LinkProperties); method public void onLosing(android.net.Network, int); @@ -27717,16 +27720,16 @@ package android.net { public class NetworkInfo implements android.os.Parcelable { method public int describeContents(); - method public android.net.NetworkInfo.DetailedState getDetailedState(); + method public deprecated android.net.NetworkInfo.DetailedState getDetailedState(); method public java.lang.String getExtraInfo(); method public deprecated java.lang.String getReason(); method public deprecated android.net.NetworkInfo.State getState(); - method public int getSubtype(); - method public java.lang.String getSubtypeName(); + method public deprecated int getSubtype(); + method public deprecated java.lang.String getSubtypeName(); method public deprecated int getType(); method public deprecated java.lang.String getTypeName(); method public deprecated boolean isAvailable(); - method public boolean isConnected(); + method public deprecated boolean isConnected(); method public deprecated boolean isConnectedOrConnecting(); method public deprecated boolean isFailover(); method public deprecated boolean isRoaming(); @@ -36810,7 +36813,7 @@ package android.provider { public static abstract interface MediaStore.Audio.AlbumColumns { field public static final java.lang.String ALBUM = "album"; - field public static final java.lang.String ALBUM_ART = "album_art"; + field public static final deprecated java.lang.String ALBUM_ART = "album_art"; field public static final java.lang.String ALBUM_ID = "album_id"; field public static final java.lang.String ALBUM_KEY = "album_key"; field public static final java.lang.String ARTIST = "artist"; @@ -36932,7 +36935,7 @@ package android.provider { } public static abstract interface MediaStore.Audio.PlaylistsColumns { - field public static final java.lang.String DATA = "_data"; + field public static final deprecated java.lang.String DATA = "_data"; field public static final java.lang.String DATE_ADDED = "date_added"; field public static final java.lang.String DATE_MODIFIED = "date_modified"; field public static final java.lang.String NAME = "name"; @@ -36994,15 +36997,15 @@ package android.provider { public static class MediaStore.Images.Thumbnails implements android.provider.BaseColumns { ctor public MediaStore.Images.Thumbnails(); - method public static void cancelThumbnailRequest(android.content.ContentResolver, long); - method public static void cancelThumbnailRequest(android.content.ContentResolver, long, long); + method public static deprecated void cancelThumbnailRequest(android.content.ContentResolver, long); + method public static deprecated void cancelThumbnailRequest(android.content.ContentResolver, long, long); method public static android.net.Uri getContentUri(java.lang.String); - method public static android.graphics.Bitmap getThumbnail(android.content.ContentResolver, long, int, android.graphics.BitmapFactory.Options); - method public static android.graphics.Bitmap getThumbnail(android.content.ContentResolver, long, long, int, android.graphics.BitmapFactory.Options); + method public static deprecated android.graphics.Bitmap getThumbnail(android.content.ContentResolver, long, int, android.graphics.BitmapFactory.Options); + method public static deprecated android.graphics.Bitmap getThumbnail(android.content.ContentResolver, long, long, int, android.graphics.BitmapFactory.Options); method public static final android.database.Cursor query(android.content.ContentResolver, android.net.Uri, java.lang.String[]); method public static final android.database.Cursor queryMiniThumbnail(android.content.ContentResolver, long, int, java.lang.String[]); method public static final android.database.Cursor queryMiniThumbnails(android.content.ContentResolver, android.net.Uri, int, java.lang.String[]); - field public static final java.lang.String DATA = "_data"; + field public static final deprecated java.lang.String DATA = "_data"; field public static final java.lang.String DEFAULT_SORT_ORDER = "image_id ASC"; field public static final android.net.Uri EXTERNAL_CONTENT_URI; field public static final int FULL_SCREEN_KIND = 2; // 0x2 @@ -37017,7 +37020,7 @@ package android.provider { } public static abstract interface MediaStore.MediaColumns implements android.provider.BaseColumns { - field public static final java.lang.String DATA = "_data"; + field public static final deprecated java.lang.String DATA = "_data"; field public static final java.lang.String DATE_ADDED = "date_added"; field public static final java.lang.String DATE_MODIFIED = "date_modified"; field public static final java.lang.String DISPLAY_NAME = "_display_name"; @@ -37045,12 +37048,12 @@ package android.provider { public static class MediaStore.Video.Thumbnails implements android.provider.BaseColumns { ctor public MediaStore.Video.Thumbnails(); - method public static void cancelThumbnailRequest(android.content.ContentResolver, long); - method public static void cancelThumbnailRequest(android.content.ContentResolver, long, long); + method public static deprecated void cancelThumbnailRequest(android.content.ContentResolver, long); + method public static deprecated void cancelThumbnailRequest(android.content.ContentResolver, long, long); method public static android.net.Uri getContentUri(java.lang.String); - method public static android.graphics.Bitmap getThumbnail(android.content.ContentResolver, long, int, android.graphics.BitmapFactory.Options); - method public static android.graphics.Bitmap getThumbnail(android.content.ContentResolver, long, long, int, android.graphics.BitmapFactory.Options); - field public static final java.lang.String DATA = "_data"; + method public static deprecated android.graphics.Bitmap getThumbnail(android.content.ContentResolver, long, int, android.graphics.BitmapFactory.Options); + method public static deprecated android.graphics.Bitmap getThumbnail(android.content.ContentResolver, long, long, int, android.graphics.BitmapFactory.Options); + field public static final deprecated java.lang.String DATA = "_data"; field public static final java.lang.String DEFAULT_SORT_ORDER = "video_id ASC"; field public static final android.net.Uri EXTERNAL_CONTENT_URI; field public static final int FULL_SCREEN_KIND = 2; // 0x2 @@ -45264,6 +45267,7 @@ package android.text.style { method public int getSpanTypeId(); method public android.content.res.ColorStateList getTextColor(); method public int getTextFontWeight(); + method public android.os.LocaleList getTextLocales(); method public int getTextSize(); method public int getTextStyle(); method public android.graphics.Typeface getTypeface(); @@ -51415,6 +51419,7 @@ package android.view.textclassifier { method public default android.view.textclassifier.TextClassification classifyText(android.view.textclassifier.TextClassification.Request); method public default android.view.textclassifier.TextClassification classifyText(java.lang.CharSequence, int, int, android.os.LocaleList); method public default void destroy(); + method public default android.view.textclassifier.TextLanguage detectLanguage(android.view.textclassifier.TextLanguage.Request); method public default android.view.textclassifier.TextLinks generateLinks(android.view.textclassifier.TextLinks.Request); method public default int getMaxGenerateLinksTextLength(); method public default boolean isDestroyed(); @@ -51455,6 +51460,39 @@ package android.view.textclassifier { field public static final android.os.Parcelable.Creator<android.view.textclassifier.TextClassifier.EntityConfig> CREATOR; } + public final class TextLanguage implements android.os.Parcelable { + method public int describeContents(); + method public float getConfidenceScore(android.icu.util.ULocale); + method public android.os.Bundle getExtras(); + method public java.lang.String getId(); + method public android.icu.util.ULocale getLocale(int); + method public int getLocaleHypothesisCount(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.view.textclassifier.TextLanguage> CREATOR; + } + + public static final class TextLanguage.Builder { + ctor public TextLanguage.Builder(); + method public android.view.textclassifier.TextLanguage build(); + method public android.view.textclassifier.TextLanguage.Builder putLocale(android.icu.util.ULocale, float); + method public android.view.textclassifier.TextLanguage.Builder setExtras(android.os.Bundle); + method public android.view.textclassifier.TextLanguage.Builder setId(java.lang.String); + } + + public static final class TextLanguage.Request implements android.os.Parcelable { + method public int describeContents(); + method public android.os.Bundle getExtras(); + method public java.lang.CharSequence getText(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.view.textclassifier.TextLanguage.Request> CREATOR; + } + + public static final class TextLanguage.Request.Builder { + ctor public TextLanguage.Request.Builder(java.lang.CharSequence); + method public android.view.textclassifier.TextLanguage.Request build(); + method public android.view.textclassifier.TextLanguage.Request.Builder setExtras(android.os.Bundle); + } + public final class TextLinks implements android.os.Parcelable { method public int apply(android.text.Spannable, int, java.util.function.Function<android.view.textclassifier.TextLinks.TextLink, android.view.textclassifier.TextLinks.TextLinkSpan>); method public int describeContents(); diff --git a/api/system-current.txt b/api/system-current.txt index 580a7601ca81..26036ea9c281 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -4471,6 +4471,23 @@ package android.provider { field public static final java.lang.String VOLUME_HUSH_GESTURE = "volume_hush_gesture"; } + public static final class Telephony.Carriers implements android.provider.BaseColumns { + field public static final java.lang.String APN_SET_ID = "apn_set_id"; + field public static final int CARRIER_EDITED = 4; // 0x4 + field public static final java.lang.String EDITED = "edited"; + field public static final java.lang.String MAX_CONNS = "max_conns"; + field public static final java.lang.String MAX_CONNS_TIME = "max_conns_time"; + field public static final java.lang.String MODEM_COGNITIVE = "modem_cognitive"; + field public static final java.lang.String MTU = "mtu"; + field public static final int NO_SET_SET = 0; // 0x0 + field public static final int UNEDITED = 0; // 0x0 + field public static final int USER_DELETED = 2; // 0x2 + field public static final java.lang.String USER_EDITABLE = "user_editable"; + field public static final int USER_EDITED = 1; // 0x1 + field public static final java.lang.String USER_VISIBLE = "user_visible"; + field public static final java.lang.String WAIT_TIME = "wait_time"; + } + public final class TimeZoneRulesDataContract { field public static final java.lang.String AUTHORITY = "com.android.timezone"; } @@ -5486,6 +5503,7 @@ package android.telephony { method public deprecated boolean isVisualVoicemailEnabled(android.telecom.PhoneAccountHandle); method public boolean needsOtaServiceProvisioning(); method public int setAllowedCarriers(int, java.util.List<android.service.carrier.CarrierIdentifier>); + method public void setCarrierDataEnabled(boolean); method public void setDataActivationState(int); method public deprecated void setDataEnabled(int, boolean); method public void setDataRoamingEnabled(boolean); @@ -6372,7 +6390,8 @@ package android.telephony.ims.feature { } public static class MmTelFeature.MmTelCapabilities { - ctor public MmTelFeature.MmTelCapabilities(android.telephony.ims.feature.ImsFeature.Capabilities); + ctor public MmTelFeature.MmTelCapabilities(); + ctor public deprecated MmTelFeature.MmTelCapabilities(android.telephony.ims.feature.ImsFeature.Capabilities); ctor public MmTelFeature.MmTelCapabilities(int); method public final void addCapabilities(int); method public final boolean isCapable(int); diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index b289a3e59efd..92daf08dc59b 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -51,6 +51,7 @@ import android.content.pm.UserInfo; import android.graphics.Bitmap; import android.net.ProxyInfo; import android.net.Uri; +import android.os.Binder; import android.os.Bundle; import android.os.Parcelable; import android.os.PersistableBundle; @@ -5786,7 +5787,8 @@ public class DevicePolicyManager { } if (mService != null) { try { - return mService.checkDeviceIdentifierAccess(packageName, userId); + return mService.checkDeviceIdentifierAccess(packageName, userId, + Binder.getCallingPid(), Binder.getCallingUid()); } catch (RemoteException re) { throw re.rethrowFromSystemServer(); } diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl index cf0cad83b41d..ce1f4ef9e2e4 100644 --- a/core/java/android/app/admin/IDevicePolicyManager.aidl +++ b/core/java/android/app/admin/IDevicePolicyManager.aidl @@ -153,7 +153,7 @@ interface IDevicePolicyManager { void clearProfileOwner(in ComponentName who); boolean hasUserSetupCompleted(); - boolean checkDeviceIdentifierAccess(in String packageName, int userHandle); + boolean checkDeviceIdentifierAccess(in String packageName, int userHandle, int pid, int uid); void setDeviceOwnerLockScreenInfo(in ComponentName who, CharSequence deviceOwnerInfo); CharSequence getDeviceOwnerLockScreenInfo(); diff --git a/core/java/android/bluetooth/BluetoothA2dpSink.java b/core/java/android/bluetooth/BluetoothA2dpSink.java index fda2f8927535..cb996f3381b7 100755 --- a/core/java/android/bluetooth/BluetoothA2dpSink.java +++ b/core/java/android/bluetooth/BluetoothA2dpSink.java @@ -24,6 +24,7 @@ import android.content.ServiceConnection; import android.os.Binder; import android.os.IBinder; import android.os.RemoteException; +import android.os.UserHandle; import android.util.Log; import java.util.ArrayList; @@ -183,7 +184,7 @@ public final class BluetoothA2dpSink implements BluetoothProfile { ComponentName comp = intent.resolveSystemService(mContext.getPackageManager(), 0); intent.setComponent(comp); if (comp == null || !mContext.bindServiceAsUser(intent, mConnection, 0, - mContext.getUser())) { + UserHandle.CURRENT_OR_SELF)) { Log.e(TAG, "Could not bind to Bluetooth A2DP Service with " + intent); return false; } diff --git a/core/java/android/bluetooth/BluetoothAvrcpController.java b/core/java/android/bluetooth/BluetoothAvrcpController.java index e7c8944788fd..c447868d6f0c 100644 --- a/core/java/android/bluetooth/BluetoothAvrcpController.java +++ b/core/java/android/bluetooth/BluetoothAvrcpController.java @@ -23,6 +23,7 @@ import android.content.ServiceConnection; import android.os.Binder; import android.os.IBinder; import android.os.RemoteException; +import android.os.UserHandle; import android.util.Log; import java.util.ArrayList; @@ -138,7 +139,7 @@ public final class BluetoothAvrcpController implements BluetoothProfile { ComponentName comp = intent.resolveSystemService(mContext.getPackageManager(), 0); intent.setComponent(comp); if (comp == null || !mContext.bindServiceAsUser(intent, mConnection, 0, - mContext.getUser())) { + UserHandle.CURRENT_OR_SELF)) { Log.e(TAG, "Could not bind to Bluetooth AVRCP Controller Service with " + intent); return false; } diff --git a/core/java/android/bluetooth/BluetoothHeadsetClient.java b/core/java/android/bluetooth/BluetoothHeadsetClient.java index ec18d42698c1..549c1faddd90 100644 --- a/core/java/android/bluetooth/BluetoothHeadsetClient.java +++ b/core/java/android/bluetooth/BluetoothHeadsetClient.java @@ -25,6 +25,7 @@ import android.os.Binder; import android.os.Bundle; import android.os.IBinder; import android.os.RemoteException; +import android.os.UserHandle; import android.util.Log; import java.util.ArrayList; @@ -428,7 +429,7 @@ public final class BluetoothHeadsetClient implements BluetoothProfile { ComponentName comp = intent.resolveSystemService(mContext.getPackageManager(), 0); intent.setComponent(comp); if (comp == null || !mContext.bindServiceAsUser(intent, mConnection, 0, - mContext.getUser())) { + UserHandle.CURRENT_OR_SELF)) { Log.e(TAG, "Could not bind to Bluetooth Headset Client Service with " + intent); return false; } diff --git a/core/java/android/bluetooth/BluetoothHealth.java b/core/java/android/bluetooth/BluetoothHealth.java index b967fb20f023..22d41d9c896e 100644 --- a/core/java/android/bluetooth/BluetoothHealth.java +++ b/core/java/android/bluetooth/BluetoothHealth.java @@ -24,6 +24,7 @@ import android.os.Binder; import android.os.IBinder; import android.os.ParcelFileDescriptor; import android.os.RemoteException; +import android.os.UserHandle; import android.util.Log; import java.util.ArrayList; @@ -491,7 +492,7 @@ public final class BluetoothHealth implements BluetoothProfile { ComponentName comp = intent.resolveSystemService(mContext.getPackageManager(), 0); intent.setComponent(comp); if (comp == null || !mContext.bindServiceAsUser(intent, mConnection, 0, - mContext.getUser())) { + UserHandle.CURRENT_OR_SELF)) { Log.e(TAG, "Could not bind to Bluetooth Health Service with " + intent); return false; } diff --git a/core/java/android/bluetooth/BluetoothHearingAid.java b/core/java/android/bluetooth/BluetoothHearingAid.java index 606f00a8239d..47c4ee6139d6 100644 --- a/core/java/android/bluetooth/BluetoothHearingAid.java +++ b/core/java/android/bluetooth/BluetoothHearingAid.java @@ -29,6 +29,7 @@ import android.content.ServiceConnection; import android.os.Binder; import android.os.IBinder; import android.os.RemoteException; +import android.os.UserHandle; import android.util.Log; import com.android.internal.annotations.GuardedBy; @@ -205,7 +206,7 @@ public final class BluetoothHearingAid implements BluetoothProfile { ComponentName comp = intent.resolveSystemService(mContext.getPackageManager(), 0); intent.setComponent(comp); if (comp == null || !mContext.bindServiceAsUser(intent, mConnection, 0, - android.os.Process.myUserHandle())) { + UserHandle.CURRENT_OR_SELF)) { Log.e(TAG, "Could not bind to Bluetooth Hearing Aid Service with " + intent); return; } diff --git a/core/java/android/bluetooth/BluetoothHidDevice.java b/core/java/android/bluetooth/BluetoothHidDevice.java index 3bc8544ebf87..e44f36e90c75 100644 --- a/core/java/android/bluetooth/BluetoothHidDevice.java +++ b/core/java/android/bluetooth/BluetoothHidDevice.java @@ -24,6 +24,7 @@ import android.content.Intent; import android.content.ServiceConnection; import android.os.IBinder; import android.os.RemoteException; +import android.os.UserHandle; import android.util.Log; import java.util.ArrayList; @@ -454,7 +455,7 @@ public final class BluetoothHidDevice implements BluetoothProfile { ComponentName comp = intent.resolveSystemService(mContext.getPackageManager(), 0); intent.setComponent(comp); if (comp == null || !mContext.bindServiceAsUser(intent, mConnection, 0, - mContext.getUser())) { + UserHandle.CURRENT_OR_SELF)) { Log.e(TAG, "Could not bind to Bluetooth HID Device Service with " + intent); return false; } diff --git a/core/java/android/bluetooth/BluetoothHidHost.java b/core/java/android/bluetooth/BluetoothHidHost.java index 0ca39f169a72..58a25221552a 100644 --- a/core/java/android/bluetooth/BluetoothHidHost.java +++ b/core/java/android/bluetooth/BluetoothHidHost.java @@ -25,6 +25,7 @@ import android.content.ServiceConnection; import android.os.Binder; import android.os.IBinder; import android.os.RemoteException; +import android.os.UserHandle; import android.util.Log; import java.util.ArrayList; @@ -279,7 +280,7 @@ public final class BluetoothHidHost implements BluetoothProfile { ComponentName comp = intent.resolveSystemService(mContext.getPackageManager(), 0); intent.setComponent(comp); if (comp == null || !mContext.bindServiceAsUser(intent, mConnection, 0, - mContext.getUser())) { + UserHandle.CURRENT_OR_SELF)) { Log.e(TAG, "Could not bind to Bluetooth HID Service with " + intent); return false; } diff --git a/core/java/android/bluetooth/BluetoothMap.java b/core/java/android/bluetooth/BluetoothMap.java index 98c23c600f14..fc5f830a8940 100644 --- a/core/java/android/bluetooth/BluetoothMap.java +++ b/core/java/android/bluetooth/BluetoothMap.java @@ -24,6 +24,7 @@ import android.content.ServiceConnection; import android.os.Binder; import android.os.IBinder; import android.os.RemoteException; +import android.os.UserHandle; import android.util.Log; import java.util.ArrayList; @@ -110,7 +111,7 @@ public final class BluetoothMap implements BluetoothProfile { ComponentName comp = intent.resolveSystemService(mContext.getPackageManager(), 0); intent.setComponent(comp); if (comp == null || !mContext.bindServiceAsUser(intent, mConnection, 0, - mContext.getUser())) { + UserHandle.CURRENT_OR_SELF)) { Log.e(TAG, "Could not bind to Bluetooth MAP Service with " + intent); return false; } diff --git a/core/java/android/bluetooth/BluetoothMapClient.java b/core/java/android/bluetooth/BluetoothMapClient.java index 559a59b68b4e..1c82e1984b66 100644 --- a/core/java/android/bluetooth/BluetoothMapClient.java +++ b/core/java/android/bluetooth/BluetoothMapClient.java @@ -25,6 +25,7 @@ import android.content.ServiceConnection; import android.net.Uri; import android.os.IBinder; import android.os.RemoteException; +import android.os.UserHandle; import android.util.Log; import java.util.ArrayList; @@ -128,7 +129,7 @@ public final class BluetoothMapClient implements BluetoothProfile { ComponentName comp = intent.resolveSystemService(mContext.getPackageManager(), 0); intent.setComponent(comp); if (comp == null || !mContext.bindServiceAsUser(intent, mConnection, 0, - mContext.getUser())) { + UserHandle.CURRENT_OR_SELF)) { Log.e(TAG, "Could not bind to Bluetooth MAP MCE Service with " + intent); return false; } diff --git a/core/java/android/bluetooth/BluetoothPan.java b/core/java/android/bluetooth/BluetoothPan.java index 58be73296027..8923d734c844 100644 --- a/core/java/android/bluetooth/BluetoothPan.java +++ b/core/java/android/bluetooth/BluetoothPan.java @@ -26,6 +26,7 @@ import android.content.ServiceConnection; import android.os.Binder; import android.os.IBinder; import android.os.RemoteException; +import android.os.UserHandle; import android.util.Log; import java.util.ArrayList; @@ -150,7 +151,7 @@ public final class BluetoothPan implements BluetoothProfile { ComponentName comp = intent.resolveSystemService(mContext.getPackageManager(), 0); intent.setComponent(comp); if (comp == null || !mContext.bindServiceAsUser(intent, mConnection, 0, - mContext.getUser())) { + UserHandle.CURRENT_OR_SELF)) { Log.e(TAG, "Could not bind to Bluetooth Pan Service with " + intent); return false; } diff --git a/core/java/android/bluetooth/BluetoothPbap.java b/core/java/android/bluetooth/BluetoothPbap.java index ae264e19bb7c..a601df02d032 100644 --- a/core/java/android/bluetooth/BluetoothPbap.java +++ b/core/java/android/bluetooth/BluetoothPbap.java @@ -24,6 +24,7 @@ import android.content.Intent; import android.content.ServiceConnection; import android.os.IBinder; import android.os.RemoteException; +import android.os.UserHandle; import android.util.Log; import java.util.ArrayList; @@ -164,7 +165,7 @@ public class BluetoothPbap implements BluetoothProfile { ComponentName comp = intent.resolveSystemService(mContext.getPackageManager(), 0); intent.setComponent(comp); if (comp == null || !mContext.bindServiceAsUser(intent, mConnection, 0, - mContext.getUser())) { + UserHandle.CURRENT_OR_SELF)) { Log.e(TAG, "Could not bind to Bluetooth Pbap Service with " + intent); return false; } diff --git a/core/java/android/bluetooth/BluetoothPbapClient.java b/core/java/android/bluetooth/BluetoothPbapClient.java index 1446adc8b9c3..cbc96c073338 100644 --- a/core/java/android/bluetooth/BluetoothPbapClient.java +++ b/core/java/android/bluetooth/BluetoothPbapClient.java @@ -23,6 +23,7 @@ import android.content.ServiceConnection; import android.os.Binder; import android.os.IBinder; import android.os.RemoteException; +import android.os.UserHandle; import android.util.Log; import java.util.ArrayList; @@ -116,7 +117,7 @@ public final class BluetoothPbapClient implements BluetoothProfile { ComponentName comp = intent.resolveSystemService(mContext.getPackageManager(), 0); intent.setComponent(comp); if (comp == null || !mContext.bindServiceAsUser(intent, mConnection, 0, - mContext.getUser())) { + UserHandle.CURRENT_OR_SELF)) { Log.e(TAG, "Could not bind to Bluetooth PBAP Client Service with " + intent); return false; } diff --git a/core/java/android/bluetooth/BluetoothSap.java b/core/java/android/bluetooth/BluetoothSap.java index 1b732062f614..ebf6bed54475 100644 --- a/core/java/android/bluetooth/BluetoothSap.java +++ b/core/java/android/bluetooth/BluetoothSap.java @@ -24,6 +24,7 @@ import android.content.ServiceConnection; import android.os.Binder; import android.os.IBinder; import android.os.RemoteException; +import android.os.UserHandle; import android.util.Log; import java.util.ArrayList; @@ -148,7 +149,7 @@ public final class BluetoothSap implements BluetoothProfile { ComponentName comp = intent.resolveSystemService(mContext.getPackageManager(), 0); intent.setComponent(comp); if (comp == null || !mContext.bindServiceAsUser(intent, mConnection, 0, - mContext.getUser())) { + UserHandle.CURRENT_OR_SELF)) { Log.e(TAG, "Could not bind to Bluetooth SAP Service with " + intent); return false; } diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java index c19909da3007..599c2d2d3594 100644 --- a/core/java/android/content/ContentResolver.java +++ b/core/java/android/content/ContentResolver.java @@ -35,6 +35,12 @@ import android.database.ContentObserver; import android.database.CrossProcessCursorWrapper; import android.database.Cursor; import android.database.IContentObserver; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.ImageDecoder; +import android.graphics.ImageDecoder.ImageInfo; +import android.graphics.ImageDecoder.Source; +import android.graphics.PixelFormat; import android.graphics.Point; import android.graphics.drawable.Drawable; import android.net.Uri; @@ -49,9 +55,11 @@ import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemClock; import android.os.UserHandle; +import android.provider.DocumentsContract; import android.text.TextUtils; import android.util.EventLog; import android.util.Log; +import android.util.Size; import com.android.internal.util.MimeIconUtils; import com.android.internal.util.Preconditions; @@ -68,6 +76,7 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.List; +import java.util.Objects; import java.util.Random; import java.util.concurrent.atomic.AtomicBoolean; @@ -3188,4 +3197,62 @@ public abstract class ContentResolver { } return query; } + + /** + * Convenience method that efficiently loads a visual thumbnail for the + * given {@link Uri}. Internally calls + * {@link ContentProvider#openTypedAssetFile} on the remote provider, but + * also defensively resizes any returned content to match the requested + * target size. + * + * @param uri The item that should be visualized as a thumbnail. + * @param size The target area on the screen where this thumbnail will be + * shown. This is passed to the provider as {@link #EXTRA_SIZE} + * to help it avoid downloading or generating heavy resources. + * @param signal A signal to cancel the operation in progress. + * @return Valid {@link Bitmap} which is a visual thumbnail. + * @throws IOException If any trouble was encountered while generating or + * loading the thumbnail, or if + * {@link CancellationSignal#cancel()} was invoked. + */ + public @NonNull Bitmap loadThumbnail(@NonNull Uri uri, @NonNull Size size, + @Nullable CancellationSignal signal) throws IOException { + Objects.requireNonNull(uri); + Objects.requireNonNull(size); + + try (ContentProviderClient client = acquireContentProviderClient(uri)) { + return loadThumbnail(client, uri, size, signal, ImageDecoder.ALLOCATOR_DEFAULT); + } + } + + /** {@hide} */ + public static Bitmap loadThumbnail(@NonNull ContentProviderClient client, @NonNull Uri uri, + @NonNull Size size, @Nullable CancellationSignal signal, int allocator) + throws IOException { + Objects.requireNonNull(client); + Objects.requireNonNull(uri); + Objects.requireNonNull(size); + + // Convert to Point, since that's what the API is defined as + final Bundle opts = new Bundle(); + opts.putParcelable(EXTRA_SIZE, Point.convert(size)); + + return ImageDecoder.decodeBitmap(ImageDecoder.createSource(() -> { + return client.openTypedAssetFileDescriptor(uri, "image/*", opts, signal); + }), (ImageDecoder decoder, ImageInfo info, Source source) -> { + decoder.setAllocator(allocator); + + // One last-ditch check to see if we've been canceled. + if (signal != null) signal.throwIfCanceled(); + + // We requested a rough thumbnail size, but the remote size may have + // returned something giant, so defensively scale down as needed. + final int widthSample = info.getSize().getWidth() / size.getWidth(); + final int heightSample = info.getSize().getHeight() / size.getHeight(); + final int sample = Math.min(widthSample, heightSample); + if (sample > 1) { + decoder.setTargetSampleSize(sample); + } + }); + } } diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java index 1fbfa40d5d64..47145874490f 100644 --- a/core/java/android/net/ConnectivityManager.java +++ b/core/java/android/net/ConnectivityManager.java @@ -2816,10 +2816,11 @@ public class ConnectivityManager { * @param network The {@link Network} of the satisfying network. * @param networkCapabilities The {@link NetworkCapabilities} of the satisfying network. * @param linkProperties The {@link LinkProperties} of the satisfying network. + * @param blocked Whether access to the {@link Network} is blocked due to system policy. * @hide */ public void onAvailable(Network network, NetworkCapabilities networkCapabilities, - LinkProperties linkProperties) { + LinkProperties linkProperties, boolean blocked) { // Internally only this method is called when a new network is available, and // it calls the callback in the same way and order that older versions used // to call so as not to change the behavior. @@ -2830,6 +2831,7 @@ public class ConnectivityManager { } onCapabilitiesChanged(network, networkCapabilities); onLinkPropertiesChanged(network, linkProperties); + onBlockedStatusChanged(network, blocked); } /** @@ -2837,7 +2839,8 @@ public class ConnectivityManager { * This callback may be called more than once if the {@link Network} that is * satisfying the request changes. This will always immediately be followed by a * call to {@link #onCapabilitiesChanged(Network, NetworkCapabilities)} then by a - * call to {@link #onLinkPropertiesChanged(Network, LinkProperties)}. + * call to {@link #onLinkPropertiesChanged(Network, LinkProperties)}, and a call to + * {@link #onBlockedStatusChanged(Network, boolean)}. * * @param network The {@link Network} of the satisfying network. */ @@ -2916,6 +2919,14 @@ public class ConnectivityManager { */ public void onNetworkResumed(Network network) {} + /** + * Called when access to the specified network is blocked or unblocked. + * + * @param network The {@link Network} whose blocked status has changed. + * @param blocked The blocked status of this {@link Network}. + */ + public void onBlockedStatusChanged(Network network, boolean blocked) {} + private NetworkRequest networkRequest; } @@ -2962,6 +2973,8 @@ public class ConnectivityManager { public static final int CALLBACK_SUSPENDED = BASE + 9; /** @hide */ public static final int CALLBACK_RESUMED = BASE + 10; + /** @hide */ + public static final int CALLBACK_BLK_CHANGED = BASE + 11; /** @hide */ public static String getCallbackName(int whichCallback) { @@ -2976,6 +2989,7 @@ public class ConnectivityManager { case EXPIRE_LEGACY_REQUEST: return "EXPIRE_LEGACY_REQUEST"; case CALLBACK_SUSPENDED: return "CALLBACK_SUSPENDED"; case CALLBACK_RESUMED: return "CALLBACK_RESUMED"; + case CALLBACK_BLK_CHANGED: return "CALLBACK_BLK_CHANGED"; default: return Integer.toString(whichCallback); } @@ -3022,7 +3036,7 @@ public class ConnectivityManager { case CALLBACK_AVAILABLE: { NetworkCapabilities cap = getObject(message, NetworkCapabilities.class); LinkProperties lp = getObject(message, LinkProperties.class); - callback.onAvailable(network, cap, lp); + callback.onAvailable(network, cap, lp, message.arg1 != 0); break; } case CALLBACK_LOSING: { @@ -3055,6 +3069,10 @@ public class ConnectivityManager { callback.onNetworkResumed(network); break; } + case CALLBACK_BLK_CHANGED: { + boolean blocked = message.arg1 != 0; + callback.onBlockedStatusChanged(network, blocked); + } } } diff --git a/core/java/android/net/NetworkCapabilities.java b/core/java/android/net/NetworkCapabilities.java index 12b6f9e1b370..0bdfca7f5025 100644 --- a/core/java/android/net/NetworkCapabilities.java +++ b/core/java/android/net/NetworkCapabilities.java @@ -1590,4 +1590,14 @@ public final class NetworkCapabilities implements Parcelable { Preconditions.checkArgument(isValidCapability(capability), "NetworkCapability " + capability + "out of range"); } + + /** + * Check if this {@code NetworkCapability} instance is metered. + * + * @return {@code true} if {@code NET_CAPABILITY_NOT_METERED} is not set on this instance. + * @hide + */ + public boolean isMetered() { + return !hasCapability(NET_CAPABILITY_NOT_METERED); + } } diff --git a/core/java/android/net/NetworkInfo.java b/core/java/android/net/NetworkInfo.java index d912dd105fe9..1a1d2d33424c 100644 --- a/core/java/android/net/NetworkInfo.java +++ b/core/java/android/net/NetworkInfo.java @@ -202,7 +202,9 @@ public class NetworkInfo implements Parcelable { * Return a network-type-specific integer describing the subtype * of the network. * @return the network subtype + * @deprecated Use {@link android.telephony.TelephonyManager#getDataNetworkType} instead. */ + @Deprecated public int getSubtype() { synchronized (this) { return mSubtype; @@ -243,7 +245,9 @@ public class NetworkInfo implements Parcelable { /** * Return a human-readable name describing the subtype of the network. * @return the name of the network subtype + * @deprecated Use {@link android.telephony.TelephonyManager#getDataNetworkType} instead. */ + @Deprecated public String getSubtypeName() { synchronized (this) { return mSubtypeName; @@ -278,7 +282,15 @@ public class NetworkInfo implements Parcelable { * connections and pass data. * <p>Always call this before attempting to perform data transactions. * @return {@code true} if network connectivity exists, {@code false} otherwise. + * @deprecated Apps should instead use the + * {@link android.net.ConnectivityManager.NetworkCallback} API to + * learn about connectivity changes. See + * {@link ConnectivityManager#registerDefaultNetworkCallback} and + * {@link ConnectivityManager#registerNetworkCallback}. These will + * give a more accurate picture of the connectivity state of + * the device and let apps react more easily and quickly to changes. */ + @Deprecated public boolean isConnected() { synchronized (this) { return mState == State.CONNECTED; @@ -411,7 +423,15 @@ public class NetworkInfo implements Parcelable { /** * Reports the current fine-grained state of the network. * @return the fine-grained state + * @deprecated Apps should instead use the + * {@link android.net.ConnectivityManager.NetworkCallback} API to + * learn about connectivity changes. See + * {@link ConnectivityManager#registerDefaultNetworkCallback} and + * {@link ConnectivityManager#registerNetworkCallback}. These will + * give a more accurate picture of the connectivity state of + * the device and let apps react more easily and quickly to changes. */ + @Deprecated public DetailedState getDetailedState() { synchronized (this) { return mDetailedState; diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java index 412a700de65b..292543c4a19a 100644 --- a/core/java/android/os/Build.java +++ b/core/java/android/os/Build.java @@ -130,9 +130,9 @@ public class Build { * <a href="/training/articles/security-key-attestation.html">key attestation</a> to obtain * proof of the device's original identifiers. * - * <p>Requires Permission: READ_PRIVILEGED_PHONE_STATE or for the calling package to be the - * device or profile owner. Profile owner access is deprecated and will be removed in a future - * release. + * <p>Requires Permission: READ_PRIVILEGED_PHONE_STATE, or for the calling package to be the + * device or profile owner and have the READ_PHONE_STATE permission. Profile owner access is + * deprecated and will be removed in a future release. * * @return The serial number if specified. */ diff --git a/core/java/android/provider/DocumentsContract.java b/core/java/android/provider/DocumentsContract.java index 8c40e0e6cb8c..954d18abc6e1 100644 --- a/core/java/android/provider/DocumentsContract.java +++ b/core/java/android/provider/DocumentsContract.java @@ -34,6 +34,7 @@ import android.content.res.AssetFileDescriptor; import android.database.Cursor; import android.graphics.Bitmap; import android.graphics.BitmapFactory; +import android.graphics.ImageDecoder; import android.graphics.Matrix; import android.graphics.Point; import android.media.ExifInterface; @@ -53,6 +54,7 @@ import android.system.ErrnoException; import android.system.Os; import android.util.DataUnit; import android.util.Log; +import android.util.Size; import libcore.io.IoUtils; @@ -136,10 +138,11 @@ public final class DocumentsContract { public static final String EXTRA_EXCLUDE_SELF = "android.provider.extra.EXCLUDE_SELF"; /** - * Included in {@link AssetFileDescriptor#getExtras()} when returned - * thumbnail should be rotated. + * An extra number of degrees that an image should be rotated during the + * decode process to be presented correctly. * - * @see MediaStore.Images.ImageColumns#ORIENTATION + * @see AssetFileDescriptor#getExtras() + * @see android.provider.MediaStore.Images.ImageColumns#ORIENTATION */ public static final String EXTRA_ORIENTATION = "android.provider.extra.ORIENTATION"; @@ -1093,75 +1096,10 @@ public final class DocumentsContract { /** {@hide} */ @UnsupportedAppUsage - public static Bitmap getDocumentThumbnail( - ContentProviderClient client, Uri documentUri, Point size, CancellationSignal signal) - throws RemoteException, IOException { - final Bundle openOpts = new Bundle(); - openOpts.putParcelable(ContentResolver.EXTRA_SIZE, size); - - AssetFileDescriptor afd = null; - Bitmap bitmap = null; - try { - afd = client.openTypedAssetFileDescriptor(documentUri, "image/*", openOpts, signal); - - final FileDescriptor fd = afd.getFileDescriptor(); - final long offset = afd.getStartOffset(); - - // Try seeking on the returned FD, since it gives us the most - // optimal decode path; otherwise fall back to buffering. - BufferedInputStream is = null; - try { - Os.lseek(fd, offset, SEEK_SET); - } catch (ErrnoException e) { - is = new BufferedInputStream(new FileInputStream(fd), THUMBNAIL_BUFFER_SIZE); - is.mark(THUMBNAIL_BUFFER_SIZE); - } - - // We requested a rough thumbnail size, but the remote size may have - // returned something giant, so defensively scale down as needed. - final BitmapFactory.Options opts = new BitmapFactory.Options(); - opts.inJustDecodeBounds = true; - if (is != null) { - BitmapFactory.decodeStream(is, null, opts); - } else { - BitmapFactory.decodeFileDescriptor(fd, null, opts); - } - - final int widthSample = opts.outWidth / size.x; - final int heightSample = opts.outHeight / size.y; - - opts.inJustDecodeBounds = false; - opts.inSampleSize = Math.min(widthSample, heightSample); - if (is != null) { - is.reset(); - bitmap = BitmapFactory.decodeStream(is, null, opts); - } else { - try { - Os.lseek(fd, offset, SEEK_SET); - } catch (ErrnoException e) { - e.rethrowAsIOException(); - } - bitmap = BitmapFactory.decodeFileDescriptor(fd, null, opts); - } - - // Transform the bitmap if requested. We use a side-channel to - // communicate the orientation, since EXIF thumbnails don't contain - // the rotation flags of the original image. - final Bundle extras = afd.getExtras(); - final int orientation = (extras != null) ? extras.getInt(EXTRA_ORIENTATION, 0) : 0; - if (orientation != 0) { - final int width = bitmap.getWidth(); - final int height = bitmap.getHeight(); - - final Matrix m = new Matrix(); - m.setRotate(orientation, width / 2, height / 2); - bitmap = Bitmap.createBitmap(bitmap, 0, 0, width, height, m, false); - } - } finally { - IoUtils.closeQuietly(afd); - } - - return bitmap; + public static Bitmap getDocumentThumbnail(ContentProviderClient client, Uri documentUri, + Point size, CancellationSignal signal) throws IOException { + return ContentResolver.loadThumbnail(client, documentUri, Point.convert(size), signal, + ImageDecoder.ALLOCATOR_DEFAULT); } /** diff --git a/core/java/android/provider/MediaStore.java b/core/java/android/provider/MediaStore.java index 828fd7386d80..f5660b950c0a 100644 --- a/core/java/android/provider/MediaStore.java +++ b/core/java/android/provider/MediaStore.java @@ -29,7 +29,6 @@ import android.content.ContentValues; import android.content.Context; import android.content.Intent; import android.content.UriPermission; -import android.content.res.AssetFileDescriptor; import android.database.Cursor; import android.database.DatabaseUtils; import android.graphics.Bitmap; @@ -39,6 +38,7 @@ import android.net.Uri; import android.os.Bundle; import android.os.CancellationSignal; import android.os.Environment; +import android.os.OperationCanceledException; import android.os.RemoteException; import android.service.media.CameraPrewarmService; import android.util.ArrayMap; @@ -402,7 +402,16 @@ public final class MediaStore { * access. * <p> * Type: TEXT + * + * @deprecated Apps may not have filesystem permissions to directly + * access this path. Instead of trying to open this path + * directly, apps should use + * {@link ContentResolver#openFileDescriptor(Uri, String)} + * to gain access. This value will always be {@code NULL} + * for apps targeting + * {@link android.os.Build.VERSION_CODES#Q} or higher. */ + @Deprecated public static final String DATA = "_data"; /** @@ -641,6 +650,7 @@ public final class MediaStore { * This class is used internally by Images.Thumbnails and Video.Thumbnails, it's not intended * to be accessed elsewhere. */ + @Deprecated private static class InternalThumbnails implements BaseColumns { /** * Currently outstanding thumbnail requests that can be cancelled. @@ -654,13 +664,14 @@ public final class MediaStore { * * @see #cancelThumbnail(ContentResolver, Uri) */ + @Deprecated static @Nullable Bitmap getThumbnail(@NonNull ContentResolver cr, @NonNull Uri uri, int kind, @Nullable BitmapFactory.Options opts) { - final Bundle openOpts = new Bundle(); + final Point size; if (kind == ThumbnailConstants.MICRO_KIND) { - openOpts.putParcelable(ContentResolver.EXTRA_SIZE, ThumbnailConstants.MICRO_SIZE); + size = ThumbnailConstants.MICRO_SIZE; } else if (kind == ThumbnailConstants.MINI_KIND) { - openOpts.putParcelable(ContentResolver.EXTRA_SIZE, ThumbnailConstants.MINI_SIZE); + size = ThumbnailConstants.MINI_SIZE; } else { throw new IllegalArgumentException("Unsupported kind: " + kind); } @@ -674,9 +685,8 @@ public final class MediaStore { } } - try (AssetFileDescriptor afd = cr.openTypedAssetFileDescriptor(uri, - "image/*", openOpts, signal)) { - return BitmapFactory.decodeFileDescriptor(afd.getFileDescriptor(), null, opts); + try { + return cr.loadThumbnail(uri, Point.convert(size), signal); } catch (IOException e) { Log.w(TAG, "Failed to obtain thumbnail for " + uri, e); return null; @@ -693,6 +703,7 @@ public final class MediaStore { * Only the original process which made the request can cancel their own * requests. */ + @Deprecated static void cancelThumbnail(@NonNull ContentResolver cr, @NonNull Uri uri) { synchronized (sPending) { final CancellationSignal signal = sPending.get(uri); @@ -936,9 +947,8 @@ public final class MediaStore { } /** - * This class allows developers to query and get two kinds of thumbnails: - * MINI_KIND: 512 x 384 thumbnail - * MICRO_KIND: 96 x 96 thumbnail + * This class provides utility methods to obtain thumbnails for various + * {@link Images} items. */ public static class Thumbnails implements BaseColumns { public static final Cursor query(ContentResolver cr, Uri uri, String[] projection) { @@ -958,13 +968,19 @@ public final class MediaStore { } /** - * This method cancels the thumbnail request so clients waiting for getThumbnail will be - * interrupted and return immediately. Only the original process which made the getThumbnail - * requests can cancel their own requests. + * Cancel any outstanding {@link #getThumbnail} requests, causing + * them to return by throwing a {@link OperationCanceledException}. + * <p> + * This method has no effect on + * {@link ContentResolver#loadThumbnail} calls, since they provide + * their own {@link CancellationSignal}. * - * @param cr ContentResolver - * @param origId original image id + * @deprecated Callers should migrate to using + * {@link ContentResolver#loadThumbnail}, since it + * offers richer control over requested thumbnail sizes + * and cancellation behavior. */ + @Deprecated public static void cancelThumbnailRequest(ContentResolver cr, long origId) { final Uri uri = ContentUris.withAppendedId( Images.Media.EXTERNAL_CONTENT_URI, origId); @@ -972,51 +988,66 @@ public final class MediaStore { } /** - * This method checks if the thumbnails of the specified image (origId) has been created. - * It will be blocked until the thumbnails are generated. + * Return thumbnail representing a specific image item. If a + * thumbnail doesn't exist, this method will block until it's + * generated. Callers are responsible for their own in-memory + * caching of returned values. * - * @param cr ContentResolver used to dispatch queries to MediaProvider. - * @param origId Original image id associated with thumbnail of interest. - * @param kind The type of thumbnail to fetch. Should be either MINI_KIND or MICRO_KIND. - * @param options this is only used for MINI_KIND when decoding the Bitmap - * @return A Bitmap instance. It could be null if the original image - * associated with origId doesn't exist or memory is not enough. - */ - public static Bitmap getThumbnail(ContentResolver cr, long origId, int kind, + * @param imageId the image item to obtain a thumbnail for. + * @param kind optimal thumbnail size desired. + * @return decoded thumbnail, or {@code null} if problem was + * encountered. + * @deprecated Callers should migrate to using + * {@link ContentResolver#loadThumbnail}, since it + * offers richer control over requested thumbnail sizes + * and cancellation behavior. + */ + @Deprecated + public static Bitmap getThumbnail(ContentResolver cr, long imageId, int kind, BitmapFactory.Options options) { final Uri uri = ContentUris.withAppendedId( - Images.Media.EXTERNAL_CONTENT_URI, origId); + Images.Media.EXTERNAL_CONTENT_URI, imageId); return InternalThumbnails.getThumbnail(cr, uri, kind, options); } /** - * This method cancels the thumbnail request so clients waiting for getThumbnail will be - * interrupted and return immediately. Only the original process which made the getThumbnail - * requests can cancel their own requests. + * Cancel any outstanding {@link #getThumbnail} requests, causing + * them to return by throwing a {@link OperationCanceledException}. + * <p> + * This method has no effect on + * {@link ContentResolver#loadThumbnail} calls, since they provide + * their own {@link CancellationSignal}. * - * @param cr ContentResolver - * @param origId original image id - * @param groupId the same groupId used in getThumbnail. + * @deprecated Callers should migrate to using + * {@link ContentResolver#loadThumbnail}, since it + * offers richer control over requested thumbnail sizes + * and cancellation behavior. */ - public static void cancelThumbnailRequest(ContentResolver cr, long origId, long groupId) { + @Deprecated + public static void cancelThumbnailRequest(ContentResolver cr, long origId, + long groupId) { cancelThumbnailRequest(cr, origId); } /** - * This method checks if the thumbnails of the specified image (origId) has been created. - * It will be blocked until the thumbnails are generated. + * Return thumbnail representing a specific image item. If a + * thumbnail doesn't exist, this method will block until it's + * generated. Callers are responsible for their own in-memory + * caching of returned values. * - * @param cr ContentResolver used to dispatch queries to MediaProvider. - * @param origId Original image id associated with thumbnail of interest. - * @param groupId the id of group to which this request belongs - * @param kind The type of thumbnail to fetch. Should be either MINI_KIND or MICRO_KIND. - * @param options this is only used for MINI_KIND when decoding the Bitmap - * @return A Bitmap instance. It could be null if the original image - * associated with origId doesn't exist or memory is not enough. - */ - public static Bitmap getThumbnail(ContentResolver cr, long origId, long groupId, + * @param imageId the image item to obtain a thumbnail for. + * @param kind optimal thumbnail size desired. + * @return decoded thumbnail, or {@code null} if problem was + * encountered. + * @deprecated Callers should migrate to using + * {@link ContentResolver#loadThumbnail}, since it + * offers richer control over requested thumbnail sizes + * and cancellation behavior. + */ + @Deprecated + public static Bitmap getThumbnail(ContentResolver cr, long imageId, long groupId, int kind, BitmapFactory.Options options) { - return getThumbnail(cr, origId, kind, options); + return getThumbnail(cr, imageId, kind, options); } /** @@ -1059,7 +1090,16 @@ public final class MediaStore { * access. * <p> * Type: TEXT + * + * @deprecated Apps may not have filesystem permissions to directly + * access this path. Instead of trying to open this path + * directly, apps should use + * {@link ContentResolver#loadThumbnail} + * to gain access. This value will always be + * {@code NULL} for apps targeting + * {@link android.os.Build.VERSION_CODES#Q} or higher. */ + @Deprecated public static final String DATA = "_data"; /** @@ -1509,7 +1549,16 @@ public final class MediaStore { * access. * <p> * Type: TEXT + * + * @deprecated Apps may not have filesystem permissions to directly + * access this path. Instead of trying to open this path + * directly, apps should use + * {@link ContentResolver#openFileDescriptor(Uri, String)} + * to gain access. This value will always be + * {@code NULL} for apps targeting + * {@link android.os.Build.VERSION_CODES#Q} or higher. */ + @Deprecated public static final String DATA = "_data"; /** @@ -1790,7 +1839,16 @@ public final class MediaStore { /** * Cached album art. * <P>Type: TEXT</P> + * + * @deprecated Apps may not have filesystem permissions to directly + * access this path. Instead of trying to open this path + * directly, apps should use + * {@link ContentResolver#loadThumbnail} + * to gain access. This value will always be + * {@code NULL} for apps targeting + * {@link android.os.Build.VERSION_CODES#Q} or higher. */ + @Deprecated public static final String ALBUM_ART = "album_art"; } @@ -2009,20 +2067,24 @@ public final class MediaStore { } /** - * This class allows developers to query and get two kinds of thumbnails: - * MINI_KIND: 512 x 384 thumbnail - * MICRO_KIND: 96 x 96 thumbnail - * + * This class provides utility methods to obtain thumbnails for various + * {@link Video} items. */ public static class Thumbnails implements BaseColumns { /** - * This method cancels the thumbnail request so clients waiting for getThumbnail will be - * interrupted and return immediately. Only the original process which made the getThumbnail - * requests can cancel their own requests. + * Cancel any outstanding {@link #getThumbnail} requests, causing + * them to return by throwing a {@link OperationCanceledException}. + * <p> + * This method has no effect on + * {@link ContentResolver#loadThumbnail} calls, since they provide + * their own {@link CancellationSignal}. * - * @param cr ContentResolver - * @param origId original video id + * @deprecated Callers should migrate to using + * {@link ContentResolver#loadThumbnail}, since it + * offers richer control over requested thumbnail sizes + * and cancellation behavior. */ + @Deprecated public static void cancelThumbnailRequest(ContentResolver cr, long origId) { final Uri uri = ContentUris.withAppendedId( Video.Media.EXTERNAL_CONTENT_URI, origId); @@ -2030,51 +2092,66 @@ public final class MediaStore { } /** - * This method checks if the thumbnails of the specified image (origId) has been created. - * It will be blocked until the thumbnails are generated. + * Return thumbnail representing a specific video item. If a + * thumbnail doesn't exist, this method will block until it's + * generated. Callers are responsible for their own in-memory + * caching of returned values. * - * @param cr ContentResolver used to dispatch queries to MediaProvider. - * @param origId Original image id associated with thumbnail of interest. - * @param kind The type of thumbnail to fetch. Should be either MINI_KIND or MICRO_KIND. - * @param options this is only used for MINI_KIND when decoding the Bitmap - * @return A Bitmap instance. It could be null if the original image - * associated with origId doesn't exist or memory is not enough. - */ - public static Bitmap getThumbnail(ContentResolver cr, long origId, int kind, + * @param videoId the video item to obtain a thumbnail for. + * @param kind optimal thumbnail size desired. + * @return decoded thumbnail, or {@code null} if problem was + * encountered. + * @deprecated Callers should migrate to using + * {@link ContentResolver#loadThumbnail}, since it + * offers richer control over requested thumbnail sizes + * and cancellation behavior. + */ + @Deprecated + public static Bitmap getThumbnail(ContentResolver cr, long videoId, int kind, BitmapFactory.Options options) { final Uri uri = ContentUris.withAppendedId( - Video.Media.EXTERNAL_CONTENT_URI, origId); + Video.Media.EXTERNAL_CONTENT_URI, videoId); return InternalThumbnails.getThumbnail(cr, uri, kind, options); } /** - * This method checks if the thumbnails of the specified image (origId) has been created. - * It will be blocked until the thumbnails are generated. + * Cancel any outstanding {@link #getThumbnail} requests, causing + * them to return by throwing a {@link OperationCanceledException}. + * <p> + * This method has no effect on + * {@link ContentResolver#loadThumbnail} calls, since they provide + * their own {@link CancellationSignal}. * - * @param cr ContentResolver used to dispatch queries to MediaProvider. - * @param origId Original image id associated with thumbnail of interest. - * @param groupId the id of group to which this request belongs - * @param kind The type of thumbnail to fetch. Should be either MINI_KIND or MICRO_KIND - * @param options this is only used for MINI_KIND when decoding the Bitmap - * @return A Bitmap instance. It could be null if the original image associated with - * origId doesn't exist or memory is not enough. - */ - public static Bitmap getThumbnail(ContentResolver cr, long origId, long groupId, - int kind, BitmapFactory.Options options) { - return getThumbnail(cr, origId, kind, options); + * @deprecated Callers should migrate to using + * {@link ContentResolver#loadThumbnail}, since it + * offers richer control over requested thumbnail sizes + * and cancellation behavior. + */ + @Deprecated + public static void cancelThumbnailRequest(ContentResolver cr, long videoId, + long groupId) { + cancelThumbnailRequest(cr, videoId); } /** - * This method cancels the thumbnail request so clients waiting for getThumbnail will be - * interrupted and return immediately. Only the original process which made the getThumbnail - * requests can cancel their own requests. + * Return thumbnail representing a specific video item. If a + * thumbnail doesn't exist, this method will block until it's + * generated. Callers are responsible for their own in-memory + * caching of returned values. * - * @param cr ContentResolver - * @param origId original video id - * @param groupId the same groupId used in getThumbnail. + * @param videoId the video item to obtain a thumbnail for. + * @param kind optimal thumbnail size desired. + * @return decoded thumbnail, or {@code null} if problem was + * encountered. + * @deprecated Callers should migrate to using + * {@link ContentResolver#loadThumbnail}, since it + * offers richer control over requested thumbnail sizes + * and cancellation behavior. */ - public static void cancelThumbnailRequest(ContentResolver cr, long origId, long groupId) { - cancelThumbnailRequest(cr, origId); + @Deprecated + public static Bitmap getThumbnail(ContentResolver cr, long videoId, long groupId, + int kind, BitmapFactory.Options options) { + return getThumbnail(cr, videoId, kind, options); } /** @@ -2110,14 +2187,17 @@ public final class MediaStore { /** * Path to the thumbnail file on disk. * <p> - * Note that apps may not have filesystem permissions to directly - * access this path. Instead of trying to open this path directly, - * apps should use - * {@link ContentResolver#openFileDescriptor(Uri, String)} to gain - * access. - * <p> * Type: TEXT + * + * @deprecated Apps may not have filesystem permissions to directly + * access this path. Instead of trying to open this path + * directly, apps should use + * {@link ContentResolver#openFileDescriptor(Uri, String)} + * to gain access. This value will always be + * {@code NULL} for apps targeting + * {@link android.os.Build.VERSION_CODES#Q} or higher. */ + @Deprecated public static final String DATA = "_data"; /** diff --git a/core/java/android/speech/tts/TextToSpeechService.java b/core/java/android/speech/tts/TextToSpeechService.java index 10d7911316ac..ec63cd941b3f 100644 --- a/core/java/android/speech/tts/TextToSpeechService.java +++ b/core/java/android/speech/tts/TextToSpeechService.java @@ -1112,7 +1112,6 @@ public abstract class TextToSpeechService extends Service { @Override protected void playImpl() { - dispatchOnStart(); super.playImpl(); try { mFileOutputStream.close(); diff --git a/core/java/android/text/style/TextAppearanceSpan.java b/core/java/android/text/style/TextAppearanceSpan.java index 648bd1bba20c..f846a356d8fa 100644 --- a/core/java/android/text/style/TextAppearanceSpan.java +++ b/core/java/android/text/style/TextAppearanceSpan.java @@ -23,6 +23,7 @@ import android.content.res.TypedArray; import android.graphics.LeakyTypefaceStorage; import android.graphics.Typeface; import android.graphics.fonts.Font; +import android.os.LocaleList; import android.os.Parcel; import android.text.ParcelableSpan; import android.text.TextPaint; @@ -66,6 +67,7 @@ public class TextAppearanceSpan extends MetricAffectingSpan implements Parcelabl private final Typeface mTypeface; private final int mTextFontWeight; + private final LocaleList mTextLocales; private final float mShadowRadius; private final float mShadowDx; @@ -149,6 +151,19 @@ public class TextAppearanceSpan extends MetricAffectingSpan implements Parcelabl mTextFontWeight = a.getInt(com.android.internal.R.styleable .TextAppearance_textFontWeight, -1); + final String localeString = a.getString(com.android.internal.R.styleable + .TextAppearance_textLocale); + if (localeString != null) { + LocaleList localeList = LocaleList.forLanguageTags(localeString); + if (!localeList.isEmpty()) { + mTextLocales = localeList; + } else { + mTextLocales = null; + } + } else { + mTextLocales = null; + } + mShadowRadius = a.getFloat(com.android.internal.R.styleable .TextAppearance_shadowRadius, 0.0f); mShadowDx = a.getFloat(com.android.internal.R.styleable @@ -201,6 +216,7 @@ public class TextAppearanceSpan extends MetricAffectingSpan implements Parcelabl mTypeface = null; mTextFontWeight = -1; + mTextLocales = null; mShadowRadius = 0.0f; mShadowDx = 0.0f; @@ -233,6 +249,7 @@ public class TextAppearanceSpan extends MetricAffectingSpan implements Parcelabl mTypeface = LeakyTypefaceStorage.readTypefaceFromParcel(src); mTextFontWeight = src.readInt(); + mTextLocales = src.readParcelable(LocaleList.class.getClassLoader()); mShadowRadius = src.readFloat(); mShadowDx = src.readFloat(); @@ -285,6 +302,7 @@ public class TextAppearanceSpan extends MetricAffectingSpan implements Parcelabl LeakyTypefaceStorage.writeTypefaceToParcel(mTypeface, dest); dest.writeInt(mTextFontWeight); + dest.writeParcelable(mTextLocales, flags); dest.writeFloat(mShadowRadius); dest.writeFloat(mShadowDx); @@ -349,6 +367,15 @@ public class TextAppearanceSpan extends MetricAffectingSpan implements Parcelabl } /** + * Returns the {@link android.os.LocaleList} specified by this span, or <code>null</code> + * if it does not specify one. + */ + @Nullable + public LocaleList getTextLocales() { + return mTextLocales; + } + + /** * Returns the typeface specified by this span, or <code>null</code> * if it does not specify one. */ @@ -487,6 +514,10 @@ public class TextAppearanceSpan extends MetricAffectingSpan implements Parcelabl ds.setTextSize(mTextSize); } + if (mTextLocales != null) { + ds.setTextLocales(mTextLocales); + } + if (mHasElegantTextHeight) { ds.setElegantTextHeight(mElegantTextHeight); } diff --git a/core/java/android/view/textclassifier/TextClassifier.java b/core/java/android/view/textclassifier/TextClassifier.java index 9692579de6ba..2e92f14c931c 100644 --- a/core/java/android/view/textclassifier/TextClassifier.java +++ b/core/java/android/view/textclassifier/TextClassifier.java @@ -21,7 +21,6 @@ import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.StringDef; -import android.annotation.UnsupportedAppUsage; import android.annotation.WorkerThread; import android.os.LocaleList; import android.os.Looper; @@ -212,34 +211,13 @@ public interface TextClassifier { return suggestSelection(request); } - // TODO: Remove once apps can build against the latest sdk. - /** @hide */ - @UnsupportedAppUsage - default TextSelection suggestSelection( - @NonNull CharSequence text, - @IntRange(from = 0) int selectionStartIndex, - @IntRange(from = 0) int selectionEndIndex, - @Nullable TextSelection.Options options) { - if (options == null) { - return suggestSelection(new TextSelection.Request.Builder( - text, selectionStartIndex, selectionEndIndex).build()); - } else if (options.getRequest() != null) { - return suggestSelection(options.getRequest()); - } else { - return suggestSelection( - new TextSelection.Request.Builder(text, selectionStartIndex, selectionEndIndex) - .setDefaultLocales(options.getDefaultLocales()) - .build()); - } - } - /** * Classifies the specified text and returns a {@link TextClassification} object that can be * used to generate a widget for handling the classified text. * * <p><strong>NOTE: </strong>Call on a worker thread. * - * <strong>NOTE: </strong>If a TextClassifier has been destroyed, calls to this method should + * <p><strong>NOTE: </strong>If a TextClassifier has been destroyed, calls to this method should * throw an {@link IllegalStateException}. See {@link #isDestroyed()}. * * @param request the text classification request @@ -262,7 +240,7 @@ public interface TextClassifier { * {@link #classifyText(TextClassification.Request)}. If that method calls this method, * a stack overflow error will happen. * - * <strong>NOTE: </strong>If a TextClassifier has been destroyed, calls to this method should + * <p><strong>NOTE: </strong>If a TextClassifier has been destroyed, calls to this method should * throw an {@link IllegalStateException}. See {@link #isDestroyed()}. * * @param text text providing context for the text to classify (which is specified @@ -292,34 +270,13 @@ public interface TextClassifier { return classifyText(request); } - // TODO: Remove once apps can build against the latest sdk. - /** @hide */ - @UnsupportedAppUsage - default TextClassification classifyText( - @NonNull CharSequence text, - @IntRange(from = 0) int startIndex, - @IntRange(from = 0) int endIndex, - @Nullable TextClassification.Options options) { - if (options == null) { - return classifyText( - new TextClassification.Request.Builder(text, startIndex, endIndex).build()); - } else if (options.getRequest() != null) { - return classifyText(options.getRequest()); - } else { - return classifyText(new TextClassification.Request.Builder(text, startIndex, endIndex) - .setDefaultLocales(options.getDefaultLocales()) - .setReferenceTime(options.getReferenceTime()) - .build()); - } - } - /** * Generates and returns a {@link TextLinks} that may be applied to the text to annotate it with * links information. * * <p><strong>NOTE: </strong>Call on a worker thread. * - * <strong>NOTE: </strong>If a TextClassifier has been destroyed, calls to this method should + * <p><strong>NOTE: </strong>If a TextClassifier has been destroyed, calls to this method should * throw an {@link IllegalStateException}. See {@link #isDestroyed()}. * * @param request the text links request @@ -334,27 +291,10 @@ public interface TextClassifier { return new TextLinks.Builder(request.getText().toString()).build(); } - // TODO: Remove once apps can build against the latest sdk. - /** @hide */ - @UnsupportedAppUsage - default TextLinks generateLinks( - @NonNull CharSequence text, @Nullable TextLinks.Options options) { - if (options == null) { - return generateLinks(new TextLinks.Request.Builder(text).build()); - } else if (options.getRequest() != null) { - return generateLinks(options.getRequest()); - } else { - return generateLinks(new TextLinks.Request.Builder(text) - .setDefaultLocales(options.getDefaultLocales()) - .setEntityConfig(options.getEntityConfig()) - .build()); - } - } - /** * Returns the maximal length of text that can be processed by generateLinks. * - * <strong>NOTE: </strong>If a TextClassifier has been destroyed, calls to this method should + * <p><strong>NOTE: </strong>If a TextClassifier has been destroyed, calls to this method should * throw an {@link IllegalStateException}. See {@link #isDestroyed()}. * * @see #generateLinks(TextLinks.Request) @@ -365,9 +305,29 @@ public interface TextClassifier { } /** + * Detects the language of the specified text. + * + * <p><strong>NOTE: </strong>Call on a worker thread. + * + * + * <p><strong>NOTE: </strong>If a TextClassifier has been destroyed, calls to this method should + * throw an {@link IllegalStateException}. See {@link #isDestroyed()}. + * + * @param request the {@link TextLanguage} request. + * @return the {@link TextLanguage} result. + */ + @WorkerThread + @NonNull + default TextLanguage detectLanguage(@NonNull TextLanguage.Request request) { + Preconditions.checkNotNull(request); + Utils.checkMainThread(); + return TextLanguage.EMPTY; + } + + /** * Reports a selection event. * - * <strong>NOTE: </strong>If a TextClassifier has been destroyed, calls to this method should + * <p><strong>NOTE: </strong>If a TextClassifier has been destroyed, calls to this method should * throw an {@link IllegalStateException}. See {@link #isDestroyed()}. */ default void onSelectionEvent(@NonNull SelectionEvent event) {} @@ -375,7 +335,7 @@ public interface TextClassifier { /** * Destroys this TextClassifier. * - * <strong>NOTE: </strong>If a TextClassifier has been destroyed, calls to its methods should + * <p><strong>NOTE: </strong>If a TextClassifier has been destroyed, calls to its methods should * throw an {@link IllegalStateException}. See {@link #isDestroyed()}. * * <p>Subsequent calls to this method are no-ops. @@ -385,7 +345,7 @@ public interface TextClassifier { /** * Returns whether or not this TextClassifier has been destroyed. * - * <strong>NOTE: </strong>If a TextClassifier has been destroyed, caller should not interact + * <p><strong>NOTE: </strong>If a TextClassifier has been destroyed, caller should not interact * with the classifier and an attempt to do so would throw an {@link IllegalStateException}. * However, this method should never throw an {@link IllegalStateException}. * @@ -396,9 +356,7 @@ public interface TextClassifier { } /** @hide **/ - default void dump(@NonNull IndentingPrintWriter printWriter) { - - } + default void dump(@NonNull IndentingPrintWriter printWriter) {} /** * Configuration object for specifying what entities to identify. diff --git a/core/java/android/view/textclassifier/TextLanguage.java b/core/java/android/view/textclassifier/TextLanguage.java new file mode 100644 index 000000000000..d28459e733f0 --- /dev/null +++ b/core/java/android/view/textclassifier/TextLanguage.java @@ -0,0 +1,307 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view.textclassifier; + +import android.annotation.FloatRange; +import android.annotation.IntRange; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.icu.util.ULocale; +import android.os.Bundle; +import android.os.Parcel; +import android.os.Parcelable; +import android.util.ArrayMap; + +import com.android.internal.util.Preconditions; + +import java.util.Locale; +import java.util.Map; + +/** + * Represents the result of language detection of a piece of text. + * <p> + * This contains a list of locales, each paired with a confidence score, sorted in decreasing + * order of those scores. E.g., for a given input text, the model may return + * {@code [<"en", 0.85>, <"fr", 0.15>]}. This sample result means the model reports that it is + * 85% likely that the entire text is in English and 15% likely that the entire text is in French, + * etc. It does not mean that 85% of the input is in English and 15% is in French. + */ +public final class TextLanguage implements Parcelable { + + public static final Creator<TextLanguage> CREATOR = new Creator<TextLanguage>() { + @Override + public TextLanguage createFromParcel(Parcel in) { + return readFromParcel(in); + } + + @Override + public TextLanguage[] newArray(int size) { + return new TextLanguage[size]; + } + }; + + static final TextLanguage EMPTY = new Builder().build(); + + @Nullable private final String mId; + private final EntityConfidence mEntityConfidence; + private final Bundle mBundle; + + private TextLanguage( + @Nullable String id, + EntityConfidence entityConfidence, + Bundle bundle) { + mId = id; + mEntityConfidence = entityConfidence; + mBundle = bundle; + } + + /** + * Returns the id, if one exists, for this object. + */ + @Nullable + public String getId() { + return mId; + } + + /** + * Returns the number of possible locales for the processed text. + */ + @IntRange(from = 0) + public int getLocaleHypothesisCount() { + return mEntityConfidence.getEntities().size(); + } + + /** + * Returns the language locale at the specified index. Locales are ordered from high + * confidence to low confidence. + * + * @throws IndexOutOfBoundsException if the specified index is out of range. + * @see #getLocaleCount() for the number of locales available. + */ + @NonNull + public ULocale getLocale(int index) { + return ULocale.forLanguageTag(mEntityConfidence.getEntities().get(index)); + } + + /** + * Returns the confidence score for the specified language locale. The value ranges from + * 0 (low confidence) to 1 (high confidence). 0 indicates that the locale was not found for + * the processed text. + */ + @FloatRange(from = 0.0, to = 1.0) + public float getConfidenceScore(@NonNull ULocale locale) { + return mEntityConfidence.getConfidenceScore(locale.toLanguageTag()); + } + + /** + * Returns a bundle containing non-structured extra information about this result. + * + * <p><b>NOTE: </b>Each call to this method returns a new bundle copy so clients should prefer + * to hold a reference to the returned bundle rather than frequently calling this method. + */ + @NonNull + public Bundle getExtras() { + return mBundle.deepCopy(); + } + + @Override + public String toString() { + return String.format( + Locale.US, + "TextLanguage {id=%s, locales=%s, bundle=%s}", + mId, mEntityConfidence, mBundle); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(mId); + mEntityConfidence.writeToParcel(dest, flags); + dest.writeBundle(mBundle); + } + + private static TextLanguage readFromParcel(Parcel in) { + return new TextLanguage( + in.readString(), + EntityConfidence.CREATOR.createFromParcel(in), + in.readBundle()); + } + + /** + * Builder used to build TextLanguage objects. + */ + public static final class Builder { + + @Nullable private String mId; + private final Map<String, Float> mEntityConfidenceMap = new ArrayMap<>(); + @Nullable private Bundle mBundle; + + /** + * Sets a language locale for the processed text and assigns a confidence score. If the + * locale has already been set, this updates it. + * + * @param confidenceScore a value from 0 (low confidence) to 1 (high confidence). + * 0 implies the locale does not exist for the processed text. + * Values greater than 1 are clamped to 1. + */ + @NonNull + public Builder putLocale( + @NonNull ULocale locale, + @FloatRange(from = 0.0, to = 1.0) float confidenceScore) { + Preconditions.checkNotNull(locale); + mEntityConfidenceMap.put(locale.toLanguageTag(), confidenceScore); + return this; + } + + /** + * Sets an optional id for the TextLanguage object. + */ + @NonNull + public Builder setId(@Nullable String id) { + mId = id; + return this; + } + + /** + * Sets a bundle containing non-structured extra information about the TextLanguage object. + */ + @NonNull + public Builder setExtras(@NonNull Bundle bundle) { + mBundle = Preconditions.checkNotNull(bundle); + return this; + } + + /** + * Builds and returns a new TextLanguage object. + * <p> + * If necessary, this method will verify fields, clamp them, and make them immutable. + */ + @NonNull + public TextLanguage build() { + mBundle = mBundle == null ? new Bundle() : mBundle.deepCopy(); + return new TextLanguage( + mId, + new EntityConfidence(mEntityConfidenceMap), + mBundle); + } + } + + /** + * A request object for detecting the language of a piece of text. + */ + public static final class Request implements Parcelable { + + public static final Creator<Request> CREATOR = new Creator<Request>() { + @Override + public Request createFromParcel(Parcel in) { + return readFromParcel(in); + } + + @Override + public Request[] newArray(int size) { + return new Request[size]; + } + }; + + private final CharSequence mText; + private final Bundle mBundle; + + private Request(CharSequence text, Bundle bundle) { + mText = text; + mBundle = bundle; + } + + /** + * Returns the text to process. + */ + @NonNull + public CharSequence getText() { + return mText; + } + + /** + * Returns a bundle containing non-structured extra information about this request. + * + * <p><b>NOTE: </b>Each call to this method returns a new bundle copy so clients should + * prefer to hold a reference to the returned bundle rather than frequently calling this + * method. + */ + @NonNull + public Bundle getExtras() { + return mBundle.deepCopy(); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeCharSequence(mText); + dest.writeBundle(mBundle); + } + + private static Request readFromParcel(Parcel in) { + return new Request( + in.readCharSequence(), + in.readBundle()); + } + + /** + * A builder for building TextLanguage requests. + */ + public static final class Builder { + + private final CharSequence mText; + @Nullable private Bundle mBundle; + + /** + * Creates a builder to build TextLanguage requests. + * + * @param text the text to process. + */ + public Builder(@NonNull CharSequence text) { + mText = Preconditions.checkNotNull(text); + } + + /** + * Sets a bundle containing non-structured extra information about the request. + */ + @NonNull + public Builder setExtras(@NonNull Bundle bundle) { + mBundle = Preconditions.checkNotNull(bundle); + return this; + } + + /** + * Builds and returns a new TextLanguage request object. + * <p> + * If necessary, this method will verify fields, clamp them, and make them immutable. + */ + @NonNull + public Request build() { + mBundle = mBundle == null ? new Bundle() : mBundle.deepCopy(); + return new Request(mText.toString(), mBundle); + } + } + } +} diff --git a/core/java/android/webkit/WebViewClient.java b/core/java/android/webkit/WebViewClient.java index 69d7202c4da8..300bb6fd4890 100644 --- a/core/java/android/webkit/WebViewClient.java +++ b/core/java/android/webkit/WebViewClient.java @@ -299,7 +299,7 @@ public class WebViewClient { /** * The resource was blocked because it may trick the user into a billing agreement. * - * <p>This constant is only used when targetSdkVersion is greater than {@link + * <p>This constant is only used when targetSdkVersion is at least {@link * android.os.Build.VERSION_CODES#Q}. Otherwise, {@link #SAFE_BROWSING_THREAT_UNKNOWN} is used * instead. */ diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index f74c2341d816..3dd6fd1410bd 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -3517,6 +3517,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener ColorStateList mTextColorHint = null; ColorStateList mTextColorLink = null; int mTextSize = -1; + LocaleList mTextLocales = null; String mFontFamily = null; Typeface mFontTypeface = null; boolean mFontFamilyExplicit = false; @@ -3543,6 +3544,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener + " mTextColorHint:" + mTextColorHint + "\n" + " mTextColorLink:" + mTextColorLink + "\n" + " mTextSize:" + mTextSize + "\n" + + " mTextLocales:" + mTextLocales + "\n" + " mFontFamily:" + mFontFamily + "\n" + " mFontTypeface:" + mFontTypeface + "\n" + " mFontFamilyExplicit:" + mFontFamilyExplicit + "\n" @@ -3579,6 +3581,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener com.android.internal.R.styleable.TextAppearance_textColorLink); sAppearanceValues.put(com.android.internal.R.styleable.TextView_textSize, com.android.internal.R.styleable.TextAppearance_textSize); + sAppearanceValues.put(com.android.internal.R.styleable.TextView_textLocale, + com.android.internal.R.styleable.TextAppearance_textLocale); sAppearanceValues.put(com.android.internal.R.styleable.TextView_typeface, com.android.internal.R.styleable.TextAppearance_typeface); sAppearanceValues.put(com.android.internal.R.styleable.TextView_fontFamily, @@ -3652,6 +3656,15 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener attributes.mTextSize = appearance.getDimensionPixelSize(attr, attributes.mTextSize); break; + case com.android.internal.R.styleable.TextAppearance_textLocale: + final String localeString = appearance.getString(attr); + if (localeString != null) { + final LocaleList localeList = LocaleList.forLanguageTags(localeString); + if (!localeList.isEmpty()) { + attributes.mTextLocales = localeList; + } + } + break; case com.android.internal.R.styleable.TextAppearance_typeface: attributes.mTypefaceIndex = appearance.getInt(attr, attributes.mTypefaceIndex); if (attributes.mTypefaceIndex != -1 && !attributes.mFontFamilyExplicit) { @@ -3738,6 +3751,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener setRawTextSize(attributes.mTextSize, true /* shouldRequestLayout */); } + if (attributes.mTextLocales != null) { + setTextLocales(attributes.mTextLocales); + } + if (attributes.mTypefaceIndex != -1 && !attributes.mFontFamilyExplicit) { attributes.mFontFamily = null; } diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index cdb65edd81e7..9bedab53bb2c 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -4551,6 +4551,11 @@ <attr name="typeface" /> <!-- Font family (named by string or as a font resource reference) for the text. --> <attr name="fontFamily" /> + <!-- Specifies the {@link android.os.LocaleList} for the text. + May be a string value, which is a comma-separated language tag list, such as "ja-JP,zh-CN". + When not specified or an empty string is given, it will fallback to the default one. + {@see android.os.LocaleList#forLanguageTags(String)} --> + <attr name="textLocale" format="string" /> <!-- Color of the text selection highlight. --> <attr name="textColorHighlight" /> <!-- Color of the hint text. --> @@ -4642,6 +4647,13 @@ <attr name="textFontWeight" /> <!-- Font family (named by string or as a font resource reference) for the text. --> <attr name="fontFamily" /> + <!-- Specifies the {@link android.os.LocaleList} for the text in this TextView. + If not given, the system default will be used. + May be a string value, which is a comma-separated language tag list, such as "ja-JP,zh-CN". + When not specified or an empty string is given, it will fallback to the default one. + {@see android.os.LocaleList#forLanguageTags(String)} + {@see android.text.TextView#setTextLocales(android.os.LocaleList)} --> + <attr name="textLocale" format="string" /> <!-- Text color for links. --> <attr name="textColorLink" /> <!-- Makes the cursor visible (the default) or invisible. --> diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index 95517186fe9e..31212a6ab28f 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -2915,6 +2915,7 @@ <public name="minimumUiTimeout" /> <public name="isLightTheme" /> <public name="isSplitRequired" /> + <public name="textLocale" /> </public-group> <public-group type="drawable" first-id="0x010800b4"> diff --git a/core/tests/coretests/src/android/content/ContentResolverTest.java b/core/tests/coretests/src/android/content/ContentResolverTest.java index 6256d08adb00..0036186994fe 100644 --- a/core/tests/coretests/src/android/content/ContentResolverTest.java +++ b/core/tests/coretests/src/android/content/ContentResolverTest.java @@ -13,31 +13,149 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package android.content; -import android.content.ContentResolver; -import android.provider.ContactsContract; -import android.test.AndroidTestCase; -import android.test.suitebuilder.annotation.LargeTest; -import android.test.suitebuilder.annotation.Suppress; - -@Suppress // Failing. -public class ContentResolverTest extends AndroidTestCase { - private ContentResolver mContentResolver; - - @Override - protected void setUp() throws Exception { - super.setUp(); - mContentResolver = mContext.getContentResolver(); - } - - @LargeTest - public void testCursorFinalizer() throws Exception { - // TODO: Want a test case that more predictably reproduce this issue. Selected - // 600 as this causes the problem 100% of the runs on current hw, it might not - // do so on some other configuration though. - for (int i = 0; i < 600; i++) { - mContentResolver.query(ContactsContract.Contacts.CONTENT_URI, null, null, null, null); - } +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import android.content.res.AssetFileDescriptor; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.ImageDecoder; +import android.graphics.Paint; +import android.net.Uri; +import android.os.MemoryFile; +import android.os.ParcelFileDescriptor; +import android.support.test.InstrumentationRegistry; +import android.support.test.runner.AndroidJUnit4; +import android.util.Size; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +public class ContentResolverTest { + + private ContentResolver mResolver; + private IContentProvider mProvider; + private ContentProviderClient mClient; + + private int mSize = 256_000; + private MemoryFile mImage; + + @Before + public void setUp() throws Exception { + mResolver = InstrumentationRegistry.getInstrumentation().getTargetContext() + .getContentResolver(); + mProvider = mock(IContentProvider.class); + mClient = new ContentProviderClient(mResolver, mProvider, false); + + mImage = new MemoryFile("temp.png", mSize); + } + + @After + public void tearDown() throws Exception { + mImage.close(); + mImage = null; + } + + private void initImage(int width, int height) throws Exception { + final Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); + final Canvas canvas = new Canvas(bitmap); + + canvas.drawColor(Color.RED); + + final Paint paint = new Paint(); + paint.setColor(Color.BLUE); + paint.setStyle(Paint.Style.FILL); + canvas.drawRect(0, 0, width / 2, height / 2, paint); + + bitmap.compress(Bitmap.CompressFormat.PNG, 90, mImage.getOutputStream()); + + final AssetFileDescriptor afd = new AssetFileDescriptor( + new ParcelFileDescriptor(mImage.getFileDescriptor()), 0, mSize, null); + when(mProvider.openTypedAssetFile(any(), any(), any(), any(), any())).thenReturn(afd); + } + + private static void assertImageAspectAndContents(Bitmap bitmap) { + // And correct aspect ratio + final int before = (100 * 1280) / 960; + final int after = (100 * bitmap.getWidth()) / bitmap.getHeight(); + assertEquals(before, after); + + // And scaled correctly + final int halfX = bitmap.getWidth() / 2; + final int halfY = bitmap.getHeight() / 2; + assertEquals(Color.BLUE, bitmap.getPixel(halfX - 10, halfY - 10)); + assertEquals(Color.RED, bitmap.getPixel(halfX + 10, halfY - 10)); + assertEquals(Color.RED, bitmap.getPixel(halfX - 10, halfY + 10)); + assertEquals(Color.RED, bitmap.getPixel(halfX + 10, halfY + 10)); + } + + @Test + public void testLoadThumbnail_Normal() throws Exception { + initImage(1280, 960); + + Bitmap res = ContentResolver.loadThumbnail(mClient, + Uri.parse("content://com.example/"), new Size(1280, 960), null, + ImageDecoder.ALLOCATOR_SOFTWARE); + + // Size should be untouched + assertEquals(1280, res.getWidth()); + assertEquals(960, res.getHeight()); + + assertImageAspectAndContents(res); + } + + @Test + public void testLoadThumbnail_Scaling() throws Exception { + initImage(1280, 960); + + Bitmap res = ContentResolver.loadThumbnail(mClient, + Uri.parse("content://com.example/"), new Size(320, 240), null, + ImageDecoder.ALLOCATOR_SOFTWARE); + + // Size should be much smaller + assertTrue(res.getWidth() <= 640); + assertTrue(res.getHeight() <= 480); + + assertImageAspectAndContents(res); + } + + @Test + public void testLoadThumbnail_Aspect() throws Exception { + initImage(1280, 960); + + Bitmap res = ContentResolver.loadThumbnail(mClient, + Uri.parse("content://com.example/"), new Size(240, 320), null, + ImageDecoder.ALLOCATOR_SOFTWARE); + + // Size should be much smaller + assertTrue(res.getWidth() <= 640); + assertTrue(res.getHeight() <= 480); + + assertImageAspectAndContents(res); + } + + @Test + public void testLoadThumbnail_Tiny() throws Exception { + initImage(32, 24); + + Bitmap res = ContentResolver.loadThumbnail(mClient, + Uri.parse("content://com.example/"), new Size(320, 240), null, + ImageDecoder.ALLOCATOR_SOFTWARE); + + // Size should be untouched + assertEquals(32, res.getWidth()); + assertEquals(24, res.getHeight()); + + assertImageAspectAndContents(res); } } diff --git a/core/tests/coretests/src/android/view/textclassifier/TextLanguageTest.java b/core/tests/coretests/src/android/view/textclassifier/TextLanguageTest.java new file mode 100644 index 000000000000..75ca769294ce --- /dev/null +++ b/core/tests/coretests/src/android/view/textclassifier/TextLanguageTest.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view.textclassifier; + +import static org.junit.Assert.assertEquals; + +import android.icu.util.ULocale; +import android.os.Bundle; +import android.os.Parcel; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Tests for TextLanguage. + */ +@SmallTest +@RunWith(AndroidJUnit4.class) +public final class TextLanguageTest { + + private static final float EPSILON = 0.000001f; + + @Test + public void testParcel() throws Exception { + final String bundleKey = "experiment.int"; + final Bundle bundle = new Bundle(); + bundle.putInt(bundleKey, 1234); + + final TextLanguage reference = new TextLanguage.Builder() + .setId("id") + .setExtras(bundle) + .putLocale(ULocale.ENGLISH, 0.8f) + .putLocale(ULocale.GERMAN, 0.2f) + .build(); + + final Parcel parcel = Parcel.obtain(); + reference.writeToParcel(parcel, 0); + parcel.setDataPosition(0); + final TextLanguage result = TextLanguage.CREATOR.createFromParcel(parcel); + + assertEquals("id", result.getId()); + assertEquals(1234, result.getExtras().getInt(bundleKey)); + assertEquals(2, result.getLocaleHypothesisCount()); + assertEquals(ULocale.ENGLISH, result.getLocale(0)); + assertEquals(0.8f, result.getConfidenceScore(ULocale.ENGLISH), EPSILON); + assertEquals(ULocale.GERMAN, result.getLocale(1)); + assertEquals(0.2f, result.getConfidenceScore(ULocale.GERMAN), EPSILON); + } + + @Test + public void testRequestParcel() throws Exception { + final String text = "This is random text"; + final String bundleKey = "experiment.str"; + final Bundle bundle = new Bundle(); + bundle.putString(bundleKey, "bundle"); + + final TextLanguage.Request reference = new TextLanguage.Request.Builder(text) + .setExtras(bundle) + .build(); + + final Parcel parcel = Parcel.obtain(); + reference.writeToParcel(parcel, 0); + parcel.setDataPosition(0); + final TextLanguage.Request result = TextLanguage.Request.CREATOR.createFromParcel(parcel); + + assertEquals(text, result.getText()); + assertEquals("bundle", result.getExtras().getString(bundleKey)); + } +} diff --git a/graphics/java/android/graphics/Point.java b/graphics/java/android/graphics/Point.java index f291e2702948..ea9350174f97 100644 --- a/graphics/java/android/graphics/Point.java +++ b/graphics/java/android/graphics/Point.java @@ -19,6 +19,7 @@ package android.graphics; import android.annotation.NonNull; import android.os.Parcel; import android.os.Parcelable; +import android.util.Size; import android.util.proto.ProtoOutputStream; import java.io.PrintWriter; @@ -168,4 +169,14 @@ public class Point implements Parcelable { x = in.readInt(); y = in.readInt(); } + + /** {@hide} */ + public static @NonNull Point convert(@NonNull Size size) { + return new Point(size.getWidth(), size.getHeight()); + } + + /** {@hide} */ + public static @NonNull Size convert(@NonNull Point point) { + return new Size(point.x, point.y); + } } diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CarrierActionUtils.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CarrierActionUtils.java index 33cb596415d5..4518d79ff611 100644 --- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CarrierActionUtils.java +++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CarrierActionUtils.java @@ -30,7 +30,7 @@ import android.telephony.TelephonyManager; import android.text.TextUtils; import android.util.Log; import com.android.internal.telephony.PhoneConstants; -import com.android.carrierdefaultapp.R; + /** * This util class provides common logic for carrier actions */ @@ -102,7 +102,7 @@ public class CarrierActionUtils { SubscriptionManager.getDefaultVoiceSubscriptionId()); logd("onDisableAllMeteredApns subId: " + subId); final TelephonyManager telephonyMgr = context.getSystemService(TelephonyManager.class); - telephonyMgr.carrierActionSetMeteredApnsEnabled(subId, !ENABLE); + telephonyMgr.createForSubscriptionId(subId).setCarrierDataEnabled(!ENABLE); } private static void onEnableAllMeteredApns(Intent intent, Context context) { @@ -110,7 +110,7 @@ public class CarrierActionUtils { SubscriptionManager.getDefaultVoiceSubscriptionId()); logd("onEnableAllMeteredApns subId: " + subId); final TelephonyManager telephonyMgr = context.getSystemService(TelephonyManager.class); - telephonyMgr.carrierActionSetMeteredApnsEnabled(subId, ENABLE); + telephonyMgr.createForSubscriptionId(subId).setCarrierDataEnabled(ENABLE); } private static void onEnableDefaultURLHandler(Context context) { diff --git a/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/CarrierDefaultReceiverTest.java b/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/CarrierDefaultReceiverTest.java index f9dbcd45b19b..5d84d6450574 100644 --- a/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/CarrierDefaultReceiverTest.java +++ b/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/CarrierDefaultReceiverTest.java @@ -104,6 +104,6 @@ public class CarrierDefaultReceiverTest extends InstrumentationTestCase { assertNotNull(pendingIntent); Rlog.d(TAG, "verify carrier action: disable all metered apns"); - verify(mTelephonyMgr).carrierActionSetMeteredApnsEnabled(eq(subId), eq(false)); + verify(mTelephonyMgr).setCarrierDataEnabled(eq(false)); } } diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index 2fbf42fc0f39..637830906124 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -137,15 +137,9 @@ <integer name="quick_settings_brightness_dialog_short_timeout">2000</integer> <integer name="quick_settings_brightness_dialog_long_timeout">4000</integer> - <!-- Should "4G" be shown instead of "LTE" when the network is NETWORK_TYPE_LTE? --> - <bool name="config_show4GForLTE">true</bool> - <!-- Show indicator for Wifi on but not connected. --> <bool name="config_showWifiIndicatorWhenEnabled">false</bool> - <!-- Should "LTE"/"4G" be shown instead of "LTE+"/"4G+" when on NETWORK_TYPE_LTE_CA? --> - <bool name="config_hideLtePlus">false</bool> - <!-- The number of milliseconds before the heads up notification auto-dismisses. --> <integer name="heads_up_notification_decay">5000</integer> diff --git a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java index 0215fda81485..3fe99445f49d 100644 --- a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java +++ b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java @@ -87,6 +87,8 @@ public class SwipeHelper implements Gefingerpoken { private Runnable mWatchLongPress; private final long mLongPressTimeout; + protected boolean mSwipingInProgress; + final private int[] mTmpPos = new int[2]; private final int mFalsingThreshold; private boolean mTouchAboveFalsingThreshold; @@ -127,6 +129,10 @@ public class SwipeHelper implements Gefingerpoken { mDisableHwLayers = disableHwLayers; } + public boolean isSwipingInProgress() { + return mSwipingInProgress; + } + private float getPos(MotionEvent ev) { return mSwipeDirection == X ? ev.getX() : ev.getY(); } @@ -318,6 +324,7 @@ public class SwipeHelper implements Gefingerpoken { if (Math.abs(delta) > mPagingTouchSlop && Math.abs(delta) > Math.abs(deltaPerpendicular)) { if (mCallback.canChildBeDragged(mCurrView)) { + mSwipingInProgress = true; mCallback.onBeginDrag(mCurrView); mDragging = true; mInitialTouchPos = getPos(ev); @@ -437,6 +444,7 @@ public class SwipeHelper implements Gefingerpoken { wasRemoved = row.isRemoved(); } if (!mCancelled || wasRemoved) { + mSwipingInProgress = false; mCallback.onChildDismissed(animView); } if (endAction != null) { @@ -626,6 +634,7 @@ public class SwipeHelper implements Gefingerpoken { !swipedFastEnough() /* useAccelerateInterpolator */); } else { // snappity + mSwipingInProgress = false; mCallback.onDragCancelled(mCurrView); snapChild(mCurrView, 0 /* leftTarget */, velocity); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/VisibilityLocationProvider.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/VisibilityLocationProvider.java index 81208c4330c5..53ebe747c2e9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/VisibilityLocationProvider.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/VisibilityLocationProvider.java @@ -24,7 +24,10 @@ import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow public interface VisibilityLocationProvider { /** - * @return whether the view is in a visible location right now. + * Returns whether an ExpandableNotificationRow is in a visible location or not. + * + * @param row + * @return true if row is in a visible location */ boolean isInVisibleLocation(ExpandableNotificationRow row); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationListContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationListContainer.java index fa75c7131e09..cfb6d990a9a7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationListContainer.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationListContainer.java @@ -22,6 +22,7 @@ import android.view.View; import android.view.ViewGroup; import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper; +import com.android.systemui.statusbar.notification.VisibilityLocationProvider; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.ExpandableView; import com.android.systemui.statusbar.notification.NotificationData; @@ -31,7 +32,8 @@ import com.android.systemui.statusbar.notification.logging.NotificationLogger; * Interface representing the entity that contains notifications. It can have * notification views added and removed from it, and will manage displaying them to the user. */ -public interface NotificationListContainer { +public interface NotificationListContainer extends ExpandableView.OnHeightChangedListener, + VisibilityLocationProvider { /** * Called when a child is being transferred. @@ -128,14 +130,6 @@ public interface NotificationListContainer { ViewGroup getViewParentForNotification(NotificationData.Entry entry); /** - * Called when the height of an expandable view changes. - * - * @param view view whose height changed - * @param animate whether this change should be animated - */ - void onHeightChanged(ExpandableView view, boolean animate); - - /** * Resets the currently exposed menu view. * * @param animate whether to animate the closing/change of menu view @@ -158,13 +152,6 @@ public interface NotificationListContainer { */ void cleanUpViewState(View view); - /** - * Returns whether an ExpandableNotificationRow is in a visible location or not. - * - * @param row - * @return true if row is in a visible location - */ - boolean isInVisibleLocation(ExpandableNotificationRow row); /** * Sets a listener to listen for changes in notification locations. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java index bac42ffceb92..0bc54a33347c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java @@ -25,6 +25,8 @@ import android.animation.ObjectAnimator; import android.animation.PropertyValuesHolder; import android.animation.TimeAnimator; import android.animation.ValueAnimator; +import android.animation.ValueAnimator.AnimatorUpdateListener; +import android.annotation.NonNull; import android.annotation.Nullable; import android.app.WallpaperManager; import android.content.Context; @@ -43,10 +45,6 @@ import android.os.ServiceManager; import android.provider.Settings; import android.service.notification.StatusBarNotification; -import androidx.annotation.NonNull; -import androidx.annotation.VisibleForTesting; -import androidx.core.graphics.ColorUtils; - import android.util.AttributeSet; import android.util.DisplayMetrics; import android.util.Log; @@ -69,6 +67,8 @@ import android.view.animation.Interpolator; import android.widget.OverScroller; import android.widget.ScrollView; +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.graphics.ColorUtils; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.internal.statusbar.IStatusBarService; @@ -84,6 +84,7 @@ import com.android.systemui.classifier.FalsingManager; import com.android.systemui.colorextraction.SysuiColorExtractor; import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin; import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.MenuItem; +import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.OnMenuEventListener; import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.DragDownHelper.DragDownCallback; @@ -117,7 +118,7 @@ import com.android.systemui.statusbar.notification.VisibilityLocationProvider; import com.android.systemui.statusbar.phone.DozeParameters; import com.android.systemui.statusbar.phone.HeadsUpAppearanceController; import com.android.systemui.statusbar.phone.HeadsUpManagerPhone; -import com.android.systemui.statusbar.phone.HeadsUpManagerPhone.AnimationStateHandler; +import com.android.systemui.statusbar.phone.HeadsUpTouchHelper; import com.android.systemui.statusbar.phone.LockscreenGestureLogger; import com.android.systemui.statusbar.phone.NotificationGroupManager; import com.android.systemui.statusbar.phone.NotificationGroupManager.OnGroupChangeListener; @@ -142,10 +143,8 @@ import java.util.function.BiConsumer; /** * A layout which handles a dynamic amount of notifications and presents them in a scrollable stack. */ -public class NotificationStackScrollLayout extends ViewGroup - implements ExpandHelper.Callback, ScrollAdapter, OnHeightChangedListener, - OnGroupChangeListener, VisibilityLocationProvider, NotificationListContainer, - ConfigurationListener, DragDownCallback, AnimationStateHandler, Dumpable { +public class NotificationStackScrollLayout extends ViewGroup implements ScrollAdapter, + NotificationListContainer, ConfigurationListener, Dumpable { public static final float BACKGROUND_ALPHA_DIMMED = 0.7f; private static final String TAG = "StackScroller"; @@ -160,7 +159,6 @@ public class NotificationStackScrollLayout extends ViewGroup private ExpandHelper mExpandHelper; private final NotificationSwipeHelper mSwipeHelper; - private boolean mSwipingInProgress; private int mCurrentStackHeight = Integer.MAX_VALUE; private final Paint mBackgroundPaint = new Paint(); private final boolean mShouldDrawNotificationBackground; @@ -344,7 +342,7 @@ public class NotificationStackScrollLayout extends ViewGroup private float mDimAmount; private ValueAnimator mDimAnimator; private ArrayList<ExpandableView> mTmpSortedChildren = new ArrayList<>(); - private Animator.AnimatorListener mDimEndListener = new AnimatorListenerAdapter() { + private final Animator.AnimatorListener mDimEndListener = new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { mDimAnimator = null; @@ -485,12 +483,12 @@ public class NotificationStackScrollLayout extends ViewGroup mBgColor = context.getColor(R.color.notification_shade_background_color); int minHeight = res.getDimensionPixelSize(R.dimen.notification_min_height); int maxHeight = res.getDimensionPixelSize(R.dimen.notification_max_height); - mExpandHelper = new ExpandHelper(getContext(), this, + mExpandHelper = new ExpandHelper(getContext(), mExpandHelperCallback, minHeight, maxHeight); mExpandHelper.setEventSource(this); mExpandHelper.setScrollAdapter(this); - mSwipeHelper = new NotificationSwipeHelper(SwipeHelper.X, new SwipeHelperCallback(), - getContext(), new NotificationMenuListener()); + mSwipeHelper = new NotificationSwipeHelper(SwipeHelper.X, mNotificationCallback, + getContext(), mMenuEventListener); mStackScrollAlgorithm = createStackScrollAlgorithm(context); initView(context); mFalsingManager = FalsingManager.getInstance(context); @@ -530,7 +528,7 @@ public class NotificationStackScrollLayout extends ViewGroup inflateEmptyShadeView(); inflateFooterView(); - mVisualStabilityManager.setVisibilityLocationProvider(this); + mVisualStabilityManager.setVisibilityLocationProvider(this::isInVisibleLocation); setLongPressListener(mEntryManager.getNotificationLongClicker()); } @@ -589,7 +587,7 @@ public class NotificationStackScrollLayout extends ViewGroup return false; } - @ShadeViewRefactor(RefactorComponent.INPUT) + @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) public RemoteInputController.Delegate createDelegate() { return new RemoteInputController.Delegate() { public void setRemoteInputActive(NotificationData.Entry entry, @@ -628,7 +626,7 @@ public class NotificationStackScrollLayout extends ViewGroup } @Override - @ShadeViewRefactor(RefactorComponent.INPUT) + @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) public NotificationSwipeActionHelper getSwipeActionHelper() { return mSwipeHelper; } @@ -1245,11 +1243,6 @@ public class NotificationStackScrollLayout extends ViewGroup return firstChild != null ? firstChild.getMinHeight() : mCollapsedSize; } - @ShadeViewRefactor(RefactorComponent.INPUT) - public void setLongPressListener(ExpandableNotificationRow.LongPressListener listener) { - mLongPressListener = listener; - } - @ShadeViewRefactor(RefactorComponent.ADAPTER) public void setQsContainer(ViewGroup qsContainer) { mQsContainer = qsContainer; @@ -1273,7 +1266,7 @@ public class NotificationStackScrollLayout extends ViewGroup return false; } - @ShadeViewRefactor(RefactorComponent.INPUT) + @ShadeViewRefactor(RefactorComponent.COORDINATOR) public ExpandableView getClosestChildAtRawPosition(float touchX, float touchY) { getLocationOnScreen(mTempInt2); float localTouchY = touchY - mTempInt2[1]; @@ -1303,16 +1296,8 @@ public class NotificationStackScrollLayout extends ViewGroup return closestChild; } - @Override - @ShadeViewRefactor(RefactorComponent.INPUT) - public ExpandableView getChildAtRawPosition(float touchX, float touchY) { - getLocationOnScreen(mTempInt2); - return getChildAtPosition(touchX - mTempInt2[0], touchY - mTempInt2[1]); - } - - @Override - @ShadeViewRefactor(RefactorComponent.INPUT) - public ExpandableView getChildAtPosition(float touchX, float touchY) { + @ShadeViewRefactor(RefactorComponent.COORDINATOR) + private ExpandableView getChildAtPosition(float touchX, float touchY) { return getChildAtPosition(touchX, touchY, true /* requireMinHeight */); } @@ -1325,7 +1310,7 @@ public class NotificationStackScrollLayout extends ViewGroup * @param requireMinHeight Whether a minimum height is required for a child to be returned. * @return the child at the given location. */ - @ShadeViewRefactor(RefactorComponent.INPUT) + @ShadeViewRefactor(RefactorComponent.COORDINATOR) private ExpandableView getChildAtPosition(float touchX, float touchY, boolean requireMinHeight) { // find the view under the pointer, accounting for GONE views @@ -1365,71 +1350,9 @@ public class NotificationStackScrollLayout extends ViewGroup return null; } - @Override - @ShadeViewRefactor(RefactorComponent.ADAPTER) - public boolean canChildBeExpanded(View v) { - return v instanceof ExpandableNotificationRow - && ((ExpandableNotificationRow) v).isExpandable() - && !((ExpandableNotificationRow) v).areGutsExposed() - && (mIsExpanded || !((ExpandableNotificationRow) v).isPinned()); - } - - /* Only ever called as a consequence of an expansion gesture in the shade. */ - @Override - @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) - public void setUserExpandedChild(View v, boolean userExpanded) { - if (v instanceof ExpandableNotificationRow) { - ExpandableNotificationRow row = (ExpandableNotificationRow) v; - if (userExpanded && onKeyguard()) { - // Due to a race when locking the screen while touching, a notification may be - // expanded even after we went back to keyguard. An example of this happens if - // you click in the empty space while expanding a group. - - // We also need to un-user lock it here, since otherwise the content height - // calculated might be wrong. We also can't invert the two calls since - // un-userlocking it will trigger a layout switch in the content view. - row.setUserLocked(false); - updateContentHeight(); - notifyHeightChangeListener(row); - return; - } - row.setUserExpanded(userExpanded, true /* allowChildrenExpansion */); - row.onExpandedByGesture(userExpanded); - } - } - - @Override - @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) - public void setExpansionCancelled(View v) { - if (v instanceof ExpandableNotificationRow) { - ((ExpandableNotificationRow) v).setGroupExpansionChanging(false); - } - } - - @Override - @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) - public void setUserLockedChild(View v, boolean userLocked) { - if (v instanceof ExpandableNotificationRow) { - ((ExpandableNotificationRow) v).setUserLocked(userLocked); - } - cancelLongPress(); - requestDisallowInterceptTouchEvent(true); - } - - @Override - @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) - public void expansionStateChanged(boolean isExpanding) { - mExpandingNotification = isExpanding; - if (!mExpandedInThisMotion) { - mMaxScrollAfterExpand = mOwnScrollY; - mExpandedInThisMotion = true; - } - } - - @Override - @ShadeViewRefactor(RefactorComponent.COORDINATOR) - public int getMaxExpandHeight(ExpandableView view) { - return view.getMaxContentHeight(); + private ExpandableView getChildAtRawPosition(float touchX, float touchY) { + getLocationOnScreen(mTempInt2); + return getChildAtPosition(touchX - mTempInt2[0], touchY - mTempInt2[1]); } @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) @@ -1526,14 +1449,6 @@ public class NotificationStackScrollLayout extends ViewGroup return mStatusBarState == StatusBarState.KEYGUARD; } - @ShadeViewRefactor(RefactorComponent.INPUT) - private void setSwipingInProgress(boolean isSwiped) { - mSwipingInProgress = isSwiped; - if (isSwiped) { - requestDisallowInterceptTouchEvent(true); - } - } - @Override @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) protected void onConfigurationChanged(Configuration newConfig) { @@ -1567,249 +1482,6 @@ public class NotificationStackScrollLayout extends ViewGroup return this; } - @Override - @ShadeViewRefactor(RefactorComponent.INPUT) - public boolean onTouchEvent(MotionEvent ev) { - boolean isCancelOrUp = ev.getActionMasked() == MotionEvent.ACTION_CANCEL - || ev.getActionMasked() == MotionEvent.ACTION_UP; - handleEmptySpaceClick(ev); - boolean expandWantsIt = false; - if (mIsExpanded && !mSwipingInProgress && !mOnlyScrollingInThisMotion) { - if (isCancelOrUp) { - mExpandHelper.onlyObserveMovements(false); - } - boolean wasExpandingBefore = mExpandingNotification; - expandWantsIt = mExpandHelper.onTouchEvent(ev); - if (mExpandedInThisMotion && !mExpandingNotification && wasExpandingBefore - && !mDisallowScrollingInThisMotion) { - dispatchDownEventToScroller(ev); - } - } - boolean scrollerWantsIt = false; - if (mIsExpanded && !mSwipingInProgress && !mExpandingNotification - && !mDisallowScrollingInThisMotion) { - scrollerWantsIt = onScrollTouch(ev); - } - boolean horizontalSwipeWantsIt = false; - if (!mIsBeingDragged - && !mExpandingNotification - && !mExpandedInThisMotion - && !mOnlyScrollingInThisMotion - && !mDisallowDismissInThisMotion) { - horizontalSwipeWantsIt = mSwipeHelper.onTouchEvent(ev); - } - - // Check if we need to clear any snooze leavebehinds - NotificationGuts guts = mNotificationGutsManager.getExposedGuts(); - if (guts != null && !NotificationSwipeHelper.isTouchInView(ev, guts) - && guts.getGutsContent() instanceof NotificationSnooze) { - NotificationSnooze ns = (NotificationSnooze) guts.getGutsContent(); - if ((ns.isExpanded() && isCancelOrUp) - || (!horizontalSwipeWantsIt && scrollerWantsIt)) { - // If the leavebehind is expanded we clear it on the next up event, otherwise we - // clear it on the next non-horizontal swipe or expand event. - checkSnoozeLeavebehind(); - } - } - if (ev.getActionMasked() == MotionEvent.ACTION_UP) { - mCheckForLeavebehind = true; - } - return horizontalSwipeWantsIt || scrollerWantsIt || expandWantsIt || super.onTouchEvent(ev); - } - - @ShadeViewRefactor(RefactorComponent.INPUT) - private void dispatchDownEventToScroller(MotionEvent ev) { - MotionEvent downEvent = MotionEvent.obtain(ev); - downEvent.setAction(MotionEvent.ACTION_DOWN); - onScrollTouch(downEvent); - downEvent.recycle(); - } - - @Override - @ShadeViewRefactor(RefactorComponent.INPUT) - public boolean onGenericMotionEvent(MotionEvent event) { - if (!isScrollingEnabled() || !mIsExpanded || mSwipingInProgress || mExpandingNotification - || mDisallowScrollingInThisMotion) { - return false; - } - if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) { - switch (event.getAction()) { - case MotionEvent.ACTION_SCROLL: { - if (!mIsBeingDragged) { - final float vscroll = event.getAxisValue(MotionEvent.AXIS_VSCROLL); - if (vscroll != 0) { - final int delta = (int) (vscroll * getVerticalScrollFactor()); - final int range = getScrollRange(); - int oldScrollY = mOwnScrollY; - int newScrollY = oldScrollY - delta; - if (newScrollY < 0) { - newScrollY = 0; - } else if (newScrollY > range) { - newScrollY = range; - } - if (newScrollY != oldScrollY) { - setOwnScrollY(newScrollY); - return true; - } - } - } - } - } - } - return super.onGenericMotionEvent(event); - } - - @ShadeViewRefactor(RefactorComponent.INPUT) - private boolean onScrollTouch(MotionEvent ev) { - if (!isScrollingEnabled()) { - return false; - } - if (isInsideQsContainer(ev) && !mIsBeingDragged) { - return false; - } - mForcedScroll = null; - initVelocityTrackerIfNotExists(); - mVelocityTracker.addMovement(ev); - - final int action = ev.getAction(); - - switch (action & MotionEvent.ACTION_MASK) { - case MotionEvent.ACTION_DOWN: { - if (getChildCount() == 0 || !isInContentBounds(ev)) { - return false; - } - boolean isBeingDragged = !mScroller.isFinished(); - setIsBeingDragged(isBeingDragged); - /* - * If being flinged and user touches, stop the fling. isFinished - * will be false if being flinged. - */ - if (!mScroller.isFinished()) { - mScroller.forceFinished(true); - } - - // Remember where the motion event started - mLastMotionY = (int) ev.getY(); - mDownX = (int) ev.getX(); - mActivePointerId = ev.getPointerId(0); - break; - } - case MotionEvent.ACTION_MOVE: - final int activePointerIndex = ev.findPointerIndex(mActivePointerId); - if (activePointerIndex == -1) { - Log.e(TAG, "Invalid pointerId=" + mActivePointerId + " in onTouchEvent"); - break; - } - - final int y = (int) ev.getY(activePointerIndex); - final int x = (int) ev.getX(activePointerIndex); - int deltaY = mLastMotionY - y; - final int xDiff = Math.abs(x - mDownX); - final int yDiff = Math.abs(deltaY); - if (!mIsBeingDragged && yDiff > mTouchSlop && yDiff > xDiff) { - setIsBeingDragged(true); - if (deltaY > 0) { - deltaY -= mTouchSlop; - } else { - deltaY += mTouchSlop; - } - } - if (mIsBeingDragged) { - // Scroll to follow the motion event - mLastMotionY = y; - int range = getScrollRange(); - if (mExpandedInThisMotion) { - range = Math.min(range, mMaxScrollAfterExpand); - } - - float scrollAmount; - if (deltaY < 0) { - scrollAmount = overScrollDown(deltaY); - } else { - scrollAmount = overScrollUp(deltaY, range); - } - - // Calling customOverScrollBy will call onCustomOverScrolled, which - // sets the scrolling if applicable. - if (scrollAmount != 0.0f) { - // The scrolling motion could not be compensated with the - // existing overScroll, we have to scroll the view - customOverScrollBy((int) scrollAmount, mOwnScrollY, - range, getHeight() / 2); - // If we're scrolling, leavebehinds should be dismissed - checkSnoozeLeavebehind(); - } - } - break; - case MotionEvent.ACTION_UP: - if (mIsBeingDragged) { - final VelocityTracker velocityTracker = mVelocityTracker; - velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity); - int initialVelocity = (int) velocityTracker.getYVelocity(mActivePointerId); - - if (shouldOverScrollFling(initialVelocity)) { - onOverScrollFling(true, initialVelocity); - } else { - if (getChildCount() > 0) { - if ((Math.abs(initialVelocity) > mMinimumVelocity)) { - float currentOverScrollTop = getCurrentOverScrollAmount(true); - if (currentOverScrollTop == 0.0f || initialVelocity > 0) { - fling(-initialVelocity); - } else { - onOverScrollFling(false, initialVelocity); - } - } else { - if (mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0, - getScrollRange())) { - animateScroll(); - } - } - } - } - mActivePointerId = INVALID_POINTER; - endDrag(); - } - - break; - case MotionEvent.ACTION_CANCEL: - if (mIsBeingDragged && getChildCount() > 0) { - if (mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0, getScrollRange())) { - animateScroll(); - } - mActivePointerId = INVALID_POINTER; - endDrag(); - } - break; - case MotionEvent.ACTION_POINTER_DOWN: { - final int index = ev.getActionIndex(); - mLastMotionY = (int) ev.getY(index); - mDownX = (int) ev.getX(index); - mActivePointerId = ev.getPointerId(index); - break; - } - case MotionEvent.ACTION_POINTER_UP: - onSecondaryPointerUp(ev); - mLastMotionY = (int) ev.getY(ev.findPointerIndex(mActivePointerId)); - mDownX = (int) ev.getX(ev.findPointerIndex(mActivePointerId)); - break; - } - return true; - } - - @ShadeViewRefactor(RefactorComponent.INPUT) - protected boolean isInsideQsContainer(MotionEvent ev) { - return ev.getY() < mQsContainer.getBottom(); - } - - @ShadeViewRefactor(RefactorComponent.INPUT) - private void onOverScrollFling(boolean open, int initialVelocity) { - if (mOverscrollTopChangedListener != null) { - mOverscrollTopChangedListener.flingTopOverscroll(initialVelocity, open); - } - mDontReportNextOverScroll = true; - setOverScrollAmount(0.0f, true, false); - } - /** * Perform a scroll upwards and adapt the overscroll amounts accordingly * @@ -1817,7 +1489,7 @@ public class NotificationStackScrollLayout extends ViewGroup * @return The amount of scrolling to be performed by the scroller, * not handled by the overScroll amount. */ - @ShadeViewRefactor(RefactorComponent.INPUT) + @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) private float overScrollUp(int deltaY, int range) { deltaY = Math.max(deltaY, 0); float currentTopAmount = getCurrentOverScrollAmount(true); @@ -1876,24 +1548,6 @@ public class NotificationStackScrollLayout extends ViewGroup return scrollAmount; } - @ShadeViewRefactor(RefactorComponent.INPUT) - private void onSecondaryPointerUp(MotionEvent ev) { - final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >> - MotionEvent.ACTION_POINTER_INDEX_SHIFT; - final int pointerId = ev.getPointerId(pointerIndex); - if (pointerId == mActivePointerId) { - // This was our active pointer going up. Choose a new - // active pointer and adjust accordingly. - // TODO: Make this decision more intelligent. - final int newPointerIndex = pointerIndex == 0 ? 1 : 0; - mLastMotionY = (int) ev.getY(newPointerIndex); - mActivePointerId = ev.getPointerId(newPointerIndex); - if (mVelocityTracker != null) { - mVelocityTracker.clear(); - } - } - } - @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) private void initVelocityTrackerIfNotExists() { if (mVelocityTracker == null) { @@ -2636,7 +2290,7 @@ public class NotificationStackScrollLayout extends ViewGroup * numbers mean that the finger/cursor is moving down the screen, * which means we want to scroll towards the top. */ - @ShadeViewRefactor(RefactorComponent.INPUT) + @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) protected void fling(int velocityY) { if (getChildCount() > 0) { int scrollRange = getScrollRange(); @@ -2674,7 +2328,7 @@ public class NotificationStackScrollLayout extends ViewGroup * @return Whether a fling performed on the top overscroll edge lead to the expanded * overScroll view (i.e QS). */ - @ShadeViewRefactor(RefactorComponent.INPUT) + @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) private boolean shouldOverScrollFling(int initialVelocity) { float topOverScroll = getCurrentOverScrollAmount(true); return mScrolledToTopOnFirstDown @@ -2757,7 +2411,7 @@ public class NotificationStackScrollLayout extends ViewGroup return Math.max(desiredPadding, mIntrinsicPadding); } - @ShadeViewRefactor(RefactorComponent.INPUT) + @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) private float getRubberBandFactor(boolean onTop) { if (!onTop) { return RUBBER_BAND_FACTOR_NORMAL; @@ -2777,99 +2431,13 @@ public class NotificationStackScrollLayout extends ViewGroup * rubberbanded, false if it is technically an overscroll but rather a motion to expand the * overscroll view (e.g. expand QS). */ - @ShadeViewRefactor(RefactorComponent.INPUT) + @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) private boolean isRubberbanded(boolean onTop) { return !onTop || mExpandedInThisMotion || mIsExpansionChanging || mPanelTracking || !mScrolledToTopOnFirstDown; } - @ShadeViewRefactor(RefactorComponent.INPUT) - private void endDrag() { - setIsBeingDragged(false); - - recycleVelocityTracker(); - - if (getCurrentOverScrollAmount(true /* onTop */) > 0) { - setOverScrollAmount(0, true /* onTop */, true /* animate */); - } - if (getCurrentOverScrollAmount(false /* onTop */) > 0) { - setOverScrollAmount(0, false /* onTop */, true /* animate */); - } - } - - @ShadeViewRefactor(RefactorComponent.INPUT) - private void transformTouchEvent(MotionEvent ev, View sourceView, View targetView) { - ev.offsetLocation(sourceView.getX(), sourceView.getY()); - ev.offsetLocation(-targetView.getX(), -targetView.getY()); - } - - @Override - @ShadeViewRefactor(RefactorComponent.INPUT) - public boolean onInterceptTouchEvent(MotionEvent ev) { - initDownStates(ev); - handleEmptySpaceClick(ev); - boolean expandWantsIt = false; - if (!mSwipingInProgress && !mOnlyScrollingInThisMotion) { - expandWantsIt = mExpandHelper.onInterceptTouchEvent(ev); - } - boolean scrollWantsIt = false; - if (!mSwipingInProgress && !mExpandingNotification) { - scrollWantsIt = onInterceptTouchEventScroll(ev); - } - boolean swipeWantsIt = false; - if (!mIsBeingDragged - && !mExpandingNotification - && !mExpandedInThisMotion - && !mOnlyScrollingInThisMotion - && !mDisallowDismissInThisMotion) { - swipeWantsIt = mSwipeHelper.onInterceptTouchEvent(ev); - } - // Check if we need to clear any snooze leavebehinds - boolean isUp = ev.getActionMasked() == MotionEvent.ACTION_UP; - NotificationGuts guts = mNotificationGutsManager.getExposedGuts(); - if (!NotificationSwipeHelper.isTouchInView(ev, guts) && isUp && !swipeWantsIt && - !expandWantsIt && !scrollWantsIt) { - mCheckForLeavebehind = false; - mNotificationGutsManager.closeAndSaveGuts(true /* removeLeavebehind */, - false /* force */, false /* removeControls */, -1 /* x */, -1 /* y */, - false /* resetMenu */); - } - if (ev.getActionMasked() == MotionEvent.ACTION_UP) { - mCheckForLeavebehind = true; - } - return swipeWantsIt || scrollWantsIt || expandWantsIt || super.onInterceptTouchEvent(ev); - } - - @ShadeViewRefactor(RefactorComponent.INPUT) - private void handleEmptySpaceClick(MotionEvent ev) { - switch (ev.getActionMasked()) { - case MotionEvent.ACTION_MOVE: - if (mTouchIsClick && (Math.abs(ev.getY() - mInitialTouchY) > mTouchSlop - || Math.abs(ev.getX() - mInitialTouchX) > mTouchSlop)) { - mTouchIsClick = false; - } - break; - case MotionEvent.ACTION_UP: - if (mStatusBarState != StatusBarState.KEYGUARD && mTouchIsClick && - isBelowLastNotification(mInitialTouchX, mInitialTouchY)) { - mOnEmptySpaceClickListener.onEmptySpaceClicked(mInitialTouchX, mInitialTouchY); - } - break; - } - } - @ShadeViewRefactor(RefactorComponent.INPUT) - private void initDownStates(MotionEvent ev) { - if (ev.getAction() == MotionEvent.ACTION_DOWN) { - mExpandedInThisMotion = false; - mOnlyScrollingInThisMotion = !mScroller.isFinished(); - mDisallowScrollingInThisMotion = false; - mDisallowDismissInThisMotion = false; - mTouchIsClick = true; - mInitialTouchX = ev.getX(); - mInitialTouchY = ev.getY(); - } - } @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) public void setChildTransferInProgress(boolean childTransferInProgress) { @@ -2896,15 +2464,6 @@ public class NotificationStackScrollLayout extends ViewGroup mCurrentStackScrollState.removeViewStateForView(child); } - @Override - @ShadeViewRefactor(RefactorComponent.INPUT) - public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) { - super.requestDisallowInterceptTouchEvent(disallowIntercept); - if (disallowIntercept) { - cancelLongPress(); - } - } - @ShadeViewRefactor(RefactorComponent.COORDINATOR) private void onViewRemovedInternal(View child, ViewGroup container) { if (mChangePositionInProgress) { @@ -3600,6 +3159,385 @@ public class NotificationStackScrollLayout extends ViewGroup mGoToFullShadeNeedsAnimation = false; } + @ShadeViewRefactor(RefactorComponent.LAYOUT_ALGORITHM) + protected StackScrollAlgorithm createStackScrollAlgorithm(Context context) { + return new StackScrollAlgorithm(context); + } + + /** + * @return Whether a y coordinate is inside the content. + */ + @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) + public boolean isInContentBounds(float y) { + return y < getHeight() - getEmptyBottomMargin(); + } + + @ShadeViewRefactor(RefactorComponent.INPUT) + public void setLongPressListener(ExpandableNotificationRow.LongPressListener listener) { + mLongPressListener = listener; + } + + @Override + @ShadeViewRefactor(RefactorComponent.INPUT) + public boolean onTouchEvent(MotionEvent ev) { + boolean isCancelOrUp = ev.getActionMasked() == MotionEvent.ACTION_CANCEL + || ev.getActionMasked() == MotionEvent.ACTION_UP; + handleEmptySpaceClick(ev); + boolean expandWantsIt = false; + boolean swipingInProgress = mSwipeHelper.isSwipingInProgress(); + if (mIsExpanded && !swipingInProgress && !mOnlyScrollingInThisMotion) { + if (isCancelOrUp) { + mExpandHelper.onlyObserveMovements(false); + } + boolean wasExpandingBefore = mExpandingNotification; + expandWantsIt = mExpandHelper.onTouchEvent(ev); + if (mExpandedInThisMotion && !mExpandingNotification && wasExpandingBefore + && !mDisallowScrollingInThisMotion) { + dispatchDownEventToScroller(ev); + } + } + boolean scrollerWantsIt = false; + if (mIsExpanded && !swipingInProgress && !mExpandingNotification + && !mDisallowScrollingInThisMotion) { + scrollerWantsIt = onScrollTouch(ev); + } + boolean horizontalSwipeWantsIt = false; + if (!mIsBeingDragged + && !mExpandingNotification + && !mExpandedInThisMotion + && !mOnlyScrollingInThisMotion + && !mDisallowDismissInThisMotion) { + horizontalSwipeWantsIt = mSwipeHelper.onTouchEvent(ev); + } + + // Check if we need to clear any snooze leavebehinds + NotificationGuts guts = mNotificationGutsManager.getExposedGuts(); + if (guts != null && !NotificationSwipeHelper.isTouchInView(ev, guts) + && guts.getGutsContent() instanceof NotificationSnooze) { + NotificationSnooze ns = (NotificationSnooze) guts.getGutsContent(); + if ((ns.isExpanded() && isCancelOrUp) + || (!horizontalSwipeWantsIt && scrollerWantsIt)) { + // If the leavebehind is expanded we clear it on the next up event, otherwise we + // clear it on the next non-horizontal swipe or expand event. + checkSnoozeLeavebehind(); + } + } + if (ev.getActionMasked() == MotionEvent.ACTION_UP) { + mCheckForLeavebehind = true; + } + return horizontalSwipeWantsIt || scrollerWantsIt || expandWantsIt || super.onTouchEvent(ev); + } + + @ShadeViewRefactor(RefactorComponent.INPUT) + private void dispatchDownEventToScroller(MotionEvent ev) { + MotionEvent downEvent = MotionEvent.obtain(ev); + downEvent.setAction(MotionEvent.ACTION_DOWN); + onScrollTouch(downEvent); + downEvent.recycle(); + } + + @Override + @ShadeViewRefactor(RefactorComponent.INPUT) + public boolean onGenericMotionEvent(MotionEvent event) { + if (!isScrollingEnabled() || !mIsExpanded || mSwipeHelper.isSwipingInProgress() || mExpandingNotification + || mDisallowScrollingInThisMotion) { + return false; + } + if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) { + switch (event.getAction()) { + case MotionEvent.ACTION_SCROLL: { + if (!mIsBeingDragged) { + final float vscroll = event.getAxisValue(MotionEvent.AXIS_VSCROLL); + if (vscroll != 0) { + final int delta = (int) (vscroll * getVerticalScrollFactor()); + final int range = getScrollRange(); + int oldScrollY = mOwnScrollY; + int newScrollY = oldScrollY - delta; + if (newScrollY < 0) { + newScrollY = 0; + } else if (newScrollY > range) { + newScrollY = range; + } + if (newScrollY != oldScrollY) { + setOwnScrollY(newScrollY); + return true; + } + } + } + } + } + } + return super.onGenericMotionEvent(event); + } + + @ShadeViewRefactor(RefactorComponent.INPUT) + private boolean onScrollTouch(MotionEvent ev) { + if (!isScrollingEnabled()) { + return false; + } + if (isInsideQsContainer(ev) && !mIsBeingDragged) { + return false; + } + mForcedScroll = null; + initVelocityTrackerIfNotExists(); + mVelocityTracker.addMovement(ev); + + final int action = ev.getAction(); + + switch (action & MotionEvent.ACTION_MASK) { + case MotionEvent.ACTION_DOWN: { + if (getChildCount() == 0 || !isInContentBounds(ev)) { + return false; + } + boolean isBeingDragged = !mScroller.isFinished(); + setIsBeingDragged(isBeingDragged); + /* + * If being flinged and user touches, stop the fling. isFinished + * will be false if being flinged. + */ + if (!mScroller.isFinished()) { + mScroller.forceFinished(true); + } + + // Remember where the motion event started + mLastMotionY = (int) ev.getY(); + mDownX = (int) ev.getX(); + mActivePointerId = ev.getPointerId(0); + break; + } + case MotionEvent.ACTION_MOVE: + final int activePointerIndex = ev.findPointerIndex(mActivePointerId); + if (activePointerIndex == -1) { + Log.e(TAG, "Invalid pointerId=" + mActivePointerId + " in onTouchEvent"); + break; + } + + final int y = (int) ev.getY(activePointerIndex); + final int x = (int) ev.getX(activePointerIndex); + int deltaY = mLastMotionY - y; + final int xDiff = Math.abs(x - mDownX); + final int yDiff = Math.abs(deltaY); + if (!mIsBeingDragged && yDiff > mTouchSlop && yDiff > xDiff) { + setIsBeingDragged(true); + if (deltaY > 0) { + deltaY -= mTouchSlop; + } else { + deltaY += mTouchSlop; + } + } + if (mIsBeingDragged) { + // Scroll to follow the motion event + mLastMotionY = y; + int range = getScrollRange(); + if (mExpandedInThisMotion) { + range = Math.min(range, mMaxScrollAfterExpand); + } + + float scrollAmount; + if (deltaY < 0) { + scrollAmount = overScrollDown(deltaY); + } else { + scrollAmount = overScrollUp(deltaY, range); + } + + // Calling customOverScrollBy will call onCustomOverScrolled, which + // sets the scrolling if applicable. + if (scrollAmount != 0.0f) { + // The scrolling motion could not be compensated with the + // existing overScroll, we have to scroll the view + customOverScrollBy((int) scrollAmount, mOwnScrollY, + range, getHeight() / 2); + // If we're scrolling, leavebehinds should be dismissed + checkSnoozeLeavebehind(); + } + } + break; + case MotionEvent.ACTION_UP: + if (mIsBeingDragged) { + final VelocityTracker velocityTracker = mVelocityTracker; + velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity); + int initialVelocity = (int) velocityTracker.getYVelocity(mActivePointerId); + + if (shouldOverScrollFling(initialVelocity)) { + onOverScrollFling(true, initialVelocity); + } else { + if (getChildCount() > 0) { + if ((Math.abs(initialVelocity) > mMinimumVelocity)) { + float currentOverScrollTop = getCurrentOverScrollAmount(true); + if (currentOverScrollTop == 0.0f || initialVelocity > 0) { + fling(-initialVelocity); + } else { + onOverScrollFling(false, initialVelocity); + } + } else { + if (mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0, + getScrollRange())) { + animateScroll(); + } + } + } + } + mActivePointerId = INVALID_POINTER; + endDrag(); + } + + break; + case MotionEvent.ACTION_CANCEL: + if (mIsBeingDragged && getChildCount() > 0) { + if (mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0, getScrollRange())) { + animateScroll(); + } + mActivePointerId = INVALID_POINTER; + endDrag(); + } + break; + case MotionEvent.ACTION_POINTER_DOWN: { + final int index = ev.getActionIndex(); + mLastMotionY = (int) ev.getY(index); + mDownX = (int) ev.getX(index); + mActivePointerId = ev.getPointerId(index); + break; + } + case MotionEvent.ACTION_POINTER_UP: + onSecondaryPointerUp(ev); + mLastMotionY = (int) ev.getY(ev.findPointerIndex(mActivePointerId)); + mDownX = (int) ev.getX(ev.findPointerIndex(mActivePointerId)); + break; + } + return true; + } + + @ShadeViewRefactor(RefactorComponent.INPUT) + protected boolean isInsideQsContainer(MotionEvent ev) { + return ev.getY() < mQsContainer.getBottom(); + } + + @ShadeViewRefactor(RefactorComponent.INPUT) + private void onOverScrollFling(boolean open, int initialVelocity) { + if (mOverscrollTopChangedListener != null) { + mOverscrollTopChangedListener.flingTopOverscroll(initialVelocity, open); + } + mDontReportNextOverScroll = true; + setOverScrollAmount(0.0f, true, false); + } + + + @ShadeViewRefactor(RefactorComponent.INPUT) + private void onSecondaryPointerUp(MotionEvent ev) { + final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >> + MotionEvent.ACTION_POINTER_INDEX_SHIFT; + final int pointerId = ev.getPointerId(pointerIndex); + if (pointerId == mActivePointerId) { + // This was our active pointer going up. Choose a new + // active pointer and adjust accordingly. + // TODO: Make this decision more intelligent. + final int newPointerIndex = pointerIndex == 0 ? 1 : 0; + mLastMotionY = (int) ev.getY(newPointerIndex); + mActivePointerId = ev.getPointerId(newPointerIndex); + if (mVelocityTracker != null) { + mVelocityTracker.clear(); + } + } + } + + @ShadeViewRefactor(RefactorComponent.INPUT) + private void endDrag() { + setIsBeingDragged(false); + + recycleVelocityTracker(); + + if (getCurrentOverScrollAmount(true /* onTop */) > 0) { + setOverScrollAmount(0, true /* onTop */, true /* animate */); + } + if (getCurrentOverScrollAmount(false /* onTop */) > 0) { + setOverScrollAmount(0, false /* onTop */, true /* animate */); + } + } + + @ShadeViewRefactor(RefactorComponent.INPUT) + private void transformTouchEvent(MotionEvent ev, View sourceView, View targetView) { + ev.offsetLocation(sourceView.getX(), sourceView.getY()); + ev.offsetLocation(-targetView.getX(), -targetView.getY()); + } + + @Override + @ShadeViewRefactor(RefactorComponent.INPUT) + public boolean onInterceptTouchEvent(MotionEvent ev) { + initDownStates(ev); + handleEmptySpaceClick(ev); + boolean expandWantsIt = false; + boolean swipingInProgress = mSwipeHelper.isSwipingInProgress(); + if (!swipingInProgress && !mOnlyScrollingInThisMotion) { + expandWantsIt = mExpandHelper.onInterceptTouchEvent(ev); + } + boolean scrollWantsIt = false; + if (!swipingInProgress && !mExpandingNotification) { + scrollWantsIt = onInterceptTouchEventScroll(ev); + } + boolean swipeWantsIt = false; + if (!mIsBeingDragged + && !mExpandingNotification + && !mExpandedInThisMotion + && !mOnlyScrollingInThisMotion + && !mDisallowDismissInThisMotion) { + swipeWantsIt = mSwipeHelper.onInterceptTouchEvent(ev); + } + // Check if we need to clear any snooze leavebehinds + boolean isUp = ev.getActionMasked() == MotionEvent.ACTION_UP; + NotificationGuts guts = mNotificationGutsManager.getExposedGuts(); + if (!NotificationSwipeHelper.isTouchInView(ev, guts) && isUp && !swipeWantsIt && + !expandWantsIt && !scrollWantsIt) { + mCheckForLeavebehind = false; + mNotificationGutsManager.closeAndSaveGuts(true /* removeLeavebehind */, + false /* force */, false /* removeControls */, -1 /* x */, -1 /* y */, + false /* resetMenu */); + } + if (ev.getActionMasked() == MotionEvent.ACTION_UP) { + mCheckForLeavebehind = true; + } + return swipeWantsIt || scrollWantsIt || expandWantsIt || super.onInterceptTouchEvent(ev); + } + + @ShadeViewRefactor(RefactorComponent.INPUT) + private void handleEmptySpaceClick(MotionEvent ev) { + switch (ev.getActionMasked()) { + case MotionEvent.ACTION_MOVE: + if (mTouchIsClick && (Math.abs(ev.getY() - mInitialTouchY) > mTouchSlop + || Math.abs(ev.getX() - mInitialTouchX) > mTouchSlop)) { + mTouchIsClick = false; + } + break; + case MotionEvent.ACTION_UP: + if (mStatusBarState != StatusBarState.KEYGUARD && mTouchIsClick && + isBelowLastNotification(mInitialTouchX, mInitialTouchY)) { + mOnEmptySpaceClickListener.onEmptySpaceClicked(mInitialTouchX, mInitialTouchY); + } + break; + } + } + + @ShadeViewRefactor(RefactorComponent.INPUT) + private void initDownStates(MotionEvent ev) { + if (ev.getAction() == MotionEvent.ACTION_DOWN) { + mExpandedInThisMotion = false; + mOnlyScrollingInThisMotion = !mScroller.isFinished(); + mDisallowScrollingInThisMotion = false; + mDisallowDismissInThisMotion = false; + mTouchIsClick = true; + mInitialTouchX = ev.getX(); + mInitialTouchY = ev.getY(); + } + } + + @Override + @ShadeViewRefactor(RefactorComponent.INPUT) + public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) { + super.requestDisallowInterceptTouchEvent(disallowIntercept); + if (disallowIntercept) { + cancelLongPress(); + } + } + @ShadeViewRefactor(RefactorComponent.INPUT) private boolean onInterceptTouchEventScroll(MotionEvent ev) { if (!isScrollingEnabled()) { @@ -3710,11 +3648,6 @@ public class NotificationStackScrollLayout extends ViewGroup return mIsBeingDragged; } - @ShadeViewRefactor(RefactorComponent.LAYOUT_ALGORITHM) - protected StackScrollAlgorithm createStackScrollAlgorithm(Context context) { - return new StackScrollAlgorithm(context); - } - /** * @return Whether the specified motion event is actually happening over the content. */ @@ -3723,13 +3656,6 @@ public class NotificationStackScrollLayout extends ViewGroup return isInContentBounds(event.getY()); } - /** - * @return Whether a y coordinate is inside the content. - */ - @ShadeViewRefactor(RefactorComponent.INPUT) - public boolean isInContentBounds(float y) { - return y < getHeight() - getEmptyBottomMargin(); - } @VisibleForTesting @ShadeViewRefactor(RefactorComponent.INPUT) @@ -3742,6 +3668,83 @@ public class NotificationStackScrollLayout extends ViewGroup } } + @ShadeViewRefactor(RefactorComponent.INPUT) + public void requestDisallowLongPress() { + cancelLongPress(); + } + + @ShadeViewRefactor(RefactorComponent.INPUT) + public void requestDisallowDismiss() { + mDisallowDismissInThisMotion = true; + } + + @ShadeViewRefactor(RefactorComponent.INPUT) + public void cancelLongPress() { + mSwipeHelper.cancelLongPress(); + } + + @ShadeViewRefactor(RefactorComponent.INPUT) + public void setOnEmptySpaceClickListener(OnEmptySpaceClickListener listener) { + mOnEmptySpaceClickListener = listener; + } + + /** @hide */ + @Override + @ShadeViewRefactor(RefactorComponent.INPUT) + public boolean performAccessibilityActionInternal(int action, Bundle arguments) { + if (super.performAccessibilityActionInternal(action, arguments)) { + return true; + } + if (!isEnabled()) { + return false; + } + int direction = -1; + switch (action) { + case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD: + // fall through + case android.R.id.accessibilityActionScrollDown: + direction = 1; + // fall through + case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD: + // fall through + case android.R.id.accessibilityActionScrollUp: + final int viewportHeight = getHeight() - mPaddingBottom - mTopPadding - mPaddingTop + - mShelf.getIntrinsicHeight(); + final int targetScrollY = Math.max(0, + Math.min(mOwnScrollY + direction * viewportHeight, getScrollRange())); + if (targetScrollY != mOwnScrollY) { + mScroller.startScroll(mScrollX, mOwnScrollY, 0, targetScrollY - mOwnScrollY); + animateScroll(); + return true; + } + break; + } + return false; + } + + @ShadeViewRefactor(RefactorComponent.INPUT) + public void closeControlsIfOutsideTouch(MotionEvent ev) { + NotificationGuts guts = mNotificationGutsManager.getExposedGuts(); + NotificationMenuRowPlugin menuRow = mSwipeHelper.getCurrentMenuRow(); + View translatingParentView = mSwipeHelper.getTranslatingParentView(); + View view = null; + if (guts != null && !guts.getGutsContent().isLeavebehind()) { + // Only close visible guts if they're not a leavebehind. + view = guts; + } else if (menuRow != null && menuRow.isMenuVisible() + && translatingParentView != null) { + // Checking menu + view = translatingParentView; + } + if (view != null && !NotificationSwipeHelper.isTouchInView(ev, view)) { + // Touch was outside visible guts / menu notification, close what's visible + mNotificationGutsManager.closeAndSaveGuts(false /* removeLeavebehind */, + false /* force */, true /* removeControls */, -1 /* x */, -1 /* y */, + false /* resetMenu */); + resetExposedMenuView(true /* animate */, true /* force */); + } + } + @Override @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) public void onWindowFocusChanged(boolean hasWindowFocus) { @@ -3760,21 +3763,6 @@ public class NotificationStackScrollLayout extends ViewGroup } } - @ShadeViewRefactor(RefactorComponent.INPUT) - public void requestDisallowLongPress() { - cancelLongPress(); - } - - @ShadeViewRefactor(RefactorComponent.INPUT) - public void requestDisallowDismiss() { - mDisallowDismissInThisMotion = true; - } - - @ShadeViewRefactor(RefactorComponent.INPUT) - public void cancelLongPress() { - mSwipeHelper.cancelLongPress(); - } - @Override @ShadeViewRefactor(RefactorComponent.COORDINATOR) public boolean isScrolledToTop() { @@ -3916,7 +3904,6 @@ public class NotificationStackScrollLayout extends ViewGroup } @Override - @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) public void onHeightChanged(ExpandableView view, boolean needsAnimation) { updateContentHeight(); updateScrollPositionOnExpandInBottom(view); @@ -3936,7 +3923,6 @@ public class NotificationStackScrollLayout extends ViewGroup } @Override - @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) public void onReset(ExpandableView view) { updateAnimationState(view); updateChronometerForChild(view); @@ -3969,13 +3955,8 @@ public class NotificationStackScrollLayout extends ViewGroup @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) public void setOnHeightChangedListener( - ExpandableView.OnHeightChangedListener mOnHeightChangedListener) { - this.mOnHeightChangedListener = mOnHeightChangedListener; - } - - @ShadeViewRefactor(RefactorComponent.INPUT) - public void setOnEmptySpaceClickListener(OnEmptySpaceClickListener listener) { - mOnEmptySpaceClickListener = listener; + ExpandableView.OnHeightChangedListener onHeightChangedListener) { + this.mOnHeightChangedListener = onHeightChangedListener; } @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) @@ -4465,7 +4446,7 @@ public class NotificationStackScrollLayout extends ViewGroup @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) public void setGroupManager(NotificationGroupManager groupManager) { this.mGroupManager = groupManager; - mGroupManager.setOnGroupChangeListener(this); + mGroupManager.setOnGroupChangeListener(mOnGroupChangeListener); } @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) @@ -4508,33 +4489,6 @@ public class NotificationStackScrollLayout extends ViewGroup return touchY > mTopPadding + mStackTranslation; } - @Override - @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) - public void onGroupExpansionChanged(ExpandableNotificationRow changedRow, boolean expanded) { - boolean animated = !mGroupExpandedForMeasure && mAnimationsEnabled - && (mIsExpanded || changedRow.isPinned()); - if (animated) { - mExpandedGroupView = changedRow; - mNeedsAnimation = true; - } - changedRow.setChildrenExpanded(expanded, animated); - if (!mGroupExpandedForMeasure) { - onHeightChanged(changedRow, false /* needsAnimation */); - } - runAfterAnimationFinished(new Runnable() { - @Override - public void run() { - changedRow.onFinishedExpansionChange(); - } - }); - } - - @Override - @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) - public void onGroupCreatedFromChildren(NotificationGroupManager.NotificationGroup group) { - mStatusBar.requestNotificationUpdate(); - } - /** @hide */ @Override @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) @@ -4567,46 +4521,6 @@ public class NotificationStackScrollLayout extends ViewGroup info.setClassName(ScrollView.class.getName()); } - /** @hide */ - @Override - @ShadeViewRefactor(RefactorComponent.INPUT) - public boolean performAccessibilityActionInternal(int action, Bundle arguments) { - if (super.performAccessibilityActionInternal(action, arguments)) { - return true; - } - if (!isEnabled()) { - return false; - } - int direction = -1; - switch (action) { - case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD: - // fall through - case android.R.id.accessibilityActionScrollDown: - direction = 1; - // fall through - case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD: - // fall through - case android.R.id.accessibilityActionScrollUp: - final int viewportHeight = getHeight() - mPaddingBottom - mTopPadding - mPaddingTop - - mShelf.getIntrinsicHeight(); - final int targetScrollY = Math.max(0, - Math.min(mOwnScrollY + direction * viewportHeight, getScrollRange())); - if (targetScrollY != mOwnScrollY) { - mScroller.startScroll(mScrollX, mOwnScrollY, 0, targetScrollY - mOwnScrollY); - animateScroll(); - return true; - } - break; - } - return false; - } - - @Override - @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) - public void onGroupsChanged() { - mStatusBar.requestNotificationUpdate(); - } - @ShadeViewRefactor(RefactorComponent.COORDINATOR) public void generateChildOrderChangedEvent() { if (mIsExpanded && mAnimationsEnabled) { @@ -4649,7 +4563,7 @@ public class NotificationStackScrollLayout extends ViewGroup public void setHeadsUpManager(HeadsUpManagerPhone headsUpManager) { mHeadsUpManager = headsUpManager; mHeadsUpManager.addListener(mRoundnessManager); - mHeadsUpManager.setAnimationStateHandler(this); + mHeadsUpManager.setAnimationStateHandler(this::setHeadsUpGoingAwayAnimationsAllowed); } @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) @@ -5168,67 +5082,7 @@ public class NotificationStackScrollLayout extends ViewGroup return !mEntryManager.getNotificationData().getActiveNotifications().isEmpty(); } - // ---------------------- DragDownHelper.OnDragDownListener ------------------------------------ - - - /* Only ever called as a consequence of a lockscreen expansion gesture. */ - @Override - @ShadeViewRefactor(RefactorComponent.INPUT) - public boolean onDraggedDown(View startingChild, int dragLengthY) { - if (mStatusBarState == StatusBarState.KEYGUARD - && hasActiveNotifications() && (!mStatusBar.isDozing() || mStatusBar.isPulsing())) { - mLockscreenGestureLogger.write( - MetricsEvent.ACTION_LS_SHADE, - (int) (dragLengthY / mDisplayMetrics.density), - 0 /* velocityDp - N/A */); - - // We have notifications, go to locked shade. - mStatusBar.goToLockedShade(startingChild); - if (startingChild instanceof ExpandableNotificationRow) { - ExpandableNotificationRow row = (ExpandableNotificationRow) startingChild; - row.onExpandedByGesture(true /* drag down is always an open */); - } - return true; - } else { - // abort gesture. - return false; - } - } - - @Override - @ShadeViewRefactor(RefactorComponent.INPUT) - public void onDragDownReset() { - setDimmed(true /* dimmed */, true /* animated */); - resetScrollPosition(); - resetCheckSnoozeLeavebehind(); - } - - @Override - @ShadeViewRefactor(RefactorComponent.INPUT) - public void onCrossedThreshold(boolean above) { - setDimmed(!above /* dimmed */, true /* animate */); - } - - @Override - @ShadeViewRefactor(RefactorComponent.INPUT) - public void onTouchSlopExceeded() { - cancelLongPress(); - checkSnoozeLeavebehind(); - } - - @Override - @ShadeViewRefactor(RefactorComponent.INPUT) - public void setEmptyDragAmount(float amount) { - mNotificationPanel.setEmptyDragAmount(amount); - } - - @Override - @ShadeViewRefactor(RefactorComponent.INPUT) - public boolean isFalsingCheckNeeded() { - return mStatusBarState == StatusBarState.KEYGUARD; - } - - @ShadeViewRefactor(RefactorComponent.INPUT) + @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) public void updateSpeedBumpIndex() { int speedBumpIndex = 0; int currentIndex = 0; @@ -5269,30 +5123,6 @@ public class NotificationStackScrollLayout extends ViewGroup mSwipeHelper.resetExposedMenuView(animate, force); } - - @ShadeViewRefactor(RefactorComponent.INPUT) - public void closeControlsIfOutsideTouch(MotionEvent ev) { - NotificationGuts guts = mNotificationGutsManager.getExposedGuts(); - NotificationMenuRowPlugin menuRow = mSwipeHelper.getCurrentMenuRow(); - View translatingParentView = mSwipeHelper.getTranslatingParentView(); - View view = null; - if (guts != null && !guts.getGutsContent().isLeavebehind()) { - // Only close visible guts if they're not a leavebehind. - view = guts; - } else if (menuRow != null && menuRow.isMenuVisible() - && translatingParentView != null) { - // Checking menu - view = translatingParentView; - } - if (view != null && !NotificationSwipeHelper.isTouchInView(ev, view)) { - // Touch was outside visible guts / menu notification, close what's visible - mNotificationGutsManager.closeAndSaveGuts(false /* removeLeavebehind */, - false /* force */, true /* removeControls */, -1 /* x */, -1 /* y */, - false /* resetMenu */); - resetExposedMenuView(true /* animate */, true /* force */); - } - } - @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER) static class AnimationEvent { @@ -5614,9 +5444,9 @@ public class NotificationStackScrollLayout extends ViewGroup } }; - class NotificationMenuListener implements NotificationMenuRowPlugin.OnMenuEventListener { + @ShadeViewRefactor(RefactorComponent.INPUT) + private final OnMenuEventListener mMenuEventListener = new OnMenuEventListener() { @Override - @ShadeViewRefactor(RefactorComponent.INPUT) public void onMenuClicked(View view, int x, int y, MenuItem item) { if (mLongPressListener == null) { return; @@ -5630,7 +5460,6 @@ public class NotificationStackScrollLayout extends ViewGroup } @Override - @ShadeViewRefactor(RefactorComponent.INPUT) public void onMenuReset(View row) { View translatingParentView = mSwipeHelper.getTranslatingParentView(); if (translatingParentView != null && row == translatingParentView) { @@ -5640,7 +5469,6 @@ public class NotificationStackScrollLayout extends ViewGroup } @Override - @ShadeViewRefactor(RefactorComponent.INPUT) public void onMenuShown(View row) { if (row instanceof ExpandableNotificationRow) { MetricsLogger.action(mContext, MetricsEvent.ACTION_REVEAL_GEAR, @@ -5649,9 +5477,11 @@ public class NotificationStackScrollLayout extends ViewGroup } mSwipeHelper.onMenuShown(row); } - } + }; - class SwipeHelperCallback implements NotificationSwipeHelper.NotificationCallback { + @ShadeViewRefactor(RefactorComponent.INPUT) + private final NotificationSwipeHelper.NotificationCallback mNotificationCallback = + new NotificationSwipeHelper.NotificationCallback() { @Override public void onDismiss() { mNotificationGutsManager.closeAndSaveGuts(true /* removeLeavebehind */, @@ -5671,10 +5501,8 @@ public class NotificationStackScrollLayout extends ViewGroup } @Override - @ShadeViewRefactor(RefactorComponent.INPUT) public void onDragCancelled(View v) { mFalsingManager.onNotificatonStopDismissing(); - setSwipingInProgress(false); } /** @@ -5682,7 +5510,6 @@ public class NotificationStackScrollLayout extends ViewGroup * re-invoking dismiss logic in case the notification has not made its way out yet). */ @Override - @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) public void onChildDismissed(View view) { ExpandableNotificationRow row = (ExpandableNotificationRow) view; if (!row.isDismissed()) { @@ -5701,7 +5528,6 @@ public class NotificationStackScrollLayout extends ViewGroup * @param view view (e.g. notification) to dismiss from the layout */ - @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) public void handleChildViewDismissed(View view) { if (mDismissAllInProgress) { return; @@ -5709,7 +5535,6 @@ public class NotificationStackScrollLayout extends ViewGroup boolean isBlockingHelperShown = false; - setSwipingInProgress(false); if (mDragAnimPendingChildren.contains(view)) { // We start the swipe and finish it in the same frame; we don't want a drag // animation. @@ -5743,13 +5568,11 @@ public class NotificationStackScrollLayout extends ViewGroup } @Override - @ShadeViewRefactor(RefactorComponent.INPUT) public boolean isAntiFalsingNeeded() { return onKeyguard(); } @Override - @ShadeViewRefactor(RefactorComponent.INPUT) public View getChildAtPosition(MotionEvent ev) { View child = NotificationStackScrollLayout.this.getChildAtPosition(ev.getX(), ev.getY()); @@ -5772,10 +5595,8 @@ public class NotificationStackScrollLayout extends ViewGroup } @Override - @ShadeViewRefactor(RefactorComponent.INPUT) public void onBeginDrag(View v) { mFalsingManager.onNotificatonStartDismissing(); - setSwipingInProgress(true); mAmbientState.onBeginDrag(v); updateContinuousShadowDrawing(); if (mAnimationsEnabled && (mIsExpanded || !isPinnedHeadsUp(v))) { @@ -5786,7 +5607,6 @@ public class NotificationStackScrollLayout extends ViewGroup } @Override - @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) public void onChildSnappedBack(View animView, float targetLeft) { mAmbientState.onDragFinished(animView); updateContinuousShadowDrawing(); @@ -5808,7 +5628,6 @@ public class NotificationStackScrollLayout extends ViewGroup } @Override - @ShadeViewRefactor(RefactorComponent.INPUT) public boolean updateSwipeProgress(View animView, boolean dismissable, float swipeProgress) { // Returning true prevents alpha fading. @@ -5816,7 +5635,6 @@ public class NotificationStackScrollLayout extends ViewGroup } @Override - @ShadeViewRefactor(RefactorComponent.INPUT) public float getFalsingThresholdFactor() { return mStatusBar.isWakeUpComingFromTouch() ? 1.5f : 1.0f; } @@ -5825,5 +5643,197 @@ public class NotificationStackScrollLayout extends ViewGroup public boolean canChildBeDismissed(View v) { return NotificationStackScrollLayout.this.canChildBeDismissed(v); } + }; + + // ---------------------- DragDownHelper.OnDragDownListener ------------------------------------ + + @ShadeViewRefactor(RefactorComponent.INPUT) + private final DragDownCallback mDragDownCallback = new DragDownCallback() { + + /* Only ever called as a consequence of a lockscreen expansion gesture. */ + @Override + public boolean onDraggedDown(View startingChild, int dragLengthY) { + if (mStatusBarState == StatusBarState.KEYGUARD + && hasActiveNotifications() && (!mStatusBar.isDozing() + || mStatusBar.isPulsing())) { + mLockscreenGestureLogger.write( + MetricsEvent.ACTION_LS_SHADE, + (int) (dragLengthY / mDisplayMetrics.density), + 0 /* velocityDp - N/A */); + + // We have notifications, go to locked shade. + mStatusBar.goToLockedShade(startingChild); + if (startingChild instanceof ExpandableNotificationRow) { + ExpandableNotificationRow row = (ExpandableNotificationRow) startingChild; + row.onExpandedByGesture(true /* drag down is always an open */); + } + return true; + } else { + // abort gesture. + return false; + } + } + + @Override + public void onDragDownReset() { + setDimmed(true /* dimmed */, true /* animated */); + resetScrollPosition(); + resetCheckSnoozeLeavebehind(); + } + + @Override + public void onCrossedThreshold(boolean above) { + setDimmed(!above /* dimmed */, true /* animate */); + } + + @Override + public void onTouchSlopExceeded() { + cancelLongPress(); + checkSnoozeLeavebehind(); + } + + @Override + public void setEmptyDragAmount(float amount) { + mNotificationPanel.setEmptyDragAmount(amount); + } + + @Override + public boolean isFalsingCheckNeeded() { + return mStatusBarState == StatusBarState.KEYGUARD; + } + }; + + public DragDownCallback getDragDownCallback() { return mDragDownCallback; } + + @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) + private final HeadsUpTouchHelper.Callback mHeadsUpCallback = new HeadsUpTouchHelper.Callback() { + @Override + public ExpandableView getChildAtRawPosition(float touchX, float touchY) { + return NotificationStackScrollLayout.this.getChildAtRawPosition(touchX, touchY); + } + + @Override + public boolean isExpanded() { + return mIsExpanded; + } + + @Override + public Context getContext() { + return mContext; + } + }; + + public HeadsUpTouchHelper.Callback getHeadsUpCallback() { return mHeadsUpCallback; } + + + @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) + private final OnGroupChangeListener mOnGroupChangeListener = new OnGroupChangeListener() { + @Override + public void onGroupExpansionChanged(ExpandableNotificationRow changedRow, boolean expanded) { + boolean animated = !mGroupExpandedForMeasure && mAnimationsEnabled + && (mIsExpanded || changedRow.isPinned()); + if (animated) { + mExpandedGroupView = changedRow; + mNeedsAnimation = true; + } + changedRow.setChildrenExpanded(expanded, animated); + if (!mGroupExpandedForMeasure) { + onHeightChanged(changedRow, false /* needsAnimation */); + } + runAfterAnimationFinished(new Runnable() { + @Override + public void run() { + changedRow.onFinishedExpansionChange(); + } + }); + } + + @Override + public void onGroupCreatedFromChildren(NotificationGroupManager.NotificationGroup group) { + mStatusBar.requestNotificationUpdate(); + } + + @Override + public void onGroupsChanged() { + mStatusBar.requestNotificationUpdate(); + } + }; + + @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) + private ExpandHelper.Callback mExpandHelperCallback = new ExpandHelper.Callback() { + @Override + public ExpandableView getChildAtPosition(float touchX, float touchY) { + return NotificationStackScrollLayout.this.getChildAtPosition(touchX, touchY); + } + + @Override + public ExpandableView getChildAtRawPosition(float touchX, float touchY) { + return NotificationStackScrollLayout.this.getChildAtRawPosition(touchX, touchY); + } + + @Override + public boolean canChildBeExpanded(View v) { + return v instanceof ExpandableNotificationRow + && ((ExpandableNotificationRow) v).isExpandable() + && !((ExpandableNotificationRow) v).areGutsExposed() + && (mIsExpanded || !((ExpandableNotificationRow) v).isPinned()); + } + + /* Only ever called as a consequence of an expansion gesture in the shade. */ + @Override + public void setUserExpandedChild(View v, boolean userExpanded) { + if (v instanceof ExpandableNotificationRow) { + ExpandableNotificationRow row = (ExpandableNotificationRow) v; + if (userExpanded && onKeyguard()) { + // Due to a race when locking the screen while touching, a notification may be + // expanded even after we went back to keyguard. An example of this happens if + // you click in the empty space while expanding a group. + + // We also need to un-user lock it here, since otherwise the content height + // calculated might be wrong. We also can't invert the two calls since + // un-userlocking it will trigger a layout switch in the content view. + row.setUserLocked(false); + updateContentHeight(); + notifyHeightChangeListener(row); + return; + } + row.setUserExpanded(userExpanded, true /* allowChildrenExpansion */); + row.onExpandedByGesture(userExpanded); + } + } + + @Override + public void setExpansionCancelled(View v) { + if (v instanceof ExpandableNotificationRow) { + ((ExpandableNotificationRow) v).setGroupExpansionChanging(false); + } + } + + @Override + public void setUserLockedChild(View v, boolean userLocked) { + if (v instanceof ExpandableNotificationRow) { + ((ExpandableNotificationRow) v).setUserLocked(userLocked); + } + cancelLongPress(); + requestDisallowInterceptTouchEvent(true); + } + + @Override + public void expansionStateChanged(boolean isExpanding) { + mExpandingNotification = isExpanding; + if (!mExpandedInThisMotion) { + mMaxScrollAfterExpand = mOwnScrollY; + mExpandedInThisMotion = true; + } + } + + @Override + public int getMaxExpandHeight(ExpandableView view) { + return view.getMaxContentHeight(); + } + }; + + public ExpandHelper.Callback getExpandHelperCallback() { + return mExpandHelperCallback; } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java index 028957d233ff..599da3b280be 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java @@ -31,11 +31,9 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.systemui.SwipeHelper; import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin; import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper; -import com.android.systemui.statusbar.notification.ShadeViewRefactor; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.ExpandableView; -@ShadeViewRefactor(ShadeViewRefactor.RefactorComponent.INPUT) class NotificationSwipeHelper extends SwipeHelper implements NotificationSwipeActionHelper { @VisibleForTesting @@ -229,6 +227,7 @@ class NotificationSwipeHelper extends SwipeHelper if (mCallback.isExpanded()) { // We don't want to quick-dismiss when it's a heads up as this might lead to closing // of the panel early. + mSwipingInProgress = false; mCallback.handleChildViewDismissed(view); } mCallback.onDismiss(); @@ -248,6 +247,7 @@ class NotificationSwipeHelper extends SwipeHelper @Override public void snapChild(final View animView, final float targetLeft, float velocity) { superSnapChild(animView, targetLeft, velocity); + mSwipingInProgress = false; mCallback.onDragCancelled(animView); if (targetLeft == 0) { handleMenuCoveredOrDismissed(); @@ -354,6 +354,7 @@ class NotificationSwipeHelper extends SwipeHelper public void onMenuShown(View animView) { setExposedMenuView(getTranslatingParentView()); + mSwipingInProgress = false; mCallback.onDragCancelled(animView); Handler handler = getHandler(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java index 4df1e3bda1a5..e4a5caaf7d71 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java @@ -32,7 +32,7 @@ import com.android.systemui.statusbar.notification.stack.NotificationStackScroll public class HeadsUpTouchHelper implements Gefingerpoken { private HeadsUpManagerPhone mHeadsUpManager; - private NotificationStackScrollLayout mStackScroller; + private Callback mCallback; private int mTrackingPointer; private float mTouchSlop; private float mInitialTouchX; @@ -44,12 +44,12 @@ public class HeadsUpTouchHelper implements Gefingerpoken { private ExpandableNotificationRow mPickedChild; public HeadsUpTouchHelper(HeadsUpManagerPhone headsUpManager, - NotificationStackScrollLayout stackScroller, + Callback callback, NotificationPanelView notificationPanelView) { mHeadsUpManager = headsUpManager; - mStackScroller = stackScroller; + mCallback = callback; mPanel = notificationPanelView; - Context context = stackScroller.getContext(); + Context context = mCallback.getContext(); final ViewConfiguration configuration = ViewConfiguration.get(context); mTouchSlop = configuration.getScaledTouchSlop(); } @@ -75,13 +75,13 @@ public class HeadsUpTouchHelper implements Gefingerpoken { mInitialTouchY = y; mInitialTouchX = x; setTrackingHeadsUp(false); - ExpandableView child = mStackScroller.getChildAtRawPosition(x, y); + ExpandableView child = mCallback.getChildAtRawPosition(x, y); mTouchingHeadsUpView = false; if (child instanceof ExpandableNotificationRow) { mPickedChild = (ExpandableNotificationRow) child; - mTouchingHeadsUpView = !mStackScroller.isExpanded() + mTouchingHeadsUpView = !mCallback.isExpanded() && mPickedChild.isHeadsUp() && mPickedChild.isPinned(); - } else if (child == null && !mStackScroller.isExpanded()) { + } else if (child == null && !mCallback.isExpanded()) { // We might touch above the visible heads up child, but then we still would // like to capture it. NotificationData.Entry topEntry = mHeadsUpManager.getTopEntry(); @@ -174,4 +174,10 @@ public class HeadsUpTouchHelper implements Gefingerpoken { mPickedChild = null; mTouchingHeadsUpView = false; } + + public interface Callback { + ExpandableView getChildAtRawPosition(float touchX, float touchY); + boolean isExpanded(); + Context getContext(); + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java index 6d53cd373d05..75077029c16b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java @@ -2521,8 +2521,8 @@ public class NotificationPanelView extends PanelView implements @Override public void setHeadsUpManager(HeadsUpManagerPhone headsUpManager) { super.setHeadsUpManager(headsUpManager); - mHeadsUpTouchHelper = new HeadsUpTouchHelper(headsUpManager, mNotificationStackScroller, - this); + mHeadsUpTouchHelper = new HeadsUpTouchHelper(headsUpManager, + mNotificationStackScroller.getHeadsUpCallback(), this); } public void setTrackedHeadsUp(ExpandableNotificationRow pickedChild) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java index 45b32c7abae0..ad9b9b30fafc 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java @@ -58,6 +58,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.view.FloatingActionMode; import com.android.internal.widget.FloatingToolbar; import com.android.systemui.Dependency; +import com.android.systemui.ExpandHelper; import com.android.systemui.R; import com.android.systemui.classifier.FalsingManager; import com.android.systemui.statusbar.DragDownHelper; @@ -182,6 +183,11 @@ public class StatusBarWindowView extends FrameLayout { } } + @VisibleForTesting + protected NotificationStackScrollLayout getStackScrollLayout() { + return mStackScrollLayout; + } + @Override public FrameLayout.LayoutParams generateLayoutParams(AttributeSet attrs) { return new LayoutParams(getContext(), attrs); @@ -215,8 +221,11 @@ public class StatusBarWindowView extends FrameLayout { public void setService(StatusBar service) { mService = service; - setDragDownHelper(new DragDownHelper(getContext(), this, mStackScrollLayout, - mStackScrollLayout)); + NotificationStackScrollLayout stackScrollLayout = getStackScrollLayout(); + ExpandHelper.Callback expandHelperCallback = stackScrollLayout.getExpandHelperCallback(); + DragDownHelper.DragDownCallback dragDownCallback = stackScrollLayout.getDragDownCallback(); + setDragDownHelper(new DragDownHelper(getContext(), this, expandHelperCallback, + dragDownCallback)); } @VisibleForTesting @@ -309,7 +318,7 @@ public class StatusBarWindowView extends FrameLayout { } } if (isDown) { - mStackScrollLayout.closeControlsIfOutsideTouch(ev); + getStackScrollLayout().closeControlsIfOutsideTouch(ev); } if (mService.isDozing()) { mService.mDozeScrimController.extendPulse(); @@ -331,13 +340,14 @@ public class StatusBarWindowView extends FrameLayout { @Override public boolean onInterceptTouchEvent(MotionEvent ev) { - if (mService.isDozing() && !mStackScrollLayout.hasPulsingNotifications()) { + NotificationStackScrollLayout stackScrollLayout = getStackScrollLayout(); + if (mService.isDozing() && !stackScrollLayout.hasPulsingNotifications()) { // Capture all touch events in always-on. return true; } boolean intercept = false; if (mNotificationPanel.isFullyExpanded() - && mStackScrollLayout.getVisibility() == View.VISIBLE + && stackScrollLayout.getVisibility() == View.VISIBLE && mStatusBarStateController.getState() == StatusBarState.KEYGUARD && !mService.isBouncerShowing() && !mService.isDozing()) { @@ -349,7 +359,7 @@ public class StatusBarWindowView extends FrameLayout { if (intercept) { MotionEvent cancellation = MotionEvent.obtain(ev); cancellation.setAction(MotionEvent.ACTION_CANCEL); - mStackScrollLayout.onInterceptTouchEvent(cancellation); + stackScrollLayout.onInterceptTouchEvent(cancellation); mNotificationPanel.onInterceptTouchEvent(cancellation); cancellation.recycle(); } @@ -391,8 +401,9 @@ public class StatusBarWindowView extends FrameLayout { } public void cancelExpandHelper() { - if (mStackScrollLayout != null) { - mStackScrollLayout.cancelExpandHelper(); + NotificationStackScrollLayout stackScrollLayout = getStackScrollLayout(); + if (stackScrollLayout != null) { + stackScrollLayout.cancelExpandHelper(); } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java index 15c18e96d709..6b4ccc4a80b5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java @@ -218,6 +218,9 @@ public class NotificationViewHierarchyManagerTest extends SysuiTestCase { public void generateChildOrderChangedEvent() {} @Override + public void onReset(ExpandableView view) {} + + @Override public int getContainerChildCount() { return mRows.size(); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarWindowViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarWindowViewTest.java index 445a194155bb..46335dc3b5ec 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarWindowViewTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarWindowViewTest.java @@ -17,11 +17,11 @@ package com.android.systemui.statusbar.phone; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.os.SystemClock; -import android.support.test.InstrumentationRegistry; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; import android.view.MotionEvent; @@ -31,6 +31,7 @@ import com.android.systemui.SysuiTestCase; import com.android.systemui.statusbar.DragDownHelper; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.StatusBarStateController; +import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout; import org.junit.Before; import org.junit.Test; @@ -43,11 +44,14 @@ public class StatusBarWindowViewTest extends SysuiTestCase { private StatusBarWindowView mView; private StatusBar mStatusBar; private DragDownHelper mDragDownHelper; + private NotificationStackScrollLayout mStackScrollLayout; @Before public void setUp() { mDependency.injectMockDependency(StatusBarStateController.class); - mView = new StatusBarWindowView(getContext(), null); + mView = spy(new StatusBarWindowView(getContext(), null)); + mStackScrollLayout = mock(NotificationStackScrollLayout.class); + when(mView.getStackScrollLayout()).thenReturn(mStackScrollLayout); mStatusBar = mock(StatusBar.class); mView.setService(mStatusBar); mDragDownHelper = mock(DragDownHelper.class); diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index b750d7959167..1c8d99a538bf 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -35,6 +35,8 @@ import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN; import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED; import static android.net.NetworkCapabilities.TRANSPORT_VPN; +import static android.net.NetworkPolicyManager.RULE_NONE; +import static android.net.NetworkPolicyManager.uidRulesToString; import static android.os.Process.INVALID_UID; import static android.system.OsConstants.IPPROTO_TCP; import static android.system.OsConstants.IPPROTO_UDP; @@ -189,6 +191,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Comparator; +import java.util.ConcurrentModificationException; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -257,6 +260,14 @@ public class ConnectivityService extends IConnectivityManager.Stub @GuardedBy("mVpns") private LockdownVpnTracker mLockdownTracker; + /** + * Stale copy of uid rules provided by NPMS. As long as they are accessed only in internal + * handler thread, they don't need a lock. + */ + private SparseIntArray mUidRules = new SparseIntArray(); + /** Flag indicating if background data is restricted. */ + private boolean mRestrictBackground; + final private Context mContext; // 0 is full bad, 100 is full good private int mDefaultInetConditionPublished = 0; @@ -419,6 +430,16 @@ public class ConnectivityService extends IConnectivityManager.Stub // Handle private DNS validation status updates. private static final int EVENT_PRIVATE_DNS_VALIDATION_UPDATE = 38; + /** + * Used to handle onUidRulesChanged event from NetworkPolicyManagerService. + */ + private static final int EVENT_UID_RULES_CHANGED = 39; + + /** + * Used to handle onRestrictBackgroundChanged event from NetworkPolicyManagerService. + */ + private static final int EVENT_DATA_SAVER_CHANGED = 40; + private static String eventName(int what) { return sMagicDecoderRing.get(what, Integer.toString(what)); } @@ -780,6 +801,9 @@ public class ConnectivityService extends IConnectivityManager.Stub mKeyStore = KeyStore.getInstance(); mTelephonyManager = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE); + // To ensure uid rules are synchronized with Network Policy, register for + // NetworkPolicyManagerService events must happen prior to NetworkPolicyManagerService + // reading existing policy from disk. try { mPolicyManager.registerListener(mPolicyListener); } catch (RemoteException e) { @@ -910,7 +934,8 @@ public class ConnectivityService extends IConnectivityManager.Stub registerPrivateDnsSettingsCallbacks(); } - private Tethering makeTethering() { + @VisibleForTesting + protected Tethering makeTethering() { // TODO: Move other elements into @Overridden getters. final TetheringDependencies deps = new TetheringDependencies() { @Override @@ -1116,11 +1141,6 @@ public class ConnectivityService extends IConnectivityManager.Stub if (ignoreBlocked) { return false; } - // Networks are never blocked for system services - // TODO: consider moving this check to NetworkPolicyManagerInternal.isUidNetworkingBlocked. - if (isSystem(uid)) { - return false; - } synchronized (mVpns) { final Vpn vpn = mVpns.get(UserHandle.getUserId(uid)); if (vpn != null && vpn.isBlockingUid(uid)) { @@ -1150,6 +1170,17 @@ public class ConnectivityService extends IConnectivityManager.Stub mNetworkInfoBlockingLogs.log(action + " " + uid); } + private void maybeLogBlockedStatusChanged(NetworkRequestInfo nri, Network net, + boolean blocked) { + if (nri == null || net == null || !LOGD_BLOCKED_NETWORKINFO) { + return; + } + String action = blocked ? "BLOCKED" : "UNBLOCKED"; + log(String.format("Blocked status changed to %s for %d(%d) on netId %d", blocked, + nri.mUid, nri.request.requestId, net.netId)); + mNetworkInfoBlockingLogs.log(action + " " + nri.mUid); + } + /** * Apply any relevant filters to {@link NetworkState} for the given UID. For * example, this may mark the network as {@link DetailedState#BLOCKED} based @@ -1651,10 +1682,17 @@ public class ConnectivityService extends IConnectivityManager.Stub private final INetworkPolicyListener mPolicyListener = new NetworkPolicyManager.Listener() { @Override public void onUidRulesChanged(int uid, int uidRules) { - // TODO: notify UID when it has requested targeted updates + mHandler.sendMessage(mHandler.obtainMessage(EVENT_UID_RULES_CHANGED, uid, uidRules)); } @Override public void onRestrictBackgroundChanged(boolean restrictBackground) { + // caller is NPMS, since we only register with them + if (LOGD_BLOCKED_NETWORKINFO) { + log("onRestrictBackgroundChanged(restrictBackground=" + restrictBackground + ")"); + } + mHandler.sendMessage(mHandler.obtainMessage( + EVENT_DATA_SAVER_CHANGED, restrictBackground ? 1 : 0, 0)); + // TODO: relocate this specific callback in Tethering. if (restrictBackground) { log("onRestrictBackgroundChanged(true): disabling tethering"); @@ -1663,6 +1701,50 @@ public class ConnectivityService extends IConnectivityManager.Stub } }; + void handleUidRulesChanged(int uid, int newRules) { + // skip update when we've already applied rules + final int oldRules = mUidRules.get(uid, RULE_NONE); + if (oldRules == newRules) return; + + maybeNotifyNetworkBlockedForNewUidRules(uid, newRules); + + if (newRules == RULE_NONE) { + mUidRules.delete(uid); + } else { + mUidRules.put(uid, newRules); + } + } + + void handleRestrictBackgroundChanged(boolean restrictBackground) { + if (mRestrictBackground == restrictBackground) return; + + for (final NetworkAgentInfo nai : mNetworkAgentInfos.values()) { + final boolean curMetered = nai.networkCapabilities.isMetered(); + maybeNotifyNetworkBlocked(nai, curMetered, curMetered, mRestrictBackground, + restrictBackground); + } + + mRestrictBackground = restrictBackground; + } + + private boolean isUidNetworkingWithVpnBlocked(int uid, int uidRules, boolean isNetworkMetered, + boolean isBackgroundRestricted) { + synchronized (mVpns) { + final Vpn vpn = mVpns.get(UserHandle.getUserId(uid)); + // Because the return value of this function depends on the list of UIDs the + // always-on VPN blocks when in lockdown mode, when the always-on VPN changes that + // list all state depending on the return value of this function has to be recomputed. + // TODO: add a trigger when the always-on VPN sets its blocked UIDs to reevaluate and + // send the necessary onBlockedStatusChanged callbacks. + if (vpn != null && vpn.isBlockingUid(uid)) { + return true; + } + } + + return mPolicyManagerInternal.isUidNetworkingBlocked(uid, uidRules, + isNetworkMetered, isBackgroundRestricted); + } + /** * Require that the caller is either in the same user or has appropriate permission to interact * across users. @@ -2118,6 +2200,28 @@ public class ConnectivityService extends IConnectivityManager.Stub pw.decreaseIndent(); pw.println(); + pw.print("Restrict background: "); + pw.println(mRestrictBackground); + pw.println(); + + pw.println("Status for known UIDs:"); + pw.increaseIndent(); + final int size = mUidRules.size(); + for (int i = 0; i < size; i++) { + // Don't crash if the array is modified while dumping in bugreports. + try { + final int uid = mUidRules.keyAt(i); + final int uidRules = mUidRules.get(uid, RULE_NONE); + pw.println("UID=" + uid + " rules=" + uidRulesToString(uidRules)); + } catch (ArrayIndexOutOfBoundsException e) { + pw.println(" ArrayIndexOutOfBoundsException"); + } catch (ConcurrentModificationException e) { + pw.println(" ConcurrentModificationException"); + } + } + pw.println(); + pw.decreaseIndent(); + pw.println("Network Requests:"); pw.increaseIndent(); dumpNetworkRequests(pw); @@ -3195,6 +3299,12 @@ public class ConnectivityService extends IConnectivityManager.Stub handlePrivateDnsValidationUpdate( (PrivateDnsValidationUpdate) msg.obj); break; + case EVENT_UID_RULES_CHANGED: + handleUidRulesChanged(msg.arg1, msg.arg2); + break; + case EVENT_DATA_SAVER_CHANGED: + handleRestrictBackgroundChanged(toBool(msg.arg1)); + break; } } } @@ -3783,6 +3893,8 @@ public class ConnectivityService extends IConnectivityManager.Stub private void setLockdownTracker(LockdownVpnTracker tracker) { // Shutdown any existing tracker final LockdownVpnTracker existing = mLockdownTracker; + // TODO: Add a trigger when the always-on VPN enable/disable to reevaluate and send the + // necessary onBlockedStatusChanged callbacks. mLockdownTracker = null; if (existing != null) { existing.shutdown(); @@ -4893,12 +5005,20 @@ public class ConnectivityService extends IConnectivityManager.Stub notifyNetworkCallbacks(nai, ConnectivityManager.CALLBACK_CAP_CHANGED); } - // Report changes that are interesting for network statistics tracking. if (prevNc != null) { - final boolean meteredChanged = prevNc.hasCapability(NET_CAPABILITY_NOT_METERED) != - newNc.hasCapability(NET_CAPABILITY_NOT_METERED); + final boolean oldMetered = prevNc.isMetered(); + final boolean newMetered = newNc.isMetered(); + final boolean meteredChanged = oldMetered != newMetered; + + if (meteredChanged) { + maybeNotifyNetworkBlocked(nai, oldMetered, newMetered, mRestrictBackground, + mRestrictBackground); + } + final boolean roamingChanged = prevNc.hasCapability(NET_CAPABILITY_NOT_ROAMING) != newNc.hasCapability(NET_CAPABILITY_NOT_ROAMING); + + // Report changes that are interesting for network statistics tracking. if (meteredChanged || roamingChanged) { notifyIfacesChangedForNetworkStats(); } @@ -5028,6 +5148,8 @@ public class ConnectivityService extends IConnectivityManager.Stub case ConnectivityManager.CALLBACK_AVAILABLE: { putParcelable(bundle, new NetworkCapabilities(networkAgent.networkCapabilities)); putParcelable(bundle, new LinkProperties(networkAgent.linkProperties)); + // For this notification, arg1 contains the blocked status. + msg.arg1 = arg1; break; } case ConnectivityManager.CALLBACK_LOSING: { @@ -5045,6 +5167,10 @@ public class ConnectivityService extends IConnectivityManager.Stub putParcelable(bundle, new LinkProperties(networkAgent.linkProperties)); break; } + case ConnectivityManager.CALLBACK_BLK_CHANGED: { + msg.arg1 = arg1; + break; + } } msg.what = notificationType; msg.setData(bundle); @@ -5600,7 +5726,76 @@ public class ConnectivityService extends IConnectivityManager.Stub return; } - callCallbackForRequest(nri, nai, ConnectivityManager.CALLBACK_AVAILABLE, 0); + final boolean metered = nai.networkCapabilities.isMetered(); + final boolean blocked = isUidNetworkingWithVpnBlocked(nri.mUid, mUidRules.get(nri.mUid), + metered, mRestrictBackground); + callCallbackForRequest(nri, nai, ConnectivityManager.CALLBACK_AVAILABLE, blocked ? 1 : 0); + } + + /** + * Notify of the blocked state apps with a registered callback matching a given NAI. + * + * Unlike other callbacks, blocked status is different between each individual uid. So for + * any given nai, all requests need to be considered according to the uid who filed it. + * + * @param nai The target NetworkAgentInfo. + * @param oldMetered True if the previous network capabilities is metered. + * @param newRestrictBackground True if data saver is enabled. + */ + private void maybeNotifyNetworkBlocked(NetworkAgentInfo nai, boolean oldMetered, + boolean newMetered, boolean oldRestrictBackground, boolean newRestrictBackground) { + + for (int i = 0; i < nai.numNetworkRequests(); i++) { + NetworkRequest nr = nai.requestAt(i); + NetworkRequestInfo nri = mNetworkRequests.get(nr); + final int uidRules = mUidRules.get(nri.mUid); + final boolean oldBlocked, newBlocked; + // mVpns lock needs to be hold here to ensure that the active VPN cannot be changed + // between these two calls. + synchronized (mVpns) { + oldBlocked = isUidNetworkingWithVpnBlocked(nri.mUid, uidRules, oldMetered, + oldRestrictBackground); + newBlocked = isUidNetworkingWithVpnBlocked(nri.mUid, uidRules, newMetered, + newRestrictBackground); + } + if (oldBlocked != newBlocked) { + callCallbackForRequest(nri, nai, ConnectivityManager.CALLBACK_BLK_CHANGED, + encodeBool(newBlocked)); + } + } + } + + /** + * Notify apps with a given UID of the new blocked state according to new uid rules. + * @param uid The uid for which the rules changed. + * @param newRules The new rules to apply. + */ + private void maybeNotifyNetworkBlockedForNewUidRules(int uid, int newRules) { + for (final NetworkAgentInfo nai : mNetworkAgentInfos.values()) { + final boolean metered = nai.networkCapabilities.isMetered(); + final boolean oldBlocked, newBlocked; + // TODO: Consider that doze mode or turn on/off battery saver would deliver lots of uid + // rules changed event. And this function actually loop through all connected nai and + // its requests. It seems that mVpns lock will be grabbed frequently in this case. + // Reduce the number of locking or optimize the use of lock are likely needed in future. + synchronized (mVpns) { + oldBlocked = isUidNetworkingWithVpnBlocked( + uid, mUidRules.get(uid), metered, mRestrictBackground); + newBlocked = isUidNetworkingWithVpnBlocked( + uid, newRules, metered, mRestrictBackground); + } + if (oldBlocked == newBlocked) { + return; + } + final int arg = encodeBool(newBlocked); + for (int i = 0; i < nai.numNetworkRequests(); i++) { + NetworkRequest nr = nai.requestAt(i); + NetworkRequestInfo nri = mNetworkRequests.get(nr); + if (nri != null && nri.mUid == uid) { + callCallbackForRequest(nri, nai, ConnectivityManager.CALLBACK_BLK_CHANGED, arg); + } + } + } } private void sendLegacyNetworkBroadcast(NetworkAgentInfo nai, DetailedState state, int type) { diff --git a/services/core/java/com/android/server/WiredAccessoryManager.java b/services/core/java/com/android/server/WiredAccessoryManager.java index fcda83d30f1c..3939bee52aa2 100644 --- a/services/core/java/com/android/server/WiredAccessoryManager.java +++ b/services/core/java/com/android/server/WiredAccessoryManager.java @@ -339,7 +339,8 @@ final class WiredAccessoryManager implements WiredAccessoryCallbacks { Slog.w(TAG, uei.getSwitchStatePath() + " not found while attempting to determine initial switch state"); } catch (Exception e) { - Slog.e(TAG, "" , e); + Slog.e(TAG, "Error while attempting to determine initial switch state for " + + uei.getDevName() , e); } } } diff --git a/services/core/java/com/android/server/net/NetworkPolicyLogger.java b/services/core/java/com/android/server/net/NetworkPolicyLogger.java index 452b699667b7..4f4b6bfdb358 100644 --- a/services/core/java/com/android/server/net/NetworkPolicyLogger.java +++ b/services/core/java/com/android/server/net/NetworkPolicyLogger.java @@ -72,6 +72,7 @@ public class NetworkPolicyLogger { static final int NTWK_ALLOWED_TMP_WHITELIST = 4; static final int NTWK_BLOCKED_BG_RESTRICT = 5; static final int NTWK_ALLOWED_DEFAULT = 6; + static final int NTWK_ALLOWED_SYSTEM = 7; private final LogBuffer mNetworkBlockedBuffer = new LogBuffer(MAX_NETWORK_BLOCKED_LOG_SIZE); private final LogBuffer mUidStateChangeBuffer = new LogBuffer(MAX_LOG_SIZE); diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerInternal.java b/services/core/java/com/android/server/net/NetworkPolicyManagerInternal.java index 61d67b74da18..099671d81a3e 100644 --- a/services/core/java/com/android/server/net/NetworkPolicyManagerInternal.java +++ b/services/core/java/com/android/server/net/NetworkPolicyManagerInternal.java @@ -16,6 +16,8 @@ package com.android.server.net; +import static com.android.server.net.NetworkPolicyManagerService.isUidNetworkingBlockedInternal; + import android.net.Network; import android.net.NetworkTemplate; import android.telephony.SubscriptionPlan; @@ -46,6 +48,28 @@ public abstract class NetworkPolicyManagerInternal { public abstract boolean isUidNetworkingBlocked(int uid, String ifname); /** + * Figure out if networking is blocked for a given set of conditions. + * + * This is used by ConnectivityService via passing stale copies of conditions, so it must not + * take any locks. + * + * @param uid The target uid. + * @param uidRules The uid rules which are obtained from NetworkPolicyManagerService. + * @param isNetworkMetered True if the network is metered. + * @param isBackgroundRestricted True if data saver is enabled. + * + * @return true if networking is blocked for the UID under the specified conditions. + */ + public static boolean isUidNetworkingBlocked(int uid, int uidRules, boolean isNetworkMetered, + boolean isBackgroundRestricted) { + // Log of invoking internal function is disabled because it will be called very + // frequently. And metrics are unlikely needed on this method because the callers are + // external and this method doesn't take any locks or perform expensive operations. + return isUidNetworkingBlockedInternal(uid, uidRules, isNetworkMetered, + isBackgroundRestricted, null); + } + + /** * Informs that an appId has been added or removed from the temp-powersave-whitelist so that * that network rules for that appId can be updated. * diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java index 48e09d744ff4..d7996422870c 100644 --- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java +++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java @@ -99,6 +99,7 @@ import static com.android.internal.util.XmlUtils.writeStringAttribute; import static com.android.server.NetworkManagementService.LIMIT_GLOBAL_ALERT; import static com.android.server.net.NetworkPolicyLogger.NTWK_ALLOWED_DEFAULT; import static com.android.server.net.NetworkPolicyLogger.NTWK_ALLOWED_NON_METERED; +import static com.android.server.net.NetworkPolicyLogger.NTWK_ALLOWED_SYSTEM; import static com.android.server.net.NetworkPolicyLogger.NTWK_ALLOWED_TMP_WHITELIST; import static com.android.server.net.NetworkPolicyLogger.NTWK_ALLOWED_WHITELIST; import static com.android.server.net.NetworkPolicyLogger.NTWK_BLOCKED_BG_RESTRICT; @@ -4837,46 +4838,75 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { final long startTime = mStatLogger.getTime(); mContext.enforceCallingOrSelfPermission(MANAGE_NETWORK_POLICY, TAG); - final boolean ret = isUidNetworkingBlockedInternal(uid, isNetworkMetered); + final int uidRules; + final boolean isBackgroundRestricted; + synchronized (mUidRulesFirstLock) { + uidRules = mUidRules.get(uid, RULE_NONE); + isBackgroundRestricted = mRestrictBackground; + } + final boolean ret = isUidNetworkingBlockedInternal(uid, uidRules, isNetworkMetered, + isBackgroundRestricted, mLogger); mStatLogger.logDurationStat(Stats.IS_UID_NETWORKING_BLOCKED, startTime); return ret; } - private boolean isUidNetworkingBlockedInternal(int uid, boolean isNetworkMetered) { - final int uidRules; - final boolean isBackgroundRestricted; - synchronized (mUidRulesFirstLock) { - uidRules = mUidRules.get(uid, RULE_NONE); - isBackgroundRestricted = mRestrictBackground; + private static boolean isSystem(int uid) { + return uid < Process.FIRST_APPLICATION_UID; + } + + static boolean isUidNetworkingBlockedInternal(int uid, int uidRules, boolean isNetworkMetered, + boolean isBackgroundRestricted, @Nullable NetworkPolicyLogger logger) { + final int reason; + // Networks are never blocked for system components + if (isSystem(uid)) { + reason = NTWK_ALLOWED_SYSTEM; } - if (hasRule(uidRules, RULE_REJECT_ALL)) { - mLogger.networkBlocked(uid, NTWK_BLOCKED_POWER); - return true; + else if (hasRule(uidRules, RULE_REJECT_ALL)) { + reason = NTWK_BLOCKED_POWER; } - if (!isNetworkMetered) { - mLogger.networkBlocked(uid, NTWK_ALLOWED_NON_METERED); - return false; + else if (!isNetworkMetered) { + reason = NTWK_ALLOWED_NON_METERED; } - if (hasRule(uidRules, RULE_REJECT_METERED)) { - mLogger.networkBlocked(uid, NTWK_BLOCKED_BLACKLIST); - return true; + else if (hasRule(uidRules, RULE_REJECT_METERED)) { + reason = NTWK_BLOCKED_BLACKLIST; } - if (hasRule(uidRules, RULE_ALLOW_METERED)) { - mLogger.networkBlocked(uid, NTWK_ALLOWED_WHITELIST); - return false; + else if (hasRule(uidRules, RULE_ALLOW_METERED)) { + reason = NTWK_ALLOWED_WHITELIST; } - if (hasRule(uidRules, RULE_TEMPORARY_ALLOW_METERED)) { - mLogger.networkBlocked(uid, NTWK_ALLOWED_TMP_WHITELIST); - return false; + else if (hasRule(uidRules, RULE_TEMPORARY_ALLOW_METERED)) { + reason = NTWK_ALLOWED_TMP_WHITELIST; } - if (isBackgroundRestricted) { - mLogger.networkBlocked(uid, NTWK_BLOCKED_BG_RESTRICT); - return true; + else if (isBackgroundRestricted) { + reason = NTWK_BLOCKED_BG_RESTRICT; } - mLogger.networkBlocked(uid, NTWK_ALLOWED_DEFAULT); - return false; + else { + reason = NTWK_ALLOWED_DEFAULT; + } + + final boolean blocked; + switch(reason) { + case NTWK_ALLOWED_DEFAULT: + case NTWK_ALLOWED_NON_METERED: + case NTWK_ALLOWED_TMP_WHITELIST: + case NTWK_ALLOWED_WHITELIST: + case NTWK_ALLOWED_SYSTEM: + blocked = false; + break; + case NTWK_BLOCKED_POWER: + case NTWK_BLOCKED_BLACKLIST: + case NTWK_BLOCKED_BG_RESTRICT: + blocked = true; + break; + default: + throw new IllegalArgumentException(); + } + if (logger != null) { + logger.networkBlocked(uid, reason); + } + + return blocked; } private class NetworkPolicyManagerInternalImpl extends NetworkPolicyManagerInternal { @@ -4918,11 +4948,18 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { public boolean isUidNetworkingBlocked(int uid, String ifname) { final long startTime = mStatLogger.getTime(); + final int uidRules; + final boolean isBackgroundRestricted; + synchronized (mUidRulesFirstLock) { + uidRules = mUidRules.get(uid, RULE_NONE); + isBackgroundRestricted = mRestrictBackground; + } final boolean isNetworkMetered; synchronized (mNetworkPoliciesSecondLock) { isNetworkMetered = mMeteredIfaces.contains(ifname); } - final boolean ret = isUidNetworkingBlockedInternal(uid, isNetworkMetered); + final boolean ret = isUidNetworkingBlockedInternal(uid, uidRules, isNetworkMetered, + isBackgroundRestricted, mLogger); mStatLogger.logDurationStat(Stats.IS_UID_NETWORKING_BLOCKED, startTime); diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java index b06be1a9a11e..2dbbf55a9347 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java @@ -70,7 +70,8 @@ abstract class BaseIDevicePolicyManager extends IDevicePolicyManager.Stub { } @Override - public boolean checkDeviceIdentifierAccess(String packageName, int userHandle) { + public boolean checkDeviceIdentifierAccess(String packageName, int userHandle, int pid, + int uid) { return false; } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 70cdba28ac83..26ea152761fb 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -7871,7 +7871,21 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } @Override - public boolean checkDeviceIdentifierAccess(String packageName, int userHandle) { + public boolean checkDeviceIdentifierAccess(String packageName, int userHandle, int pid, + int uid) { + // If the caller is not a system app then it should only be able to check its own device + // identifier access. + int callingAppId = UserHandle.getAppId(mInjector.binderGetCallingUid()); + if (callingAppId >= Process.FIRST_APPLICATION_UID + && callingAppId != UserHandle.getAppId(uid)) { + return false; + } + // A device or profile owner must also have the READ_PHONE_STATE permission to access device + // identifiers. If the package being checked does not have this permission then deny access. + if (mContext.checkPermission(android.Manifest.permission.READ_PHONE_STATE, pid, uid) + != PackageManager.PERMISSION_GRANTED) { + return false; + } // Allow access to the device owner. ComponentName deviceOwner = getDeviceOwnerComponent(true); if (deviceOwner != null && deviceOwner.getPackageName().equals(packageName)) { diff --git a/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java index 1eb88baafa48..113ee2df768e 100644 --- a/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java @@ -26,13 +26,21 @@ import static android.net.NetworkPolicy.WARNING_DISABLED; import static android.net.NetworkPolicyManager.POLICY_ALLOW_METERED_BACKGROUND; import static android.net.NetworkPolicyManager.POLICY_NONE; import static android.net.NetworkPolicyManager.POLICY_REJECT_METERED_BACKGROUND; +import static android.net.NetworkPolicyManager.RULE_ALLOW_ALL; +import static android.net.NetworkPolicyManager.RULE_ALLOW_METERED; +import static android.net.NetworkPolicyManager.RULE_NONE; +import static android.net.NetworkPolicyManager.RULE_REJECT_ALL; +import static android.net.NetworkPolicyManager.RULE_REJECT_METERED; +import static android.net.NetworkPolicyManager.RULE_TEMPORARY_ALLOW_METERED; import static android.net.NetworkPolicyManager.uidPoliciesToString; +import static android.net.NetworkPolicyManager.uidRulesToString; import static android.net.NetworkStats.IFACE_ALL; import static android.net.NetworkStats.SET_ALL; import static android.net.NetworkStats.TAG_ALL; import static android.net.NetworkTemplate.buildTemplateMobileAll; import static android.net.NetworkTemplate.buildTemplateWifi; import static android.net.TrafficStats.MB_IN_BYTES; +import static android.os.Process.SYSTEM_UID; import static android.telephony.CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED; import static android.telephony.CarrierConfigManager.DATA_CYCLE_THRESHOLD_DISABLED; import static android.telephony.CarrierConfigManager.DATA_CYCLE_USE_PLATFORM_DEFAULT; @@ -124,6 +132,7 @@ import android.text.TextUtils; import android.text.format.Time; import android.util.DataUnit; import android.util.Log; +import android.util.Pair; import android.util.Range; import android.util.RecurrenceRule; @@ -171,6 +180,7 @@ import java.time.Period; import java.time.ZoneId; import java.time.ZoneOffset; import java.time.ZonedDateTime; +import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; import java.util.Iterator; @@ -1644,6 +1654,76 @@ public class NetworkPolicyManagerServiceTest { true); } + /** + * Exhaustively test isUidNetworkingBlocked to output the expected results based on external + * conditions. + */ + @Test + public void testIsUidNetworkingBlocked() { + final ArrayList<Pair<Boolean, Integer>> expectedBlockedStates = new ArrayList<>(); + + // Metered network. Data saver on. + expectedBlockedStates.add(new Pair<>(true, RULE_NONE)); + expectedBlockedStates.add(new Pair<>(false, RULE_ALLOW_METERED)); + expectedBlockedStates.add(new Pair<>(false, RULE_TEMPORARY_ALLOW_METERED)); + expectedBlockedStates.add(new Pair<>(true, RULE_REJECT_METERED)); + expectedBlockedStates.add(new Pair<>(true, RULE_ALLOW_ALL)); + expectedBlockedStates.add(new Pair<>(true, RULE_REJECT_ALL)); + verifyNetworkBlockedState( + true /* metered */, true /* backgroundRestricted */, expectedBlockedStates); + expectedBlockedStates.clear(); + + // Metered network. Data saver off. + expectedBlockedStates.add(new Pair<>(false, RULE_NONE)); + expectedBlockedStates.add(new Pair<>(false, RULE_ALLOW_METERED)); + expectedBlockedStates.add(new Pair<>(false, RULE_TEMPORARY_ALLOW_METERED)); + expectedBlockedStates.add(new Pair<>(true, RULE_REJECT_METERED)); + expectedBlockedStates.add(new Pair<>(false, RULE_ALLOW_ALL)); + expectedBlockedStates.add(new Pair<>(true, RULE_REJECT_ALL)); + verifyNetworkBlockedState( + true /* metered */, false /* backgroundRestricted */, expectedBlockedStates); + expectedBlockedStates.clear(); + + // Non-metered network. Data saver on. + expectedBlockedStates.add(new Pair<>(false, RULE_NONE)); + expectedBlockedStates.add(new Pair<>(false, RULE_ALLOW_METERED)); + expectedBlockedStates.add(new Pair<>(false, RULE_TEMPORARY_ALLOW_METERED)); + expectedBlockedStates.add(new Pair<>(false, RULE_REJECT_METERED)); + expectedBlockedStates.add(new Pair<>(false, RULE_ALLOW_ALL)); + expectedBlockedStates.add(new Pair<>(true, RULE_REJECT_ALL)); + verifyNetworkBlockedState( + false /* metered */, true /* backgroundRestricted */, expectedBlockedStates); + + // Non-metered network. Data saver off. The result is the same as previous case since + // the network is blocked only for RULE_REJECT_ALL regardless of data saver. + verifyNetworkBlockedState( + false /* metered */, false /* backgroundRestricted */, expectedBlockedStates); + expectedBlockedStates.clear(); + } + + private void verifyNetworkBlockedState(boolean metered, boolean backgroundRestricted, + ArrayList<Pair<Boolean, Integer>> expectedBlockedStateForRules) { + final NetworkPolicyManagerInternal npmi = LocalServices + .getService(NetworkPolicyManagerInternal.class); + + for (Pair<Boolean, Integer> pair : expectedBlockedStateForRules) { + final boolean expectedResult = pair.first; + final int rule = pair.second; + assertEquals(formatBlockedStateError(UID_A, rule, metered, backgroundRestricted), + expectedResult, + npmi.isUidNetworkingBlocked(UID_A, rule, metered, backgroundRestricted)); + assertFalse(formatBlockedStateError(SYSTEM_UID, rule, metered, backgroundRestricted), + npmi.isUidNetworkingBlocked(SYSTEM_UID, rule, metered, backgroundRestricted)); + } + } + + private String formatBlockedStateError(int uid, int rule, boolean metered, + boolean backgroundRestricted) { + return String.format( + "Unexpected BlockedState: (uid=%d, rule=%s, metered=%b, backgroundRestricted=%b)", + uid, uidRulesToString(rule), metered, backgroundRestricted); + } + private SubscriptionPlan buildMonthlyDataPlan(ZonedDateTime start, long limitBytes) { return SubscriptionPlan.Builder .createRecurringMonthly(start) diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityManagerTest.java b/services/tests/servicestests/src/com/android/server/am/ActivityManagerTest.java index 63e99c85d1b5..2dfb3751c021 100644 --- a/services/tests/servicestests/src/com/android/server/am/ActivityManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/am/ActivityManagerTest.java @@ -62,14 +62,14 @@ public class ActivityManagerTest { private void testTaskIdsForUser(int userId) throws RemoteException { List<?> recentTasks = service.getRecentTasks(100, 0, userId).getList(); - assertThat(recentTasks).isNotNull(); - assertThat(recentTasks).isNotEmpty(); - for (Object elem : recentTasks) { - assertThat(elem).isInstanceOf(RecentTaskInfo.class); - RecentTaskInfo recentTask = (RecentTaskInfo) elem; - int taskId = recentTask.taskId; - assertEquals("The task id " + taskId + " should not belong to user " + userId, - taskId / UserHandle.PER_USER_RANGE, userId); + if (recentTasks != null) { + for (Object elem : recentTasks) { + assertThat(elem).isInstanceOf(RecentTaskInfo.class); + RecentTaskInfo recentTask = (RecentTaskInfo) elem; + int taskId = recentTask.taskId; + assertEquals("The task id " + taskId + " should not belong to user " + userId, + taskId / UserHandle.PER_USER_RANGE, userId); + } } } } diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java index 3127b3584dd9..d33a537f2194 100644 --- a/telecomm/java/android/telecom/TelecomManager.java +++ b/telecomm/java/android/telecom/TelecomManager.java @@ -676,7 +676,7 @@ public class TelecomManager { /** * @hide */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) + @UnsupportedAppUsage public static TelecomManager from(Context context) { return (TelecomManager) context.getSystemService(Context.TELECOM_SERVICE); } diff --git a/telephony/java/android/provider/Telephony.java b/telephony/java/android/provider/Telephony.java index 1efa906fb407..d9e71679ced8 100644 --- a/telephony/java/android/provider/Telephony.java +++ b/telephony/java/android/provider/Telephony.java @@ -18,6 +18,7 @@ package android.provider; import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; +import android.annotation.SystemApi; import android.annotation.TestApi; import android.annotation.UnsupportedAppUsage; import android.app.job.JobService; @@ -2889,100 +2890,131 @@ public final class Telephony { public static final String SUBSCRIPTION_ID = "sub_id"; /** - * The profile_id to which the APN saved in modem + * The profile_id to which the APN saved in modem. * <p>Type: INTEGER</p> *@hide */ public static final String PROFILE_ID = "profile_id"; /** - * Is the apn setting to be set in modem - * <P>Type: INTEGER (boolean)</P> + * If set to {@code true}, then the APN setting will persist to the modem. + * <p>Type: INTEGER (boolean)</p> *@hide */ + @SystemApi public static final String MODEM_COGNITIVE = "modem_cognitive"; /** - * The max connections of this apn + * The max connections of this APN. * <p>Type: INTEGER</p> *@hide */ + @SystemApi public static final String MAX_CONNS = "max_conns"; /** - * The wait time for retry of the apn + * The wait time for retry of the APN. * <p>Type: INTEGER</p> *@hide */ + @SystemApi public static final String WAIT_TIME = "wait_time"; /** - * The time to limit max connection for the apn + * The time to limit max connection for the APN. * <p>Type: INTEGER</p> *@hide */ + @SystemApi public static final String MAX_CONNS_TIME = "max_conns_time"; /** - * The MTU size of the mobile interface to which the APN connected + * The MTU(Maxinum transmit unit) size of the mobile interface to which the APN connected. * <p>Type: INTEGER </p> * @hide */ + @SystemApi public static final String MTU = "mtu"; /** - * Is this APN added/edited/deleted by a user or carrier? + * APN edit status. APN could be added/edited/deleted by a user or carrier. * <p>Type: INTEGER </p> * @hide */ + @SystemApi public static final String EDITED = "edited"; /** - * Is this APN visible to the user? - * <p>Type: INTEGER (boolean) </p> + * {@code true} if this APN visible to the user, {@code false} otherwise. + * <p>Type: INTEGER (boolean)</p> * @hide */ + @SystemApi public static final String USER_VISIBLE = "user_visible"; /** - * Is the user allowed to edit this APN? - * <p>Type: INTEGER (boolean) </p> + * {@code true} if the user allowed to edit this APN, {@code false} otherwise. + * <p>Type: INTEGER (boolean)</p> * @hide */ + @SystemApi public static final String USER_EDITABLE = "user_editable"; /** - * Following are possible values for the EDITED field + * {@link #EDITED APN edit status} indicates that this APN has not been edited or fails to + * edit. + * <p>Type: INTEGER </p> * @hide */ + @SystemApi public static final int UNEDITED = 0; + /** - * @hide + * {@link #EDITED APN edit status} indicates that this APN has been edited by users. + * <p>Type: INTEGER </p> + * @hide */ + @SystemApi public static final int USER_EDITED = 1; + /** - * @hide + * {@link #EDITED APN edit status} indicates that this APN has been deleted by users. + * <p>Type: INTEGER </p> + * @hide */ + @SystemApi public static final int USER_DELETED = 2; + /** - * DELETED_BUT_PRESENT is an intermediate value used to indicate that an entry deleted - * by the user is still present in the new APN database and therefore must remain tagged - * as user deleted rather than completely removed from the database + * {@link #EDITED APN edit status} is an intermediate value used to indicate that an entry + * deleted by the user is still present in the new APN database and therefore must remain + * tagged as user deleted rather than completely removed from the database. * @hide */ public static final int USER_DELETED_BUT_PRESENT_IN_XML = 3; + /** - * @hide + * {@link #EDITED APN edit status} indicates that this APN has been edited by carriers. + * <p>Type: INTEGER </p> + * @hide */ + @SystemApi public static final int CARRIER_EDITED = 4; + /** - * CARRIER_DELETED values are currently not used as there is no usecase. If they are used, + * {@link #EDITED APN edit status} indicates that this APN has been deleted by carriers. + * CARRIER_DELETED values are currently not used as there is no use case. If they are used, * delete() will have to change accordingly. Currently it is hardcoded to USER_DELETED. + * <p>Type: INTEGER </p> * @hide */ public static final int CARRIER_DELETED = 5; + /** - * @hide + * {@link #EDITED APN edit status} is an intermediate value used to indicate that an entry + * deleted by the carrier is still present in the new APN database and therefore must remain + * tagged as user deleted rather than completely removed from the database. + * @hide */ public static final int CARRIER_DELETED_BUT_PRESENT_IN_XML = 6; @@ -3011,16 +3043,20 @@ public final class Telephony { * The APN set id. When the user manually selects an APN or the framework sets an APN as * preferred, all APNs with the same set id as the selected APN should be prioritized over * APNs in other sets. + * <p>Type: INTEGER</p> * @hide */ + @SystemApi public static final String APN_SET_ID = "apn_set_id"; /** - * Possible value for the APN_SET_ID field. By default APNs will not belong to a set. If the - * user manually selects an APN with no set set, there is no need to prioritize any specific - * APN set ids. + * Possible value for the{@link #APN_SET_ID} field. By default APNs will not belong to a + * set. If the user manually selects an APN with no set set, there is no need to prioritize + * any specific APN set ids. + * <p>Type: INTEGER</p> * @hide */ + @SystemApi public static final int NO_SET_SET = 0; } diff --git a/telephony/java/android/telephony/ServiceState.java b/telephony/java/android/telephony/ServiceState.java index bfbcd5751bf4..e0ec2c50ab5b 100644 --- a/telephony/java/android/telephony/ServiceState.java +++ b/telephony/java/android/telephony/ServiceState.java @@ -21,7 +21,6 @@ import android.annotation.SystemApi; import android.annotation.TestApi; import android.annotation.UnsupportedAppUsage; import android.content.Intent; -import android.os.Build; import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; @@ -247,7 +246,7 @@ public class ServiceState implements Parcelable { private String mDataOperatorAlphaLong; private String mDataOperatorAlphaShort; private String mDataOperatorNumeric; - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) + @UnsupportedAppUsage private boolean mIsManualNetworkSelection; private boolean mIsEmergencyOnly; @@ -257,9 +256,9 @@ public class ServiceState implements Parcelable { @UnsupportedAppUsage private boolean mCssIndicator; - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) + @UnsupportedAppUsage private int mNetworkId; - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) + @UnsupportedAppUsage private int mSystemId; @UnsupportedAppUsage private int mCdmaRoamingIndicator; @@ -457,7 +456,7 @@ public class ServiceState implements Parcelable { * * @hide */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) + @UnsupportedAppUsage public int getVoiceRegState() { return mVoiceRegState; } @@ -472,7 +471,7 @@ public class ServiceState implements Parcelable { * * @hide */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) + @UnsupportedAppUsage public int getDataRegState() { return mDataRegState; } @@ -533,7 +532,7 @@ public class ServiceState implements Parcelable { * @return roaming status * @hide */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) + @UnsupportedAppUsage public boolean getVoiceRoaming() { return getVoiceRoamingType() != ROAMING_TYPE_NOT_ROAMING; } @@ -557,7 +556,7 @@ public class ServiceState implements Parcelable { * @return roaming type * @hide */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) + @UnsupportedAppUsage public boolean getDataRoaming() { return getDataRoamingType() != ROAMING_TYPE_NOT_ROAMING; } diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java index d0c6c49e18d6..3b4016437e9a 100644 --- a/telephony/java/android/telephony/SubscriptionManager.java +++ b/telephony/java/android/telephony/SubscriptionManager.java @@ -1368,7 +1368,7 @@ public class SubscriptionManager { } /** @hide */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) + @UnsupportedAppUsage public static int getPhoneId(int subId) { if (!isValidSubscriptionId(subId)) { if (DBG) { @@ -1664,7 +1664,7 @@ public class SubscriptionManager { * usable subId means its neither a INVALID_SUBSCRIPTION_ID nor a DEFAULT_SUB_ID. * @hide */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) + @UnsupportedAppUsage public static boolean isUsableSubIdValue(int subId) { return subId >= MIN_SUBSCRIPTION_ID_VALUE && subId <= MAX_SUBSCRIPTION_ID_VALUE; } @@ -1682,7 +1682,7 @@ public class SubscriptionManager { } /** @hide */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) + @UnsupportedAppUsage public static void putPhoneIdAndSubIdExtra(Intent intent, int phoneId) { int[] subIds = SubscriptionManager.getSubId(phoneId); if (subIds != null && subIds.length > 0) { diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index 4e391f1fdf46..69901d297f42 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -17,6 +17,7 @@ package android.telephony; import static android.content.Context.TELECOM_SERVICE; + import static com.android.internal.util.Preconditions.checkNotNull; import android.annotation.IntDef; @@ -59,6 +60,7 @@ import android.telephony.ims.aidl.IImsConfig; import android.telephony.ims.aidl.IImsMmTelFeature; import android.telephony.ims.aidl.IImsRcsFeature; import android.telephony.ims.aidl.IImsRegistration; +import android.telephony.ims.feature.MmTelFeature; import android.telephony.ims.stub.ImsRegistrationImplBase; import android.text.TextUtils; import android.util.Log; @@ -230,8 +232,7 @@ public class TelephonyManager { /** @hide /* @deprecated - use getSystemService as described above */ - @Deprecated - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) + @UnsupportedAppUsage public static TelephonyManager getDefault() { return sInstance; } @@ -320,7 +321,7 @@ public class TelephonyManager { } /** {@hide} */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) + @UnsupportedAppUsage public static TelephonyManager from(Context context) { return (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); } @@ -706,7 +707,7 @@ public class TelephonyManager { */ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) @Deprecated - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) + @UnsupportedAppUsage public static final String ACTION_PRECISE_DATA_CONNECTION_STATE_CHANGED = "android.intent.action.PRECISE_DATA_CONNECTION_STATE_CHANGED"; @@ -1310,11 +1311,11 @@ public class TelephonyManager { * Returns the unique device ID, for example, the IMEI for GSM and the MEID * or ESN for CDMA phones. Return null if device ID is not available. * - * <p>Requires Permission: READ_PRIVILEGED_PHONE_STATE or for the calling package to be the - * device or profile owner. The profile owner is an app that owns a managed profile on the - * device; for more details see <a href="https://developer.android.com/work/managed-profiles"> - * Work profiles</a>. Profile owner access is deprecated and will be removed in a future - * release. + * <p>Requires Permission: READ_PRIVILEGED_PHONE_STATE, or for the calling package to be the + * device or profile owner and have the READ_PHONE_STATE permission. The profile owner is an app + * that owns a managed profile on the device; for more details see <a + * href="https://developer.android.com/work/managed-profiles">Work profiles</a>. Profile owner + * access is deprecated and will be removed in a future release. * * @deprecated Use (@link getImei} which returns IMEI for GSM or (@link getMeid} which returns * MEID for CDMA. @@ -1339,11 +1340,11 @@ public class TelephonyManager { * Returns the unique device ID of a subscription, for example, the IMEI for * GSM and the MEID for CDMA phones. Return null if device ID is not available. * - * <p>Requires Permission: READ_PRIVILEGED_PHONE_STATE or for the calling package to be the - * device or profile owner. The profile owner is an app that owns a managed profile on the - * device; for more details see <a href="https://developer.android.com/work/managed-profiles"> - * Work profiles</a>. Profile owner access is deprecated and will be removed in a future - * release. + * <p>Requires Permission: READ_PRIVILEGED_PHONE_STATE, or for the calling package to be the + * device or profile owner and have the READ_PHONE_STATE permission. The profile owner is an app + * that owns a managed profile on the device; for more details see <a + * href="https://developer.android.com/work/managed-profiles">Work profiles</a>. Profile owner + * access is deprecated and will be removed in a future release. * * @param slotIndex of which deviceID is returned * @@ -1371,11 +1372,11 @@ public class TelephonyManager { * Returns the IMEI (International Mobile Equipment Identity). Return null if IMEI is not * available. * - * <p>Requires Permission: READ_PRIVILEGED_PHONE_STATE or for the calling package to be the - * device or profile owner. The profile owner is an app that owns a managed profile on the - * device; for more details see <a href="https://developer.android.com/work/managed-profiles"> - * Work profiles</a>. Profile owner access is deprecated and will be removed in a future - * release. + * <p>Requires Permission: READ_PRIVILEGED_PHONE_STATE, or for the calling package to be the + * device or profile owner and have the READ_PHONE_STATE permission. The profile owner is an app + * that owns a managed profile on the device; for more details see <a + * href="https://developer.android.com/work/managed-profiles">Work profiles</a>. Profile owner + * access is deprecated and will be removed in a future release. */ @SuppressAutoDoc // No support for device / profile owner. @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) @@ -1387,11 +1388,11 @@ public class TelephonyManager { * Returns the IMEI (International Mobile Equipment Identity). Return null if IMEI is not * available. * - * <p>Requires Permission: READ_PRIVILEGED_PHONE_STATE or for the calling package to be the - * device or profile owner. The profile owner is an app that owns a managed profile on the - * device; for more details see <a href="https://developer.android.com/work/managed-profiles"> - * Work profiles</a>. Profile owner access is deprecated and will be removed in a future - * release. + * <p>Requires Permission: READ_PRIVILEGED_PHONE_STATE, or for the calling package to be the + * device or profile owner and have the READ_PHONE_STATE permission. The profile owner is an app + * that owns a managed profile on the device; for more details see <a + * href="https://developer.android.com/work/managed-profiles">Work profiles</a>. Profile owner + * access is deprecated and will be removed in a future release. * * @param slotIndex of which IMEI is returned */ @@ -1440,11 +1441,11 @@ public class TelephonyManager { /** * Returns the MEID (Mobile Equipment Identifier). Return null if MEID is not available. * - * <p>Requires Permission: READ_PRIVILEGED_PHONE_STATE or for the calling package to be the - * device or profile owner. The profile owner is an app that owns a managed profile on the - * device; for more details see <a href="https://developer.android.com/work/managed-profiles"> - * Work profiles</a>. Profile owner access is deprecated and will be removed in a future - * release. + * <p>Requires Permission: READ_PRIVILEGED_PHONE_STATE, or for the calling package to be the + * device or profile owner and have the READ_PHONE_STATE permission. The profile owner is an app + * that owns a managed profile on the device; for more details see <a + * href="https://developer.android.com/work/managed-profiles">Work profiles</a>. Profile owner + * access is deprecated and will be removed in a future release. */ @SuppressAutoDoc // No support for device / profile owner. @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) @@ -1455,11 +1456,11 @@ public class TelephonyManager { /** * Returns the MEID (Mobile Equipment Identifier). Return null if MEID is not available. * - * <p>Requires Permission: READ_PRIVILEGED_PHONE_STATE or for the calling package to be the - * device or profile owner. The profile owner is an app that owns a managed profile on the - * device; for more details see <a href="https://developer.android.com/work/managed-profiles"> - * Work profiles</a>. Profile owner access is deprecated and will be removed in a future - * release. + * <p>Requires Permission: READ_PRIVILEGED_PHONE_STATE, or for the calling package to be the + * device or profile owner and have the READ_PHONE_STATE permission. The profile owner is an app + * that owns a managed profile on the device; for more details see <a + * href="https://developer.android.com/work/managed-profiles">Work profiles</a>. Profile owner + * access is deprecated and will be removed in a future release. * * @param slotIndex of which MEID is returned */ @@ -1764,7 +1765,7 @@ public class TelephonyManager { } /** {@hide} */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) + @UnsupportedAppUsage private int getPhoneTypeFromProperty(int phoneId) { String type = getTelephonyProperty(phoneId, TelephonyProperties.CURRENT_ACTIVE_PHONE, null); @@ -1948,7 +1949,7 @@ public class TelephonyManager { * @param subId * @hide */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) + @UnsupportedAppUsage public String getNetworkOperatorName(int subId) { int phoneId = SubscriptionManager.getPhoneId(subId); return getTelephonyProperty(phoneId, TelephonyProperties.PROPERTY_OPERATOR_ALPHA, ""); @@ -1976,7 +1977,7 @@ public class TelephonyManager { * @param subId * @hide */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) + @UnsupportedAppUsage public String getNetworkOperator(int subId) { int phoneId = SubscriptionManager.getPhoneId(subId); return getNetworkOperatorForPhone(phoneId); @@ -2300,7 +2301,7 @@ public class TelephonyManager { * @hide */ @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) + @UnsupportedAppUsage public int getDataNetworkType(int subId) { try{ ITelephony telephony = getITelephony(); @@ -2336,7 +2337,7 @@ public class TelephonyManager { * @hide */ @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) + @UnsupportedAppUsage public int getVoiceNetworkType(int subId) { try{ ITelephony telephony = getITelephony(); @@ -2819,7 +2820,7 @@ public class TelephonyManager { * @param subId for which SimOperator is returned * @hide */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) + @UnsupportedAppUsage public String getSimOperator(int subId) { return getSimOperatorNumeric(subId); } @@ -2833,7 +2834,7 @@ public class TelephonyManager { * @see #getSimState * @hide */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) + @UnsupportedAppUsage public String getSimOperatorNumeric() { int subId = mSubId; if (!SubscriptionManager.isUsableSubIdValue(subId)) { @@ -2862,7 +2863,7 @@ public class TelephonyManager { * @param subId for which SimOperator is returned * @hide */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) + @UnsupportedAppUsage public String getSimOperatorNumeric(int subId) { int phoneId = SubscriptionManager.getPhoneId(subId); return getSimOperatorNumericForPhone(phoneId); @@ -2876,7 +2877,7 @@ public class TelephonyManager { * @param phoneId for which SimOperator is returned * @hide */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) + @UnsupportedAppUsage public String getSimOperatorNumericForPhone(int phoneId) { return getTelephonyProperty(phoneId, TelephonyProperties.PROPERTY_ICC_OPERATOR_NUMERIC, ""); @@ -2903,7 +2904,7 @@ public class TelephonyManager { * @param subId for which SimOperatorName is returned * @hide */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) + @UnsupportedAppUsage public String getSimOperatorName(int subId) { int phoneId = SubscriptionManager.getPhoneId(subId); return getSimOperatorNameForPhone(phoneId); @@ -2933,7 +2934,7 @@ public class TelephonyManager { * @param subId for which SimCountryIso is returned * @hide */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) + @UnsupportedAppUsage public String getSimCountryIso(int subId) { int phoneId = SubscriptionManager.getPhoneId(subId); return getSimCountryIsoForPhone(phoneId); @@ -2955,11 +2956,11 @@ public class TelephonyManager { * unavailable. * * <p>Requires Permission: READ_PRIVILEGED_PHONE_STATE, for the calling app to be the device or - * profile owner, or that the calling app has carrier privileges (see {@link - * #hasCarrierPrivileges}). The profile owner is an app that owns a managed profile on the - * device; for more details see <a href="https://developer.android.com/work/managed-profiles"> - * Work profiles</a>. Profile owner access is deprecated and will be removed in a future - * release. + * profile owner and have the READ_PHONE_STATE permission, or that the calling app has carrier + * privileges (see {@link #hasCarrierPrivileges}). The profile owner is an app that owns a + * managed profile on the device; for more details see <a + * href="https://developer.android.com/work/managed-profiles">Work profiles</a>. Profile owner + * access is deprecated and will be removed in a future release. */ @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) @@ -2972,11 +2973,11 @@ public class TelephonyManager { * unavailable. * * <p>Requires Permission: READ_PRIVILEGED_PHONE_STATE, for the calling app to be the device or - * profile owner, or that the calling app has carrier privileges (see {@link - * #hasCarrierPrivileges}). The profile owner is an app that owns a managed profile on the - * device; for more details see <a href="https://developer.android.com/work/managed-profiles"> - * Work profiles</a>. Profile owner access is deprecated and will be removed in a future - * release. + * profile owner and have the READ_PHONE_STATE permission, or that the calling app has carrier + * privileges (see {@link #hasCarrierPrivileges}). The profile owner is an app that owns a + * managed profile on the device; for more details see <a + * href="https://developer.android.com/work/managed-profiles">Work profiles</a>. Profile owner + * access is deprecated and will be removed in a future release. * * @param subId for which Sim Serial number is returned * @hide @@ -3117,11 +3118,11 @@ public class TelephonyManager { * Return null if it is unavailable. * * <p>Requires Permission: READ_PRIVILEGED_PHONE_STATE, for the calling app to be the device or - * profile owner, or that the calling app has carrier privileges (see {@link - * #hasCarrierPrivileges}). The profile owner is an app that owns a managed profile on the - * device; for more details see <a href="https://developer.android.com/work/managed-profiles"> - * Work profiles</a>. Profile owner access is deprecated and will be removed in a future - * release. + * profile owner and have the READ_PHONE_STATE permission, or that the calling app has carrier + * privileges (see {@link #hasCarrierPrivileges}). The profile owner is an app that owns a + * managed profile on the device; for more details see <a + * href="https://developer.android.com/work/managed-profiles">Work profiles</a>. Profile owner + * access is deprecated and will be removed in a future release. */ @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) @@ -3135,17 +3136,17 @@ public class TelephonyManager { * Return null if it is unavailable. * * <p>Requires Permission: READ_PRIVILEGED_PHONE_STATE, for the calling app to be the device or - * profile owner, or that the calling app has carrier privileges (see {@link - * #hasCarrierPrivileges}). The profile owner is an app that owns a managed profile on the - * device; for more details see <a href="https://developer.android.com/work/managed-profiles"> - * Work profiles</a>. Profile owner access is deprecated and will be removed in a future - * release. + * profile owner and have the READ_PHONE_STATE permission, or that the calling app has carrier + * privileges (see {@link #hasCarrierPrivileges}). The profile owner is an app that owns a + * managed profile on the device; for more details see <a + * href="https://developer.android.com/work/managed-profiles">Work profiles</a>. Profile owner + * access is deprecated and will be removed in a future release. * * @param subId whose subscriber id is returned * @hide */ @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) + @UnsupportedAppUsage public String getSubscriberId(int subId) { try { IPhoneSubInfo info = getSubscriberInfo(); @@ -3530,7 +3531,7 @@ public class TelephonyManager { * @hide */ @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) + @UnsupportedAppUsage public String getMsisdn(int subId) { try { IPhoneSubInfo info = getSubscriberInfo(); @@ -4463,7 +4464,7 @@ public class TelephonyManager { /** * @hide */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) + @UnsupportedAppUsage private ITelephony getITelephony() { return ITelephony.Stub.asInterface(ServiceManager.getService(Context.TELEPHONY_SERVICE)); } @@ -7436,7 +7437,9 @@ public class TelephonyManager { @UnsupportedAppUsage public boolean isVolteAvailable() { try { - return getITelephony().isVolteAvailable(getSubId()); + return getITelephony().isAvailable(getSubId(), + MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VOICE, + ImsRegistrationImplBase.REGISTRATION_TECH_LTE, getOpPackageName()); } catch (RemoteException | NullPointerException ex) { return false; } @@ -8041,7 +8044,7 @@ public class TelephonyManager { * either READ_PRIVILEGED_PHONE_STATE or READ_PHONE_STATE to retrieve the information. * @hide */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) + @UnsupportedAppUsage public ServiceState getServiceStateForSubscriber(int subId) { try { ITelephony service = getITelephony(); @@ -8375,20 +8378,31 @@ public class TelephonyManager { } /** - * Action set from carrier signalling broadcast receivers to enable/disable metered apns - * Permissions android.Manifest.permission.MODIFY_PHONE_STATE is required - * @param subId the subscription ID that this action applies to. - * @param enabled control enable or disable metered apns. + * Used to enable or disable carrier data by the system based on carrier signalling or + * carrier privileged apps. Different from {@link #setDataEnabled(boolean)} which is linked to + * user settings, carrier data on/off won't affect user settings but will bypass the + * settings and turns off data internally if set to {@code false}. + * + * <p>If this object has been created with {@link #createForSubscriptionId}, applies to the + * given subId. Otherwise, applies to {@link SubscriptionManager#getDefaultDataSubscriptionId()} + * + * <p>Requires Permission: + * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}. + * + * @param enabled control enable or disable carrier data. * @hide */ - public void carrierActionSetMeteredApnsEnabled(int subId, boolean enabled) { + @SystemApi + @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) + public void setCarrierDataEnabled(boolean enabled) { try { ITelephony service = getITelephony(); if (service != null) { - service.carrierActionSetMeteredApnsEnabled(subId, enabled); + service.carrierActionSetMeteredApnsEnabled( + getSubId(SubscriptionManager.getDefaultDataSubscriptionId()), enabled); } } catch (RemoteException e) { - Log.e(TAG, "Error calling ITelephony#carrierActionSetMeteredApnsEnabled", e); + Log.e(TAG, "Error calling ITelephony#setCarrierDataEnabled", e); } } diff --git a/telephony/java/android/telephony/ims/ImsMmTelManager.java b/telephony/java/android/telephony/ims/ImsMmTelManager.java new file mode 100644 index 000000000000..c9cf473bb482 --- /dev/null +++ b/telephony/java/android/telephony/ims/ImsMmTelManager.java @@ -0,0 +1,760 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.telephony.ims; + + +import android.Manifest; +import android.annotation.CallbackExecutor; +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.RequiresPermission; +import android.annotation.SystemApi; +import android.content.Context; +import android.net.Uri; +import android.os.Binder; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.telephony.SubscriptionManager; +import android.telephony.ims.aidl.IImsCapabilityCallback; +import android.telephony.ims.aidl.IImsRegistrationCallback; +import android.telephony.ims.feature.ImsFeature; +import android.telephony.ims.feature.MmTelFeature; +import android.telephony.ims.stub.ImsRegistrationImplBase; + +import com.android.internal.telephony.ITelephony; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.concurrent.Executor; + +/** + * A manager for the MmTel (Multimedia Telephony) feature of an IMS network, given an associated + * subscription. + * + * Allows a user to query the IMS MmTel feature information for a subscription, register for + * registration and MmTel capability status callbacks, as well as query/modify user settings for the + * associated subscription. + * + * @see #createForSubscriptionId(Context, int) + * @hide + */ +public class ImsMmTelManager { + + private static final String TAG = "ImsMmTelManager"; + + /** + * @hide + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = "WIFI_MODE_", value = { + WIFI_MODE_WIFI_ONLY, + WIFI_MODE_CELLULAR_PREFERRED, + WIFI_MODE_WIFI_PREFERRED + }) + public @interface WiFiCallingMode {} + + /** + * Register for IMS over IWLAN if WiFi signal quality is high enough. Do not hand over to LTE + * registration if signal quality degrades. + * @hide + */ + @SystemApi + public static final int WIFI_MODE_WIFI_ONLY = 0; + + /** + * Prefer registering for IMS over LTE if LTE signal quality is high enough. + * @hide + */ + @SystemApi + public static final int WIFI_MODE_CELLULAR_PREFERRED = 1; + + /** + * Prefer registering for IMS over IWLAN if possible if WiFi signal quality is high enough. + * @hide + */ + @SystemApi + public static final int WIFI_MODE_WIFI_PREFERRED = 2; + + /** + * Callback class for receiving Registration callback events. + * @see #addImsRegistrationCallback(Executor, RegistrationCallback) (RegistrationCallback) + * @see #removeImsRegistrationCallback(RegistrationCallback) + */ + public static class RegistrationCallback { + + private static class RegistrationBinder extends IImsRegistrationCallback.Stub { + + private final RegistrationCallback mLocalCallback; + private Executor mExecutor; + + RegistrationBinder(RegistrationCallback localCallback) { + mLocalCallback = localCallback; + } + + @Override + public void onRegistered(int imsRadioTech) { + if (mLocalCallback == null) return; + + Binder.withCleanCallingIdentity(() -> + mExecutor.execute(() -> mLocalCallback.onRegistered(imsRadioTech))); + } + + @Override + public void onRegistering(int imsRadioTech) { + if (mLocalCallback == null) return; + + Binder.withCleanCallingIdentity(() -> + mExecutor.execute(() -> mLocalCallback.onRegistering(imsRadioTech))); + } + + @Override + public void onDeregistered(ImsReasonInfo info) { + if (mLocalCallback == null) return; + + Binder.withCleanCallingIdentity(() -> + mExecutor.execute(() -> mLocalCallback.onDeregistered(info))); + } + + @Override + public void onTechnologyChangeFailed(int imsRadioTech, ImsReasonInfo info) { + if (mLocalCallback == null) return; + + Binder.withCleanCallingIdentity(() -> + mExecutor.execute(() -> + mLocalCallback.onTechnologyChangeFailed(imsRadioTech, info))); + } + + @Override + public void onSubscriberAssociatedUriChanged(Uri[] uris) { + if (mLocalCallback == null) return; + + Binder.withCleanCallingIdentity(() -> + mExecutor.execute(() -> + mLocalCallback.onSubscriberAssociatedUriChanged(uris))); + } + + private void setExecutor(Executor executor) { + mExecutor = executor; + } + } + + private final RegistrationBinder mBinder = new RegistrationBinder(this); + + /** + * Notifies the framework when the IMS Provider is registered to the IMS network. + * + * @param imsRadioTech the radio access technology. Valid values are defined in + * {@link ImsRegistrationImplBase.ImsRegistrationTech}. + */ + public void onRegistered(@ImsRegistrationImplBase.ImsRegistrationTech int imsRadioTech) { + } + + /** + * Notifies the framework when the IMS Provider is trying to register the IMS network. + * + * @param imsRadioTech the radio access technology. Valid values are defined in + * {@link ImsRegistrationImplBase.ImsRegistrationTech}. + */ + public void onRegistering(@ImsRegistrationImplBase.ImsRegistrationTech int imsRadioTech) { + } + + /** + * Notifies the framework when the IMS Provider is deregistered from the IMS network. + * + * @param info the {@link ImsReasonInfo} associated with why registration was disconnected. + */ + public void onDeregistered(ImsReasonInfo info) { + } + + /** + * A failure has occurred when trying to handover registration to another technology type, + * defined in {@link ImsRegistrationImplBase.ImsRegistrationTech} + * + * @param imsRadioTech The {@link ImsRegistrationImplBase.ImsRegistrationTech} type that has + * failed + * @param info A {@link ImsReasonInfo} that identifies the reason for failure. + */ + public void onTechnologyChangeFailed( + @ImsRegistrationImplBase.ImsRegistrationTech int imsRadioTech, ImsReasonInfo info) { + } + + /** + * Returns a list of subscriber {@link Uri}s associated with this IMS subscription when + * it changes. Per RFC3455, an associated URI is a URI that the service provider has + * allocated to a user for their own usage. A user's phone number is typically one of the + * associated URIs. + * @param uris new array of subscriber {@link Uri}s that are associated with this IMS + * subscription. + * @hide + */ + public void onSubscriberAssociatedUriChanged(Uri[] uris) { + } + + /**@hide*/ + public final IImsRegistrationCallback getBinder() { + return mBinder; + } + + /**@hide*/ + //Only exposed as public for compatibility with deprecated ImsManager APIs. + public void setExecutor(Executor executor) { + mBinder.setExecutor(executor); + } + } + + /** + * Receives IMS capability status updates from the ImsService. + * + * @see #addMmTelCapabilityCallback(Executor, CapabilityCallback) (CapabilityCallback) + * @see #removeMmTelCapabilityCallback(CapabilityCallback) + */ + public static class CapabilityCallback { + + private static class CapabilityBinder extends IImsCapabilityCallback.Stub { + + private final CapabilityCallback mLocalCallback; + private Executor mExecutor; + + CapabilityBinder(CapabilityCallback c) { + mLocalCallback = c; + } + + @Override + public void onCapabilitiesStatusChanged(int config) { + if (mLocalCallback == null) return; + + Binder.withCleanCallingIdentity(() -> + mExecutor.execute(() -> mLocalCallback.onCapabilitiesStatusChanged( + new MmTelFeature.MmTelCapabilities(config)))); + } + + @Override + public void onQueryCapabilityConfiguration(int capability, int radioTech, + boolean isEnabled) { + // This is not used for public interfaces. + } + + @Override + public void onChangeCapabilityConfigurationError(int capability, int radioTech, + @ImsFeature.ImsCapabilityError int reason) { + // This is not used for public interfaces + } + + private void setExecutor(Executor executor) { + mExecutor = executor; + } + } + + private final CapabilityBinder mBinder = new CapabilityBinder(this); + + /** + * The status of the feature's capabilities has changed to either available or unavailable. + * If unavailable, the feature is not able to support the unavailable capability at this + * time. + * + * @param capabilities The new availability of the capabilities. + */ + public void onCapabilitiesStatusChanged( + MmTelFeature.MmTelCapabilities capabilities) { + } + + /**@hide*/ + public final IImsCapabilityCallback getBinder() { + return mBinder; + } + + /**@hide*/ + // Only exposed as public method for compatibility with deprecated ImsManager APIs. + // TODO: clean up dependencies and change back to private visibility. + public void setExecutor(Executor executor) { + mBinder.setExecutor(executor); + } + } + + private Context mContext; + private int mSubId; + + /** + * Create an instance of ImsManager for the subscription id specified. + * + * @param context + * @param subId The ID of the subscription that this ImsManager will use. + * @see android.telephony.SubscriptionManager#getActiveSubscriptionInfoList() + * @throws IllegalArgumentException if the subscription is invalid or + * the subscription ID is not an active subscription. + */ + public static ImsMmTelManager createForSubscriptionId(Context context, int subId) { + if (!SubscriptionManager.isValidSubscriptionId(subId) + || !getSubscriptionManager(context).isActiveSubscriptionId(subId)) { + throw new IllegalArgumentException("Invalid subscription ID"); + } + + return new ImsMmTelManager(context, subId); + } + + private ImsMmTelManager(Context context, int subId) { + mContext = context; + mSubId = subId; + } + + /** + * Registers a {@link RegistrationCallback} with the system, which will provide registration + * updates for the subscription specified in {@link #createForSubscriptionId(Context, int)}. Use + * {@link SubscriptionManager.OnSubscriptionsChangedListener} to listen to Subscription changed + * events and call {@link #removeImsRegistrationCallback(RegistrationCallback)} to clean up + * after a subscription is removed. + * @param executor The executor the callback events should be run on. + * @param c The {@link RegistrationCallback} to be added. + * @see #removeImsRegistrationCallback(RegistrationCallback) + */ + @RequiresPermission(Manifest.permission.READ_PHONE_STATE) + public void addImsRegistrationCallback(@CallbackExecutor Executor executor, + @NonNull RegistrationCallback c) { + if (c == null) { + throw new IllegalArgumentException("Must include a non-null RegistrationCallback."); + } + if (executor == null) { + throw new IllegalArgumentException("Must include a non-null Executor."); + } + c.setExecutor(executor); + try { + getITelephony().addImsRegistrationCallback(mSubId, c.getBinder(), + mContext.getOpPackageName()); + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } + } + + /** + * Removes an existing {@link RegistrationCallback}. Ensure to call this method when cleaning + * up to avoid memory leaks or when the subscription is removed. + * @param c The {@link RegistrationCallback} to be removed. + * @see SubscriptionManager.OnSubscriptionsChangedListener + * @see #addImsRegistrationCallback(Executor, RegistrationCallback) + */ + @RequiresPermission(Manifest.permission.READ_PHONE_STATE) + public void removeImsRegistrationCallback(@NonNull RegistrationCallback c) { + if (c == null) { + throw new IllegalArgumentException("Must include a non-null RegistrationCallback."); + } + try { + getITelephony().removeImsRegistrationCallback(mSubId, c.getBinder(), + mContext.getOpPackageName()); + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } + } + + /** + * Registers a {@link CapabilityCallback} with the system, which will provide MmTel capability + * updates for the subscription specified in {@link #createForSubscriptionId(Context, int)}. + * Use {@link SubscriptionManager.OnSubscriptionsChangedListener} to listen to + * subscription changed events and call + * {@link #removeImsRegistrationCallback(RegistrationCallback)} to clean up after a subscription + * is removed. + * @param executor The executor the callback events should be run on. + * @param c The MmTel {@link CapabilityCallback} to be registered. + * @see #removeMmTelCapabilityCallback(CapabilityCallback) + */ + @RequiresPermission(Manifest.permission.READ_PHONE_STATE) + public void addMmTelCapabilityCallback(@CallbackExecutor Executor executor, + @NonNull CapabilityCallback c) { + if (c == null) { + throw new IllegalArgumentException("Must include a non-null RegistrationCallback."); + } + if (executor == null) { + throw new IllegalArgumentException("Must include a non-null Executor."); + } + c.setExecutor(executor); + try { + getITelephony().addMmTelCapabilityCallback(mSubId, c.getBinder(), + mContext.getOpPackageName()); + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } + } + + /** + * Removes an existing MmTel {@link CapabilityCallback}. Be sure to call this when cleaning + * up to avoid memory leaks. + * @param c The MmTel {@link CapabilityCallback} to be removed. + * @see #addMmTelCapabilityCallback(Executor, CapabilityCallback) + */ + @RequiresPermission(Manifest.permission.READ_PHONE_STATE) + public void removeMmTelCapabilityCallback(@NonNull CapabilityCallback c) { + if (c == null) { + throw new IllegalArgumentException("Must include a non-null RegistrationCallback."); + } + try { + getITelephony().removeMmTelCapabilityCallback(mSubId, c.getBinder(), + mContext.getOpPackageName()); + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } + } + + /** + * Query the user's setting for whether or not to use MmTel capabilities over IMS, + * such as voice and video, depending on carrier configuration for the current subscription. + * @see #setAdvancedCallingSetting(boolean) + * @return true if the user’s setting for advanced calling is enabled and false otherwise. + * @hide + */ + @SystemApi + @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE) + public boolean isAdvancedCallingSettingEnabled() { + try { + return getITelephony().isAdvancedCallingSettingEnabled(mSubId); + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } + } + + /** + * Modify the user’s setting for “Advanced Calling” or "Enhanced 4G LTE", which is used to + * enable MmTel IMS features, such as voice and video calling, depending on the carrier + * configuration for the current subscription. Modifying this value may also trigger an IMS + * registration or deregistration, depending on the new value. + * @see #isAdvancedCallingEnabled() + * @hide + */ + @SystemApi + @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE) + public void setAdvancedCallingSetting(boolean isEnabled) { + try { + getITelephony().setAdvancedCallingSetting(mSubId, isEnabled); + return; + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } + } + + /** + * Query the IMS MmTel capability for a given registration technology. This does not + * necessarily mean that we are registered and the capability is available, but rather the + * subscription is capable of this service over IMS. + * + * @see android.telephony.CarrierConfigManager#KEY_CARRIER_VOLTE_AVAILABLE_BOOL + * @see android.telephony.CarrierConfigManager#KEY_CARRIER_VT_AVAILABLE_BOOL + * @see android.telephony.CarrierConfigManager#KEY_CARRIER_IMS_GBA_REQUIRED_BOOL + * @see #isAvailable(int, int) + * + * @param imsRegTech The IMS registration technology, can be one of the following: + * {@link ImsRegistrationImplBase#REGISTRATION_TECH_LTE}, + * {@link ImsRegistrationImplBase#REGISTRATION_TECH_IWLAN} + * @param capability The IMS MmTel capability to query, can be one of the following: + * {@link MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_VOICE}, + * {@link MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_VIDEO, + * {@link MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_UT}, + * {@link MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_SMS} + * @return {@code true} if the MmTel IMS capability is capable for this subscription, false + * otherwise. + */ + @RequiresPermission(Manifest.permission.READ_PHONE_STATE) + public boolean isCapable(@MmTelFeature.MmTelCapabilities.MmTelCapability int capability, + @ImsRegistrationImplBase.ImsRegistrationTech int imsRegTech) { + try { + return getITelephony().isCapable(mSubId, capability, imsRegTech, + mContext.getOpPackageName()); + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } + } + + /** + * Query the availability of an IMS MmTel capability for a given registration technology. If + * a capability is available, IMS is registered and the service is currently available over IMS. + * + * @see #isCapable(int, int) + * + * @param imsRegTech The IMS registration technology, can be one of the following: + * {@link ImsRegistrationImplBase#REGISTRATION_TECH_LTE}, + * {@link ImsRegistrationImplBase#REGISTRATION_TECH_IWLAN} + * @param capability The IMS MmTel capability to query, can be one of the following: + * {@link MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_VOICE}, + * {@link MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_VIDEO, + * {@link MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_UT}, + * {@link MmTelFeature.MmTelCapabilities#CAPABILITY_TYPE_SMS} + * @return {@code true} if the MmTel IMS capability is available for this subscription, false + * otherwise. + */ + @RequiresPermission(Manifest.permission.READ_PHONE_STATE) + public boolean isAvailable(@MmTelFeature.MmTelCapabilities.MmTelCapability int capability, + @ImsRegistrationImplBase.ImsRegistrationTech int imsRegTech) { + try { + return getITelephony().isAvailable(mSubId, capability, imsRegTech, + mContext.getOpPackageName()); + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } + } + + /** + * The user's setting for whether or not they have enabled the "Video Calling" setting. + * @return true if the user’s “Video Calling” setting is currently enabled. + * @see #setVtSetting(boolean) + */ + @SystemApi + @RequiresPermission(Manifest.permission.READ_PHONE_STATE) + public boolean isVtSettingEnabled() { + try { + return getITelephony().isVtSettingEnabled(mSubId, mContext.getOpPackageName()); + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } + } + + /** + * Change the user's setting for Video Telephony and enable the Video Telephony capability. + * @see #isVtSettingEnabled() + * @hide + */ + @SystemApi + @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE) + public void setVtSetting(boolean isEnabled) { + try { + getITelephony().setVtSetting(mSubId, isEnabled); + return; + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } + } + + /** + * @return true if the user's setting for Voice over WiFi is enabled and false if it is not. + * @see #setVoWiFiSetting(boolean) + * @hide + */ + @SystemApi + @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE) + public boolean isVoWiFiSettingEnabled() { + try { + return getITelephony().isVoWiFiSettingEnabled(mSubId); + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } + } + + /** + * Sets the user's setting for whether or not Voice over WiFi is enabled. + * @param isEnabled true if the user's setting for Voice over WiFi is enabled, false otherwise= + * @see #isVoWiFiSettingEnabled() + * @hide + */ + @SystemApi + @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE) + public void setVoWiFiSetting(boolean isEnabled) { + try { + getITelephony().setVoWiFiSetting(mSubId, isEnabled); + return; + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } + } + + /** + * @return true if the user's setting for Voice over WiFi while roaming is enabled, false + * if disabled. + * @see #setVoWiFiRoamingSetting(boolean) + * @hide + */ + @SystemApi + @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE) + public boolean isVoWiFiRoamingSettingEnabled() { + try { + return getITelephony().isVoWiFiRoamingSettingEnabled(mSubId); + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } + } + + /** + * Change the user's setting for Voice over WiFi while roaming. + * @param isEnabled true if the user's setting for Voice over WiFi while roaming is enabled, + * false otherwise. + * @see #isVoWiFiRoamingSettingEnabled() + * @hide + */ + @SystemApi + @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE) + public void setVoWiFiRoamingSetting(boolean isEnabled) { + try { + getITelephony().setVoWiFiRoamingSetting(mSubId, isEnabled); + return; + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } + } + + /** + * Overrides the Voice over WiFi capability to true for IMS, but do not persist the setting. + * Typically used during the Voice over WiFi registration process for some carriers. + * + * @param isCapable true if the IMS stack should try to register for IMS over IWLAN, false + * otherwise. + * @param mode the Voice over WiFi mode preference to set, which can be one of the following: + * - {@link #WIFI_MODE_WIFI_ONLY} + * - {@link #WIFI_MODE_CELLULAR_PREFERRED} + * - {@link #WIFI_MODE_WIFI_PREFERRED} + * @see #setVoWiFiSetting(boolean) + * @hide + */ + @SystemApi + @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE) + public void setVoWiFiNonPersistent(boolean isCapable, int mode) { + try { + getITelephony().setVoWiFiNonPersistent(mSubId, isCapable, mode); + return; + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } + } + + /** + * @return The Voice over WiFi Mode preference set by the user, which can be one of the + * following: + * - {@link #WIFI_MODE_WIFI_ONLY} + * - {@link #WIFI_MODE_CELLULAR_PREFERRED} + * - {@link #WIFI_MODE_WIFI_PREFERRED} + * @see #setVoWiFiSetting(boolean) + * @hide + */ + @SystemApi + @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE) + public @WiFiCallingMode int getVoWiFiModeSetting() { + try { + return getITelephony().getVoWiFiModeSetting(mSubId); + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } + } + + /** + * Set the user's preference for Voice over WiFi calling mode. + * @param mode The user's preference for the technology to register for IMS over, can be one of + * the following: + * - {@link #WIFI_MODE_WIFI_ONLY} + * - {@link #WIFI_MODE_CELLULAR_PREFERRED} + * - {@link #WIFI_MODE_WIFI_PREFERRED} + * @see #getVoWiFiModeSetting() + * @hide + */ + @SystemApi + @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE) + public void setVoWiFiModeSetting(@WiFiCallingMode int mode) { + try { + getITelephony().setVoWiFiModeSetting(mSubId, mode); + return; + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } + } + + /** + * Set the user's preference for Voice over WiFi calling mode while the device is roaming on + * another network. + * + * @return The user's preference for the technology to register for IMS over when roaming on + * another network, can be one of the following: + * - {@link #WIFI_MODE_WIFI_ONLY} + * - {@link #WIFI_MODE_CELLULAR_PREFERRED} + * - {@link #WIFI_MODE_WIFI_PREFERRED} + * @see #setVoWiFiRoamingSetting(boolean) + * @hide + */ + @SystemApi + @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE) + @WiFiCallingMode int getVoWiFiRoamingModeSetting() { + try { + return getITelephony().getVoWiFiRoamingModeSetting(mSubId); + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } + } + + /** + * Set the user's preference for Voice over WiFi mode while the device is roaming on another + * network. + * + * @param mode The user's preference for the technology to register for IMS over when roaming on + * another network, can be one of the following: + * - {@link #WIFI_MODE_WIFI_ONLY} + * - {@link #WIFI_MODE_CELLULAR_PREFERRED} + * - {@link #WIFI_MODE_WIFI_PREFERRED} + * @see #getVoWiFiRoamingModeSetting() + * @hide + */ + @SystemApi + @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE) + public void setVoWiFiRoamingModeSetting(@WiFiCallingMode int mode) { + try { + getITelephony().setVoWiFiRoamingModeSetting(mSubId, mode); + return; + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } + } + + /** + * Change the user's setting for RTT capability of this device. + * @param isEnabled if true RTT will be enabled during calls. + * @hide + */ + @SystemApi + @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE) + public void setRttCapabilitySetting(boolean isEnabled) { + try { + getITelephony().setRttCapabilitySetting(mSubId, isEnabled); + return; + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } + } + + /** + * @return true if TTY over VoLTE is supported + * @see android.telecom.TelecomManager#getCurrentTtyMode + * @see android.telephony.CarrierConfigManager#KEY_CARRIER_VOLTE_TTY_SUPPORTED_BOOL + * @hide + */ + @SystemApi + @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE) + boolean isTtyOverVolteEnabled() { + try { + return getITelephony().isTtyOverVolteEnabled(mSubId); + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } + } + + private static SubscriptionManager getSubscriptionManager(Context context) { + SubscriptionManager manager = context.getSystemService(SubscriptionManager.class); + if (manager == null) { + throw new RuntimeException("Could not find SubscriptionManager."); + } + return manager; + } + + private static ITelephony getITelephony() { + ITelephony binder = ITelephony.Stub.asInterface( + ServiceManager.getService(Context.TELEPHONY_SERVICE)); + if (binder == null) { + throw new RuntimeException("Could not find Telephony Service."); + } + return binder; + } +} diff --git a/telephony/java/android/telephony/ims/aidl/IImsRegistrationCallback.aidl b/telephony/java/android/telephony/ims/aidl/IImsRegistrationCallback.aidl index 4f37caa33680..749b1916962e 100644 --- a/telephony/java/android/telephony/ims/aidl/IImsRegistrationCallback.aidl +++ b/telephony/java/android/telephony/ims/aidl/IImsRegistrationCallback.aidl @@ -23,7 +23,7 @@ import android.telephony.ims.stub.ImsFeatureConfiguration; import android.telephony.ims.ImsReasonInfo; /** - * See ImsRegistrationImplBase.Callback for more information. + * See {@link ImsManager#RegistrationCallback} for more information. * * {@hide} */ diff --git a/telephony/java/android/telephony/ims/feature/ImsFeature.java b/telephony/java/android/telephony/ims/feature/ImsFeature.java index b77881e29f1e..7f69f43f6cea 100644 --- a/telephony/java/android/telephony/ims/feature/ImsFeature.java +++ b/telephony/java/android/telephony/ims/feature/ImsFeature.java @@ -167,59 +167,6 @@ public abstract class ImsFeature { */ public static final int CAPABILITY_SUCCESS = 0; - - /** - * The framework implements this callback in order to register for Feature Capability status - * updates, via {@link #onCapabilitiesStatusChanged(Capabilities)}, query Capability - * configurations, via {@link #onQueryCapabilityConfiguration}, as well as to receive error - * callbacks when the ImsService can not change the capability as requested, via - * {@link #onChangeCapabilityConfigurationError}. - * - * @hide - */ - public static class CapabilityCallback extends IImsCapabilityCallback.Stub { - - @Override - public final void onCapabilitiesStatusChanged(int config) throws RemoteException { - onCapabilitiesStatusChanged(new Capabilities(config)); - } - - /** - * Returns the result of a query for the capability configuration of a requested capability. - * - * @param capability The capability that was requested. - * @param radioTech The IMS radio technology associated with the capability. - * @param isEnabled true if the capability is enabled, false otherwise. - */ - @Override - public void onQueryCapabilityConfiguration(int capability, int radioTech, - boolean isEnabled) { - - } - - /** - * Called when a change to the capability configuration has returned an error. - * - * @param capability The capability that was requested to be changed. - * @param radioTech The IMS radio technology associated with the capability. - * @param reason error associated with the failure to change configuration. - */ - @Override - public void onChangeCapabilityConfigurationError(int capability, int radioTech, - @ImsCapabilityError int reason) { - } - - /** - * The status of the feature's capabilities has changed to either available or unavailable. - * If unavailable, the feature is not able to support the unavailable capability at this - * time. - * - * @param config The new availability of the capabilities. - */ - public void onCapabilitiesStatusChanged(Capabilities config) { - } - } - /** * Used by the ImsFeature to call back to the CapabilityCallback that the framework has * provided. diff --git a/telephony/java/android/telephony/ims/feature/MmTelFeature.java b/telephony/java/android/telephony/ims/feature/MmTelFeature.java index 7681aefc07dc..969959433f23 100644 --- a/telephony/java/android/telephony/ims/feature/MmTelFeature.java +++ b/telephony/java/android/telephony/ims/feature/MmTelFeature.java @@ -17,6 +17,8 @@ package android.telephony.ims.feature; import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.SystemApi; import android.os.Bundle; import android.os.Message; @@ -222,21 +224,31 @@ public class MmTelFeature extends ImsFeature { * This MmTelFeature can then return the status of each of these capabilities (enabled or not) * by sending a {@link #notifyCapabilitiesStatusChanged} callback to the framework. The current * status can also be queried using {@link #queryCapabilityStatus()}. + * @see #isCapable(int) */ public static class MmTelCapabilities extends Capabilities { /** - * @hide + * Create a new empty {@link MmTelCapabilities} instance. + * @see #addCapabilities(int) + * @see #removeCapabilities(int) */ @VisibleForTesting public MmTelCapabilities() { super(); } + /**@deprecated Use {@link MmTelCapabilities} to construct a new instance instead.*/ + @Deprecated public MmTelCapabilities(Capabilities c) { mCapabilities = c.mCapabilities; } + /** + * Create a new {link @MmTelCapabilities} instance with the provided capabilities. + * @param capabilities The capabilities that are supported for MmTel in the form of a + * bitfield. + */ public MmTelCapabilities(int capabilities) { mCapabilities = capabilities; } @@ -406,7 +418,10 @@ public class MmTelFeature extends ImsFeature { * support the capability that is enabled. A capability that is disabled by the framework (via * {@link #changeEnabledCapabilities}) should also show the status as disabled. */ - public final void notifyCapabilitiesStatusChanged(MmTelCapabilities c) { + public final void notifyCapabilitiesStatusChanged(@NonNull MmTelCapabilities c) { + if (c == null) { + throw new IllegalArgumentException("MmTelCapabilities must be non-null!"); + } super.notifyCapabilitiesStatusChanged(c); } @@ -414,7 +429,12 @@ public class MmTelFeature extends ImsFeature { * Notify the framework of an incoming call. * @param c The {@link ImsCallSessionImplBase} of the new incoming call. */ - public final void notifyIncomingCall(ImsCallSessionImplBase c, Bundle extras) { + public final void notifyIncomingCall(@NonNull ImsCallSessionImplBase c, + @NonNull Bundle extras) { + if (c == null || extras == null) { + throw new IllegalArgumentException("ImsCallSessionImplBase and Bundle can not be " + + "null."); + } synchronized (mLock) { if (mListener == null) { throw new IllegalStateException("Session is not available."); @@ -434,7 +454,12 @@ public class MmTelFeature extends ImsFeature { * This can be null if no call information is available for the rejected call. * @param reason The {@link ImsReasonInfo} call rejection reason. */ - public final void notifyRejectedCall(ImsCallProfile callProfile, ImsReasonInfo reason) { + public final void notifyRejectedCall(@NonNull ImsCallProfile callProfile, + @NonNull ImsReasonInfo reason) { + if (callProfile == null || reason == null) { + throw new IllegalArgumentException("ImsCallProfile and ImsReasonInfo must not be " + + "null."); + } synchronized (mLock) { if (mListener == null) { throw new IllegalStateException("Session is not available."); @@ -508,8 +533,8 @@ public class MmTelFeature extends ImsFeature { * the framework. */ @Override - public void changeEnabledCapabilities(CapabilityChangeRequest request, - CapabilityCallbackProxy c) { + public void changeEnabledCapabilities(@NonNull CapabilityChangeRequest request, + @NonNull CapabilityCallbackProxy c) { // Base implementation, no-op } @@ -531,7 +556,7 @@ public class MmTelFeature extends ImsFeature { * {@link ImsCallProfile#CALL_TYPE_VS_RX} * @return a {@link ImsCallProfile} object */ - public ImsCallProfile createCallProfile(int callSessionType, int callType) { + public @Nullable ImsCallProfile createCallProfile(int callSessionType, int callType) { // Base Implementation - Should be overridden return null; } @@ -552,7 +577,7 @@ public class MmTelFeature extends ImsFeature { * * @param profile a call profile to make the call */ - public ImsCallSessionImplBase createCallSession(ImsCallProfile profile) { + public @Nullable ImsCallSessionImplBase createCallSession(@NonNull ImsCallProfile profile) { // Base Implementation - Should be overridden return null; } @@ -569,7 +594,7 @@ public class MmTelFeature extends ImsFeature { * @return a {@link ProcessCallResult} to the framework, which will be used to determine if the * call will be placed over IMS or via CSFB. */ - public @ProcessCallResult int shouldProcessCall(String[] numbers) { + public @ProcessCallResult int shouldProcessCall(@NonNull String[] numbers) { return PROCESS_CALL_IMS; } @@ -602,7 +627,7 @@ public class MmTelFeature extends ImsFeature { * @return The {@link ImsUtImplBase} Ut interface implementation for the supplementary service * configuration. */ - public ImsUtImplBase getUt() { + public @NonNull ImsUtImplBase getUt() { // Base Implementation - Should be overridden return new ImsUtImplBase(); } @@ -611,7 +636,7 @@ public class MmTelFeature extends ImsFeature { * @return The {@link ImsEcbmImplBase} Emergency call-back mode interface for emergency VoLTE * calls that support it. */ - public ImsEcbmImplBase getEcbm() { + public @NonNull ImsEcbmImplBase getEcbm() { // Base Implementation - Should be overridden return new ImsEcbmImplBase(); } @@ -620,7 +645,7 @@ public class MmTelFeature extends ImsFeature { * @return The {@link ImsMultiEndpointImplBase} implementation for implementing Dialog event * package processing for multi-endpoint. */ - public ImsMultiEndpointImplBase getMultiEndpoint() { + public @NonNull ImsMultiEndpointImplBase getMultiEndpoint() { // Base Implementation - Should be overridden return new ImsMultiEndpointImplBase(); } @@ -646,7 +671,7 @@ public class MmTelFeature extends ImsFeature { * } * } */ - public void setUiTtyMode(int mode, Message onCompleteMessage) { + public void setUiTtyMode(int mode, @Nullable Message onCompleteMessage) { // Base Implementation - Should be overridden } @@ -680,7 +705,7 @@ public class MmTelFeature extends ImsFeature { * @return an instance of {@link ImsSmsImplBase} which should be implemented by the IMS * Provider. */ - public ImsSmsImplBase getSmsImplementation() { + public @NonNull ImsSmsImplBase getSmsImplementation() { return new ImsSmsImplBase(); } diff --git a/telephony/java/android/telephony/ims/stub/ImsRegistrationImplBase.java b/telephony/java/android/telephony/ims/stub/ImsRegistrationImplBase.java index cecf2e26f139..a08e0313bb5b 100644 --- a/telephony/java/android/telephony/ims/stub/ImsRegistrationImplBase.java +++ b/telephony/java/android/telephony/ims/stub/ImsRegistrationImplBase.java @@ -76,64 +76,6 @@ public class ImsRegistrationImplBase { private static final int REGISTRATION_STATE_REGISTERING = 1; private static final int REGISTRATION_STATE_REGISTERED = 2; - /** - * Callback class for receiving Registration callback events. - * @hide - */ - public static class Callback extends IImsRegistrationCallback.Stub { - /** - * Notifies the framework when the IMS Provider is connected to the IMS network. - * - * @param imsRadioTech the radio access technology. Valid values are defined in - * {@link ImsRegistrationTech}. - */ - @Override - public void onRegistered(@ImsRegistrationTech int imsRadioTech) { - } - - /** - * Notifies the framework when the IMS Provider is trying to connect the IMS network. - * - * @param imsRadioTech the radio access technology. Valid values are defined in - * {@link ImsRegistrationTech}. - */ - @Override - public void onRegistering(@ImsRegistrationTech int imsRadioTech) { - } - - /** - * Notifies the framework when the IMS Provider is disconnected from the IMS network. - * - * @param info the {@link ImsReasonInfo} associated with why registration was disconnected. - */ - @Override - public void onDeregistered(ImsReasonInfo info) { - } - - /** - * A failure has occurred when trying to handover registration to another technology type, - * defined in {@link ImsRegistrationTech} - * - * @param imsRadioTech The {@link ImsRegistrationTech} type that has failed - * @param info A {@link ImsReasonInfo} that identifies the reason for failure. - */ - @Override - public void onTechnologyChangeFailed(@ImsRegistrationTech int imsRadioTech, - ImsReasonInfo info) { - } - - /** - * Returns a list of subscriber {@link Uri}s associated with this IMS subscription when - * it changes. - * @param uris new array of subscriber {@link Uri}s that are associated with this IMS - * subscription. - */ - @Override - public void onSubscriberAssociatedUriChanged(Uri[] uris) { - - } - } - private final IImsRegistration mBinder = new IImsRegistration.Stub() { @Override diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl index 006b04036dca..dc233585dea4 100644 --- a/telephony/java/com/android/internal/telephony/ITelephony.aidl +++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl @@ -38,10 +38,12 @@ import android.telephony.ServiceState; import android.telephony.SignalStrength; import android.telephony.TelephonyHistogram; import android.telephony.VisualVoicemailSmsFilterSettings; +import android.telephony.ims.aidl.IImsCapabilityCallback; import android.telephony.ims.aidl.IImsConfig; import android.telephony.ims.aidl.IImsMmTelFeature; import android.telephony.ims.aidl.IImsRcsFeature; import android.telephony.ims.aidl.IImsRegistration; +import android.telephony.ims.aidl.IImsRegistrationCallback; import com.android.ims.internal.IImsServiceFeatureCallback; import com.android.internal.telephony.CellNetworkScanResult; import com.android.internal.telephony.OperatorInfo; @@ -1057,11 +1059,6 @@ interface ITelephony { */ boolean isWifiCallingAvailable(int subId); - /** - * Returns the Status of VoLTE for the subscription ID specified. - */ - boolean isVolteAvailable(int subId); - /** * Returns the Status of VT (video telephony) for the subscription ID specified. */ @@ -1505,4 +1502,117 @@ interface ITelephony { * */ int getRadioPowerState(int slotIndex, String callingPackage); + + // IMS specific AIDL commands, see ImsMmTelManager.java + + /** + * Adds an IMS registration status callback for the subscription id specified. + */ + oneway void addImsRegistrationCallback(int subId, IImsRegistrationCallback c, + String callingPackage); + /** + * Removes an existing IMS registration status callback for the subscription specified. + */ + oneway void removeImsRegistrationCallback(int subId, IImsRegistrationCallback c, + String callingPackage); + + /** + * Adds an IMS MmTel capabilities callback for the subscription specified. + */ + oneway void addMmTelCapabilityCallback(int subId, IImsCapabilityCallback c, + String callingPackage); + + /** + * Removes an existing IMS MmTel capabilities callback for the subscription specified. + */ + oneway void removeMmTelCapabilityCallback(int subId, IImsCapabilityCallback c, + String callingPackage); + + /** + * return true if the IMS MmTel capability for the given registration tech is capable. + */ + boolean isCapable(int subId, int capability, int regTech, String callingPackage); + + /** + * return true if the IMS MmTel capability for the given registration tech is available. + */ + boolean isAvailable(int subId, int capability, int regTech, String callingPackage); + + /** + * Returns true if the user's setting for 4G LTE is enabled, for the subscription specified. + */ + boolean isAdvancedCallingSettingEnabled(int subId); + + /** + * Modify the user's setting for whether or not 4G LTE is enabled. + */ + void setAdvancedCallingSetting(int subId, boolean isEnabled); + + /** + * return true if the user's setting for VT is enabled for the subscription. + */ + boolean isVtSettingEnabled(int subId, String callingPackage); + + /** + * Modify the user's setting for whether or not VT is available for the subscrption specified. + */ + void setVtSetting(int subId, boolean isEnabled); + + /** + * return true if the user's setting for whether or not Voice over WiFi is currently enabled. + */ + boolean isVoWiFiSettingEnabled(int subId); + + /** + * sets the user's setting for Voice over WiFi enabled state. + */ + void setVoWiFiSetting(int subId, boolean isEnabled); + + /** + * return true if the user's setting for Voice over WiFi while roaming is enabled. + */ + boolean isVoWiFiRoamingSettingEnabled(int subId); + + /** + * Sets the user's preference for whether or not Voice over WiFi is enabled for the current + * subscription while roaming. + */ + void setVoWiFiRoamingSetting(int subId, boolean isEnabled); + + /** + * Set the Voice over WiFi enabled state, but do not persist the setting. + */ + void setVoWiFiNonPersistent(int subId, boolean isCapable, int mode); + + /** + * return the Voice over WiFi mode preference set by the user for the subscription specified. + */ + int getVoWiFiModeSetting(int subId); + + /** + * sets the user's preference for the Voice over WiFi mode for the subscription specified. + */ + void setVoWiFiModeSetting(int subId, int mode); + + /** + * return the Voice over WiFi mode preference set by the user for the subscription specified + * while roaming. + */ + int getVoWiFiRoamingModeSetting(int subId); + + /** + * sets the user's preference for the Voice over WiFi mode for the subscription specified + * while roaming. + */ + void setVoWiFiRoamingModeSetting(int subId, int mode); + + /** + * Modify the user's setting for whether or not RTT is enabled for the subscrption specified. + */ + void setRttCapabilitySetting(int subId, boolean isEnabled); + + /** + * return true if TTY over VoLTE is enabled for the subscription specified. + */ + boolean isTtyOverVolteEnabled(int subId); } diff --git a/tests/net/java/android/net/ConnectivityManagerTest.java b/tests/net/java/android/net/ConnectivityManagerTest.java index 03a617c354fa..6174c6ca6190 100644 --- a/tests/net/java/android/net/ConnectivityManagerTest.java +++ b/tests/net/java/android/net/ConnectivityManagerTest.java @@ -219,7 +219,7 @@ public class ConnectivityManagerTest { // callback triggers captor.getValue().send(makeMessage(request, ConnectivityManager.CALLBACK_AVAILABLE)); verify(callback, timeout(500).times(1)).onAvailable(any(Network.class), - any(NetworkCapabilities.class), any(LinkProperties.class)); + any(NetworkCapabilities.class), any(LinkProperties.class), anyBoolean()); // unregister callback manager.unregisterNetworkCallback(callback); @@ -247,7 +247,7 @@ public class ConnectivityManagerTest { // callback triggers captor.getValue().send(makeMessage(req1, ConnectivityManager.CALLBACK_AVAILABLE)); verify(callback, timeout(100).times(1)).onAvailable(any(Network.class), - any(NetworkCapabilities.class), any(LinkProperties.class)); + any(NetworkCapabilities.class), any(LinkProperties.class), anyBoolean()); // unregister callback manager.unregisterNetworkCallback(callback); diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java index 1c77fcc568f6..17bcea05b294 100644 --- a/tests/net/java/com/android/server/ConnectivityServiceTest.java +++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java @@ -51,6 +51,10 @@ import static android.net.NetworkCapabilities.TRANSPORT_ETHERNET; import static android.net.NetworkCapabilities.TRANSPORT_VPN; import static android.net.NetworkCapabilities.TRANSPORT_WIFI; import static android.net.NetworkCapabilities.TRANSPORT_WIFI_AWARE; +import static android.net.NetworkPolicyManager.RULE_ALLOW_METERED; +import static android.net.NetworkPolicyManager.RULE_NONE; +import static android.net.NetworkPolicyManager.RULE_REJECT_ALL; +import static android.net.NetworkPolicyManager.RULE_REJECT_METERED; import static com.android.internal.util.TestUtils.waitForIdleHandler; import static com.android.internal.util.TestUtils.waitForIdleLooper; @@ -92,6 +96,7 @@ import android.net.ConnectivityManager.PacketKeepalive; import android.net.ConnectivityManager.PacketKeepaliveCallback; import android.net.ConnectivityManager.TooManyRequestsException; import android.net.ConnectivityThread; +import android.net.INetworkPolicyListener; import android.net.INetworkPolicyManager; import android.net.INetworkStatsService; import android.net.InterfaceConfiguration; @@ -148,6 +153,7 @@ import com.android.server.connectivity.MockableSystemProperties; import com.android.server.connectivity.Nat464Xlat; import com.android.server.connectivity.NetworkAgentInfo; import com.android.server.connectivity.NetworkMonitor; +import com.android.server.connectivity.Tethering; import com.android.server.connectivity.Vpn; import com.android.server.net.NetworkPinner; import com.android.server.net.NetworkPolicyManagerInternal; @@ -215,11 +221,13 @@ public class ConnectivityServiceTest { private MockNetworkAgent mEthernetNetworkAgent; private MockVpn mMockVpn; private Context mContext; + private INetworkPolicyListener mPolicyListener; @Mock IpConnectivityMetrics.Logger mMetricsService; @Mock DefaultNetworkMetrics mDefaultNetworkMetrics; @Mock INetworkManagementService mNetworkManagementService; @Mock INetworkStatsService mStatsService; + @Mock INetworkPolicyManager mNpm; private ArgumentCaptor<String[]> mStringArrayCaptor = ArgumentCaptor.forClass(String[].class); @@ -934,6 +942,11 @@ public class ConnectivityServiceTest { } @Override + protected Tethering makeTethering() { + return mock(Tethering.class); + } + + @Override protected int reserveNetId() { while (true) { final int netId = super.reserveNetId(); @@ -1023,6 +1036,20 @@ public class ConnectivityServiceTest { public void waitForIdle() { waitForIdle(TIMEOUT_MS); } + + public void setUidRulesChanged(int uidRules) { + try { + mPolicyListener.onUidRulesChanged(Process.myUid(), uidRules); + } catch (RemoteException ignored) { + } + } + + public void setRestrictBackgroundChanged(boolean restrictBackground) { + try { + mPolicyListener.onRestrictBackgroundChanged(restrictBackground); + } catch (RemoteException ignored) { + } + } } /** @@ -1055,12 +1082,18 @@ public class ConnectivityServiceTest { LocalServices.removeServiceForTest(NetworkPolicyManagerInternal.class); LocalServices.addService( NetworkPolicyManagerInternal.class, mock(NetworkPolicyManagerInternal.class)); + mService = new WrappedConnectivityService(mServiceContext, mNetworkManagementService, mStatsService, - mock(INetworkPolicyManager.class), + mNpm, mock(IpConnectivityLog.class)); + final ArgumentCaptor<INetworkPolicyListener> policyListenerCaptor = + ArgumentCaptor.forClass(INetworkPolicyListener.class); + verify(mNpm).registerListener(policyListenerCaptor.capture()); + mPolicyListener = policyListenerCaptor.getValue(); + // Create local CM before sending system ready so that we can answer // getSystemService() correctly. mCm = new WrappedConnectivityManager(InstrumentationRegistry.getContext(), mService); @@ -1441,7 +1474,8 @@ public class ConnectivityServiceTest { RESUMED, LOSING, LOST, - UNAVAILABLE + UNAVAILABLE, + BLOCKED_STATUS } private static class CallbackInfo { @@ -1522,6 +1556,11 @@ public class ConnectivityServiceTest { setLastCallback(CallbackState.LOST, network, null); } + @Override + public void onBlockedStatusChanged(Network network, boolean blocked) { + setLastCallback(CallbackState.BLOCKED_STATUS, network, blocked); + } + public Network getLastAvailableNetwork() { return mLastAvailableNetwork; } @@ -1582,6 +1621,7 @@ public class ConnectivityServiceTest { // - onSuspended, iff the network was suspended when the callbacks fire. // - onCapabilitiesChanged. // - onLinkPropertiesChanged. + // - onBlockedStatusChanged. // // @param agent the network to expect the callbacks on. // @param expectSuspended whether to expect a SUSPENDED callback. @@ -1589,7 +1629,7 @@ public class ConnectivityServiceTest { // onCapabilitiesChanged callback. // @param timeoutMs how long to wait for the callbacks. void expectAvailableCallbacks(MockNetworkAgent agent, boolean expectSuspended, - boolean expectValidated, int timeoutMs) { + boolean expectValidated, boolean expectBlocked, int timeoutMs) { expectCallback(CallbackState.AVAILABLE, agent, timeoutMs); if (expectSuspended) { expectCallback(CallbackState.SUSPENDED, agent, timeoutMs); @@ -1600,19 +1640,28 @@ public class ConnectivityServiceTest { expectCapabilitiesWithout(NET_CAPABILITY_VALIDATED, agent, timeoutMs); } expectCallback(CallbackState.LINK_PROPERTIES, agent, timeoutMs); + expectBlockedStatusCallback(expectBlocked, agent); } // Expects the available callbacks (validated), plus onSuspended. void expectAvailableAndSuspendedCallbacks(MockNetworkAgent agent, boolean expectValidated) { - expectAvailableCallbacks(agent, true, expectValidated, TEST_CALLBACK_TIMEOUT_MS); + expectAvailableCallbacks(agent, true, expectValidated, false, TEST_CALLBACK_TIMEOUT_MS); } void expectAvailableCallbacksValidated(MockNetworkAgent agent) { - expectAvailableCallbacks(agent, false, true, TEST_CALLBACK_TIMEOUT_MS); + expectAvailableCallbacks(agent, false, true, false, TEST_CALLBACK_TIMEOUT_MS); + } + + void expectAvailableCallbacksValidatedAndBlocked(MockNetworkAgent agent) { + expectAvailableCallbacks(agent, false, true, true, TEST_CALLBACK_TIMEOUT_MS); } void expectAvailableCallbacksUnvalidated(MockNetworkAgent agent) { - expectAvailableCallbacks(agent, false, false, TEST_CALLBACK_TIMEOUT_MS); + expectAvailableCallbacks(agent, false, false, false, TEST_CALLBACK_TIMEOUT_MS); + } + + void expectAvailableCallbacksUnvalidatedAndBlocked(MockNetworkAgent agent) { + expectAvailableCallbacks(agent, false, false, true, TEST_CALLBACK_TIMEOUT_MS); } // Expects the available callbacks (where the onCapabilitiesChanged must contain the @@ -1623,6 +1672,9 @@ public class ConnectivityServiceTest { expectCallback(CallbackState.AVAILABLE, agent, TEST_CALLBACK_TIMEOUT_MS); NetworkCapabilities nc1 = expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, agent); expectCallback(CallbackState.LINK_PROPERTIES, agent, TEST_CALLBACK_TIMEOUT_MS); + // Implicitly check the network is allowed to use. + // TODO: should we need to consider if network is in blocked status in this case? + expectBlockedStatusCallback(false, agent); NetworkCapabilities nc2 = expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, agent); assertEquals(nc1, nc2); } @@ -1665,6 +1717,12 @@ public class ConnectivityServiceTest { fn.test((NetworkCapabilities) cbi.arg)); } + void expectBlockedStatusCallback(boolean expectBlocked, MockNetworkAgent agent) { + CallbackInfo cbi = expectCallback(CallbackState.BLOCKED_STATUS, agent); + boolean actualBlocked = (boolean) cbi.arg; + assertEquals(expectBlocked, actualBlocked); + } + void assertNoCallback() { waitForIdle(); CallbackInfo c = mCallbacks.peek(); @@ -3223,7 +3281,7 @@ public class ConnectivityServiceTest { mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); mWiFiNetworkAgent.connect(false); - networkCallback.expectAvailableCallbacks(mWiFiNetworkAgent, false, false, + networkCallback.expectAvailableCallbacks(mWiFiNetworkAgent, false, false, false, TEST_CALLBACK_TIMEOUT_MS); // pass timeout and validate that UNAVAILABLE is not called @@ -3243,7 +3301,7 @@ public class ConnectivityServiceTest { mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); mWiFiNetworkAgent.connect(false); - networkCallback.expectAvailableCallbacks(mWiFiNetworkAgent, false, false, + networkCallback.expectAvailableCallbacks(mWiFiNetworkAgent, false, false, false, TEST_CALLBACK_TIMEOUT_MS); mWiFiNetworkAgent.disconnect(); networkCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent); @@ -3802,6 +3860,7 @@ public class ConnectivityServiceTest { networkCallback.expectCallback(CallbackState.NETWORK_CAPABILITIES, networkAgent); CallbackInfo cbi = networkCallback.expectCallback(CallbackState.LINK_PROPERTIES, networkAgent); + networkCallback.expectCallback(CallbackState.BLOCKED_STATUS, networkAgent); networkCallback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, networkAgent); networkCallback.assertNoCallback(); checkDirectlyConnectedRoutes(cbi.arg, Arrays.asList(myIpv4Address), @@ -4010,6 +4069,7 @@ public class ConnectivityServiceTest { mCellNetworkAgent); CallbackInfo cbi = cellNetworkCallback.expectCallback( CallbackState.LINK_PROPERTIES, mCellNetworkAgent); + cellNetworkCallback.expectCallback(CallbackState.BLOCKED_STATUS, mCellNetworkAgent); cellNetworkCallback.assertNoCallback(); assertFalse(((LinkProperties)cbi.arg).isPrivateDnsActive()); assertNull(((LinkProperties)cbi.arg).getPrivateDnsServerName()); @@ -4068,6 +4128,7 @@ public class ConnectivityServiceTest { mCellNetworkAgent); CallbackInfo cbi = cellNetworkCallback.expectCallback( CallbackState.LINK_PROPERTIES, mCellNetworkAgent); + cellNetworkCallback.expectCallback(CallbackState.BLOCKED_STATUS, mCellNetworkAgent); cellNetworkCallback.assertNoCallback(); assertFalse(((LinkProperties)cbi.arg).isPrivateDnsActive()); assertNull(((LinkProperties)cbi.arg).getPrivateDnsServerName()); @@ -4444,6 +4505,101 @@ public class ConnectivityServiceTest { mMockVpn.disconnect(); } + @Test + public void testNetworkBlockedStatus() { + final TestNetworkCallback cellNetworkCallback = new TestNetworkCallback(); + final NetworkRequest cellRequest = new NetworkRequest.Builder() + .addTransportType(TRANSPORT_CELLULAR) + .build(); + mCm.registerNetworkCallback(cellRequest, cellNetworkCallback); + + mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); + mCellNetworkAgent.connect(true); + cellNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); + + mService.setUidRulesChanged(RULE_REJECT_ALL); + cellNetworkCallback.expectBlockedStatusCallback(true, mCellNetworkAgent); + + // ConnectivityService should cache it not to invoke the callback again. + mService.setUidRulesChanged(RULE_REJECT_METERED); + cellNetworkCallback.assertNoCallback(); + + mService.setUidRulesChanged(RULE_NONE); + cellNetworkCallback.expectBlockedStatusCallback(false, mCellNetworkAgent); + + mService.setUidRulesChanged(RULE_REJECT_METERED); + cellNetworkCallback.expectBlockedStatusCallback(true, mCellNetworkAgent); + + // Restrict the network based on UID rule and NOT_METERED capability change. + mCellNetworkAgent.addCapability(NET_CAPABILITY_NOT_METERED); + cellNetworkCallback.expectCapabilitiesWith(NET_CAPABILITY_NOT_METERED, mCellNetworkAgent); + cellNetworkCallback.expectBlockedStatusCallback(false, mCellNetworkAgent); + mCellNetworkAgent.removeCapability(NET_CAPABILITY_NOT_METERED); + cellNetworkCallback.expectCapabilitiesWithout(NET_CAPABILITY_NOT_METERED, + mCellNetworkAgent); + cellNetworkCallback.expectBlockedStatusCallback(true, mCellNetworkAgent); + mService.setUidRulesChanged(RULE_ALLOW_METERED); + cellNetworkCallback.expectBlockedStatusCallback(false, mCellNetworkAgent); + + mService.setUidRulesChanged(RULE_NONE); + cellNetworkCallback.assertNoCallback(); + + // Restrict the network based on BackgroundRestricted. + mService.setRestrictBackgroundChanged(true); + cellNetworkCallback.expectBlockedStatusCallback(true, mCellNetworkAgent); + mService.setRestrictBackgroundChanged(true); + cellNetworkCallback.assertNoCallback(); + mService.setRestrictBackgroundChanged(false); + cellNetworkCallback.expectBlockedStatusCallback(false, mCellNetworkAgent); + cellNetworkCallback.assertNoCallback(); + + mCm.unregisterNetworkCallback(cellNetworkCallback); + } + + @Test + public void testNetworkBlockedStatusBeforeAndAfterConnect() { + final TestNetworkCallback defaultCallback = new TestNetworkCallback(); + mCm.registerDefaultNetworkCallback(defaultCallback); + + // No Networkcallbacks invoked before any network is active. + mService.setUidRulesChanged(RULE_REJECT_ALL); + mService.setUidRulesChanged(RULE_NONE); + mService.setUidRulesChanged(RULE_REJECT_METERED); + defaultCallback.assertNoCallback(); + + mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); + mCellNetworkAgent.connect(true); + defaultCallback.expectAvailableCallbacksUnvalidatedAndBlocked(mCellNetworkAgent); + defaultCallback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mCellNetworkAgent); + + // Allow to use the network after switching to NOT_METERED network. + mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); + mWiFiNetworkAgent.addCapability(NET_CAPABILITY_NOT_METERED); + mWiFiNetworkAgent.connect(true); + defaultCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent); + + // Switch to METERED network. Restrict the use of the network. + mWiFiNetworkAgent.disconnect(); + defaultCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent); + defaultCallback.expectAvailableCallbacksValidatedAndBlocked(mCellNetworkAgent); + + // Network becomes NOT_METERED. + mCellNetworkAgent.addCapability(NET_CAPABILITY_NOT_METERED); + defaultCallback.expectCapabilitiesWith(NET_CAPABILITY_NOT_METERED, mCellNetworkAgent); + defaultCallback.expectBlockedStatusCallback(false, mCellNetworkAgent); + + // Verify there's no Networkcallbacks invoked after data saver on/off. + mService.setRestrictBackgroundChanged(true); + mService.setRestrictBackgroundChanged(false); + defaultCallback.assertNoCallback(); + + mCellNetworkAgent.disconnect(); + defaultCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent); + defaultCallback.assertNoCallback(); + + mCm.unregisterNetworkCallback(defaultCallback); + } + /** * Make simulated InterfaceConfig for Nat464Xlat to query clat lower layer info. */ |