diff options
86 files changed, 2252 insertions, 619 deletions
diff --git a/api/current.txt b/api/current.txt index b325198658b8..85f238c1113d 100644 --- a/api/current.txt +++ b/api/current.txt @@ -6459,7 +6459,7 @@ package android.app.admin { method public android.content.ComponentName getMandatoryBackupTransport(); method public int getMaximumFailedPasswordsForWipe(android.content.ComponentName); method public long getMaximumTimeToLock(android.content.ComponentName); - method public java.util.List<java.lang.String> getMeteredDataDisabled(android.content.ComponentName); + method public java.util.List<java.lang.String> getMeteredDataDisabledPackages(android.content.ComponentName); method public int getOrganizationColor(android.content.ComponentName); method public java.lang.CharSequence getOrganizationName(android.content.ComponentName); method public java.util.List<android.telephony.data.ApnSetting> getOverrideApns(android.content.ComponentName); @@ -6567,7 +6567,7 @@ package android.app.admin { method public void setMasterVolumeMuted(android.content.ComponentName, boolean); method public void setMaximumFailedPasswordsForWipe(android.content.ComponentName, int); method public void setMaximumTimeToLock(android.content.ComponentName, long); - method public java.util.List<java.lang.String> setMeteredDataDisabled(android.content.ComponentName, java.util.List<java.lang.String>); + method public java.util.List<java.lang.String> setMeteredDataDisabledPackages(android.content.ComponentName, java.util.List<java.lang.String>); method public void setNetworkLoggingEnabled(android.content.ComponentName, boolean); method public void setOrganizationColor(android.content.ComponentName, int); method public void setOrganizationName(android.content.ComponentName, java.lang.CharSequence); @@ -7206,8 +7206,9 @@ package android.app.slice { field public static final java.lang.String HINT_ACTIONS = "actions"; field public static final java.lang.String HINT_ERROR = "error"; field public static final java.lang.String HINT_HORIZONTAL = "horizontal"; - field public static final java.lang.String HINT_KEY_WORDS = "key_words"; + field public static final java.lang.String HINT_KEYWORDS = "keywords"; field public static final java.lang.String HINT_LARGE = "large"; + field public static final java.lang.String HINT_LAST_UPDATED = "last_updated"; field public static final java.lang.String HINT_LIST = "list"; field public static final java.lang.String HINT_LIST_ITEM = "list_item"; field public static final java.lang.String HINT_NO_TINT = "no_tint"; @@ -7217,10 +7218,12 @@ package android.app.slice { field public static final java.lang.String HINT_SHORTCUT = "shortcut"; field public static final java.lang.String HINT_SUMMARY = "summary"; field public static final java.lang.String HINT_TITLE = "title"; + field public static final java.lang.String HINT_TTL = "ttl"; field public static final java.lang.String SUBTYPE_COLOR = "color"; field public static final java.lang.String SUBTYPE_CONTENT_DESCRIPTION = "content_description"; field public static final java.lang.String SUBTYPE_MAX = "max"; field public static final java.lang.String SUBTYPE_MESSAGE = "message"; + field public static final java.lang.String SUBTYPE_MILLIS = "millis"; field public static final java.lang.String SUBTYPE_PRIORITY = "priority"; field public static final java.lang.String SUBTYPE_RANGE = "range"; field public static final deprecated java.lang.String SUBTYPE_SLIDER = "slider"; @@ -7291,7 +7294,7 @@ package android.app.slice { public class SliceMetrics { ctor public SliceMetrics(android.content.Context, android.net.Uri); method public void logHidden(); - method public void logTouch(android.net.Uri); + method public void logTouch(int, android.net.Uri); method public void logVisible(); } @@ -16156,6 +16159,7 @@ package android.hardware.camera2 { field public static final int REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA = 11; // 0xb field public static final int REQUEST_AVAILABLE_CAPABILITIES_MANUAL_POST_PROCESSING = 2; // 0x2 field public static final int REQUEST_AVAILABLE_CAPABILITIES_MANUAL_SENSOR = 1; // 0x1 + field public static final int REQUEST_AVAILABLE_CAPABILITIES_MONOCHROME = 12; // 0xc field public static final int REQUEST_AVAILABLE_CAPABILITIES_MOTION_TRACKING = 10; // 0xa field public static final int REQUEST_AVAILABLE_CAPABILITIES_PRIVATE_REPROCESSING = 4; // 0x4 field public static final int REQUEST_AVAILABLE_CAPABILITIES_RAW = 3; // 0x3 @@ -28642,6 +28646,8 @@ package android.net.wifi.rtt { method public int getDistanceMm(); method public int getDistanceStdDevMm(); method public android.net.MacAddress getMacAddress(); + method public int getNumAttemptedMeasurements(); + method public int getNumSuccessfulMeasurements(); method public android.net.wifi.aware.PeerHandle getPeerHandle(); method public long getRangingTimestampMillis(); method public int getRssi(); @@ -50315,38 +50321,13 @@ package android.view.inputmethod { package android.view.textclassifier { - public abstract class Logger { - ctor public Logger(android.view.textclassifier.Logger.Config); - method public java.text.BreakIterator getTokenIterator(java.util.Locale); - method public boolean isSmartSelection(java.lang.String); - method public final void logSelectionActionEvent(int, int, int); - method public final void logSelectionActionEvent(int, int, int, android.view.textclassifier.TextClassification); - method public final void logSelectionModifiedEvent(int, int); - method public final void logSelectionModifiedEvent(int, int, android.view.textclassifier.TextClassification); - method public final void logSelectionModifiedEvent(int, int, android.view.textclassifier.TextSelection); - method public final void logSelectionStartedEvent(int, int); - method public abstract void writeEvent(android.view.textclassifier.SelectionEvent); - field public static final int OUT_OF_BOUNDS = 2147483647; // 0x7fffffff - field public static final int OUT_OF_BOUNDS_NEGATIVE = -2147483648; // 0x80000000 - field public static final java.lang.String WIDGET_CUSTOM_EDITTEXT = "customedit"; - field public static final java.lang.String WIDGET_CUSTOM_TEXTVIEW = "customview"; - field public static final java.lang.String WIDGET_CUSTOM_UNSELECTABLE_TEXTVIEW = "nosel-customview"; - field public static final java.lang.String WIDGET_EDITTEXT = "edittext"; - field public static final java.lang.String WIDGET_EDIT_WEBVIEW = "edit-webview"; - field public static final java.lang.String WIDGET_TEXTVIEW = "textview"; - field public static final java.lang.String WIDGET_UNKNOWN = "unknown"; - field public static final java.lang.String WIDGET_UNSELECTABLE_TEXTVIEW = "nosel-textview"; - field public static final java.lang.String WIDGET_WEBVIEW = "webview"; - } - - public static final class Logger.Config { - ctor public Logger.Config(android.content.Context, java.lang.String, java.lang.String); - method public java.lang.String getPackageName(); - method public java.lang.String getWidgetType(); - method public java.lang.String getWidgetVersion(); - } - public final class SelectionEvent implements android.os.Parcelable { + method public static android.view.textclassifier.SelectionEvent createSelectionActionEvent(int, int, int); + method public static android.view.textclassifier.SelectionEvent createSelectionActionEvent(int, int, int, android.view.textclassifier.TextClassification); + method public static android.view.textclassifier.SelectionEvent createSelectionModifiedEvent(int, int); + method public static android.view.textclassifier.SelectionEvent createSelectionModifiedEvent(int, int, android.view.textclassifier.TextClassification); + method public static android.view.textclassifier.SelectionEvent createSelectionModifiedEvent(int, int, android.view.textclassifier.TextSelection); + method public static android.view.textclassifier.SelectionEvent createSelectionStartedEvent(int, int); method public int describeContents(); method public long getDurationSincePreviousEvent(); method public long getDurationSinceSessionStart(); @@ -50357,13 +50338,14 @@ package android.view.textclassifier { method public int getEventType(); method public int getInvocationMethod(); method public java.lang.String getPackageName(); - method public java.lang.String getSessionId(); + method public android.view.textclassifier.TextClassificationSessionId getSessionId(); method public java.lang.String getSignature(); method public int getSmartEnd(); method public int getSmartStart(); method public int getStart(); method public java.lang.String getWidgetType(); method public java.lang.String getWidgetVersion(); + method public static boolean isTerminal(int); method public void writeToParcel(android.os.Parcel, int); field public static final int ACTION_ABANDON = 107; // 0x6b field public static final int ACTION_COPY = 101; // 0x65 @@ -50384,6 +50366,7 @@ package android.view.textclassifier { field public static final int EVENT_SMART_SELECTION_SINGLE = 3; // 0x3 field public static final int INVOCATION_LINK = 2; // 0x2 field public static final int INVOCATION_MANUAL = 1; // 0x1 + field public static final int INVOCATION_UNKNOWN = 0; // 0x0 } public final class TextClassification implements android.os.Parcelable { @@ -50426,19 +50409,45 @@ package android.view.textclassifier { field public static final android.os.Parcelable.Creator<android.view.textclassifier.TextClassification.Options> CREATOR; } + public final class TextClassificationContext { + method public java.lang.String getPackageName(); + method public java.lang.String getWidgetType(); + method public java.lang.String getWidgetVersion(); + } + + public static final class TextClassificationContext.Builder { + ctor public TextClassificationContext.Builder(java.lang.String, java.lang.String); + method public android.view.textclassifier.TextClassificationContext build(); + method public android.view.textclassifier.TextClassificationContext.Builder setWidgetVersion(java.lang.String); + } + public final class TextClassificationManager { + method public android.view.textclassifier.TextClassifier createTextClassificationSession(android.view.textclassifier.TextClassificationContext); method public android.view.textclassifier.TextClassifier getTextClassifier(); + method public void setTextClassificationSessionFactory(android.view.textclassifier.TextClassificationSessionFactory); method public void setTextClassifier(android.view.textclassifier.TextClassifier); } + public abstract interface TextClassificationSessionFactory { + method public abstract android.view.textclassifier.TextClassifier createTextClassificationSession(android.view.textclassifier.TextClassificationContext); + } + + public final class TextClassificationSessionId implements android.os.Parcelable { + method public int describeContents(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.view.textclassifier.TextClassificationSessionId> CREATOR; + } + public abstract interface TextClassifier { method public default android.view.textclassifier.TextClassification classifyText(java.lang.CharSequence, int, int, android.view.textclassifier.TextClassification.Options); method public default android.view.textclassifier.TextClassification classifyText(java.lang.CharSequence, int, int); 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.TextLinks generateLinks(java.lang.CharSequence, android.view.textclassifier.TextLinks.Options); method public default android.view.textclassifier.TextLinks generateLinks(java.lang.CharSequence); - method public default android.view.textclassifier.Logger getLogger(android.view.textclassifier.Logger.Config); method public default int getMaxGenerateLinksTextLength(); + method public default boolean isDestroyed(); + method public default void onSelectionEvent(android.view.textclassifier.SelectionEvent); method public default android.view.textclassifier.TextSelection suggestSelection(java.lang.CharSequence, int, int, android.view.textclassifier.TextSelection.Options); method public default android.view.textclassifier.TextSelection suggestSelection(java.lang.CharSequence, int, int); method public default android.view.textclassifier.TextSelection suggestSelection(java.lang.CharSequence, int, int, android.os.LocaleList); @@ -50454,6 +50463,15 @@ package android.view.textclassifier { field public static final java.lang.String TYPE_PHONE = "phone"; field public static final java.lang.String TYPE_UNKNOWN = ""; field public static final java.lang.String TYPE_URL = "url"; + field public static final java.lang.String WIDGET_TYPE_CUSTOM_EDITTEXT = "customedit"; + field public static final java.lang.String WIDGET_TYPE_CUSTOM_TEXTVIEW = "customview"; + field public static final java.lang.String WIDGET_TYPE_CUSTOM_UNSELECTABLE_TEXTVIEW = "nosel-customview"; + field public static final java.lang.String WIDGET_TYPE_EDITTEXT = "edittext"; + field public static final java.lang.String WIDGET_TYPE_EDIT_WEBVIEW = "edit-webview"; + field public static final java.lang.String WIDGET_TYPE_TEXTVIEW = "textview"; + field public static final java.lang.String WIDGET_TYPE_UNKNOWN = "unknown"; + field public static final java.lang.String WIDGET_TYPE_UNSELECTABLE_TEXTVIEW = "nosel-textview"; + field public static final java.lang.String WIDGET_TYPE_WEBVIEW = "webview"; } public static final class TextClassifier.EntityConfig implements android.os.Parcelable { diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto index c8ae42695eb1..5e75359f111e 100644 --- a/cmds/statsd/src/atoms.proto +++ b/cmds/statsd/src/atoms.proto @@ -49,7 +49,7 @@ message Atom { oneof pushed { // For StatsLog reasons, 1 is illegal and will not work. Must start at 2. BleScanStateChanged ble_scan_state_changed = 2; - BleUnoptimizedScanStateChanged ble_unoptimized_scan_state_changed = 3; + // TODO: 3 is blank, but need not be BleScanResultReceived ble_scan_result_received = 4; SensorStateChanged sensor_state_changed = 5; GpsScanStateChanged gps_scan_state_changed = 6; @@ -247,31 +247,24 @@ message ProcessLifeCycleStateChanged { * Logged from: * frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java */ +// TODO: Consider changing to tracking per-scanner-id (log from AppScanStats). message BleScanStateChanged { repeated AttributionNode attribution_node = 1; enum State { OFF = 0; ON = 1; + // RESET indicates all ble stopped. Used when it (re)starts (e.g. after it crashes). + RESET = 2; } optional State state = 2; -} - -/** - * Logs when an unoptimized ble scan state changes. - * - * Logged from: - * frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java - */ -// TODO: Consider changing to tracking per-scanner-id (log from AppScanStats). -message BleUnoptimizedScanStateChanged { - repeated AttributionNode attribution_node = 1; - enum State { - OFF = 0; - ON = 1; - } - optional State state = 2; + // Does the scan have a filter. + optional bool is_filtered = 3; + // Whether the scan is a CALLBACK_TYPE_FIRST_MATCH scan. Called 'background' scan internally. + optional bool is_first_match = 4; + // Whether the scan set to piggy-back off the results of other scans (SCAN_MODE_OPPORTUNISTIC). + optional bool is_opportunistic = 5; } /** diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index 3696eaee0913..20149dec4dad 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -425,7 +425,7 @@ import java.util.List; * vs. those targeting prior platforms. Starting with Honeycomb, an application * is not in the killable state until its {@link #onStop} has returned. This * impacts when {@link #onSaveInstanceState(Bundle)} may be called (it may be - * safely called after {@link #onPause()} and allows and application to safely + * safely called after {@link #onPause()}) and allows an application to safely * wait until {@link #onStop()} to save persistent state.</p> * * <p class="note">For applications targeting platforms starting with diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 436947f3a9a5..aec33b35849a 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -8382,12 +8382,12 @@ public class DevicePolicyManager { * @return a list of package names which could not be restricted. * @throws SecurityException if {@code admin} is not a device or profile owner. */ - public @NonNull List<String> setMeteredDataDisabled(@NonNull ComponentName admin, + public @NonNull List<String> setMeteredDataDisabledPackages(@NonNull ComponentName admin, @NonNull List<String> packageNames) { throwIfParentInstance("setMeteredDataDisabled"); if (mService != null) { try { - return mService.setMeteredDataDisabled(admin, packageNames); + return mService.setMeteredDataDisabledPackages(admin, packageNames); } catch (RemoteException re) { throw re.rethrowFromSystemServer(); } @@ -8403,11 +8403,11 @@ public class DevicePolicyManager { * @return the list of restricted package names. * @throws SecurityException if {@code admin} is not a device or profile owner. */ - public @NonNull List<String> getMeteredDataDisabled(@NonNull ComponentName admin) { + public @NonNull List<String> getMeteredDataDisabledPackages(@NonNull ComponentName admin) { throwIfParentInstance("getMeteredDataDisabled"); if (mService != null) { try { - return mService.getMeteredDataDisabled(admin); + return mService.getMeteredDataDisabledPackages(admin); } catch (RemoteException re) { throw re.rethrowFromSystemServer(); } @@ -8426,12 +8426,12 @@ public class DevicePolicyManager { * @throws SecurityException if the caller doesn't run with {@link Process#SYSTEM_UID} * @hide */ - public boolean isMeteredDataDisabledForUser(@NonNull ComponentName admin, String packageName, - @UserIdInt int userId) { + public boolean isMeteredDataDisabledPackageForUser(@NonNull ComponentName admin, + String packageName, @UserIdInt int userId) { throwIfParentInstance("getMeteredDataDisabledForUser"); if (mService != null) { try { - return mService.isMeteredDataDisabledForUser(admin, packageName, userId); + return mService.isMeteredDataDisabledPackageForUser(admin, packageName, userId); } 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 c46402faa07b..4b39a9a53252 100644 --- a/core/java/android/app/admin/IDevicePolicyManager.aidl +++ b/core/java/android/app/admin/IDevicePolicyManager.aidl @@ -404,8 +404,8 @@ interface IDevicePolicyManager { CharSequence getStartUserSessionMessage(in ComponentName admin); CharSequence getEndUserSessionMessage(in ComponentName admin); - List<String> setMeteredDataDisabled(in ComponentName admin, in List<String> packageNames); - List<String> getMeteredDataDisabled(in ComponentName admin); + List<String> setMeteredDataDisabledPackages(in ComponentName admin, in List<String> packageNames); + List<String> getMeteredDataDisabledPackages(in ComponentName admin); int addOverrideApn(in ComponentName admin, in ApnSetting apnSetting); boolean updateOverrideApn(in ComponentName admin, int apnId, in ApnSetting apnSetting); @@ -414,5 +414,5 @@ interface IDevicePolicyManager { void setOverrideApnsEnabled(in ComponentName admin, boolean enabled); boolean isOverrideApnEnabled(in ComponentName admin); - boolean isMeteredDataDisabledForUser(in ComponentName admin, String packageName, int userId); + boolean isMeteredDataDisabledPackageForUser(in ComponentName admin, String packageName, int userId); } diff --git a/core/java/android/app/backup/FullBackup.java b/core/java/android/app/backup/FullBackup.java index fb1c2d085df6..b7a8da59b170 100644 --- a/core/java/android/app/backup/FullBackup.java +++ b/core/java/android/app/backup/FullBackup.java @@ -84,6 +84,8 @@ public class FullBackup { public static final String FLAG_REQUIRED_CLIENT_SIDE_ENCRYPTION = "clientSideEncryption"; public static final String FLAG_REQUIRED_DEVICE_TO_DEVICE_TRANSFER = "deviceToDeviceTransfer"; + public static final String FLAG_REQUIRED_FAKE_CLIENT_SIDE_ENCRYPTION = + "fakeClientSideEncryption"; /** * @hide @@ -600,6 +602,8 @@ public class FullBackup { case FLAG_REQUIRED_DEVICE_TO_DEVICE_TRANSFER: flags |= BackupAgent.FLAG_DEVICE_TO_DEVICE_TRANSFER; break; + case FLAG_REQUIRED_FAKE_CLIENT_SIDE_ENCRYPTION: + flags |= BackupAgent.FLAG_FAKE_CLIENT_SIDE_ENCRYPTION_ENABLED; default: Log.w(TAG, "Unrecognized requiredFlag provided, value: \"" + f + "\""); } diff --git a/core/java/android/app/slice/Slice.java b/core/java/android/app/slice/Slice.java index 61679cb4d631..95bb1f682701 100644 --- a/core/java/android/app/slice/Slice.java +++ b/core/java/android/app/slice/Slice.java @@ -66,8 +66,10 @@ public final class Slice implements Parcelable { HINT_HORIZONTAL, HINT_PARTIAL, HINT_SEE_MORE, - HINT_KEY_WORDS, + HINT_KEYWORDS, HINT_ERROR, + HINT_TTL, + HINT_LAST_UPDATED, }) @Retention(RetentionPolicy.SOURCE) public @interface SliceHint {} @@ -168,12 +170,20 @@ public final class Slice implements Parcelable { * related to the parent slice. * Expected to be on an item of format {@link SliceItem#FORMAT_SLICE}. */ - public static final String HINT_KEY_WORDS = "key_words"; + public static final String HINT_KEYWORDS = "keywords"; /** * A hint to indicate that this slice represents an error. */ public static final String HINT_ERROR = "error"; /** + * Hint indicating an item representing a time-to-live for the content. + */ + public static final String HINT_TTL = "ttl"; + /** + * Hint indicating an item representing when the content was created or last updated. + */ + public static final String HINT_LAST_UPDATED = "last_updated"; + /** * Key to retrieve an extra added to an intent when a control is changed. */ public static final String EXTRA_TOGGLE_STATE = "android.app.slice.extra.TOGGLE_STATE"; @@ -243,6 +253,11 @@ public final class Slice implements Parcelable { * Expected to be on an item of format {@link SliceItem#FORMAT_TEXT}. */ public static final String SUBTYPE_CONTENT_DESCRIPTION = "content_description"; + /** + * Subtype to tag an item as representing a time in milliseconds since midnight, + * January 1, 1970 UTC. + */ + public static final String SUBTYPE_MILLIS = "millis"; private final SliceItem[] mItems; private final @SliceHint String[] mHints; diff --git a/core/java/android/app/slice/SliceMetrics.java b/core/java/android/app/slice/SliceMetrics.java index a7069bc107d4..20c1390b25ff 100644 --- a/core/java/android/app/slice/SliceMetrics.java +++ b/core/java/android/app/slice/SliceMetrics.java @@ -25,7 +25,7 @@ import com.android.internal.logging.MetricsLogger; /** * Metrics interface for slices. * - * This is called by SliceView, so Slice develoers should + * This is called by SliceView, so Slice developers should * not need to reference this class. * * @see androidx.slice.widget.SliceView @@ -55,9 +55,18 @@ public class SliceMetrics { } /** - * To be called whenever the use interacts with a slice. - *@param subSlice The URI of the sub-slice that is the subject of the interaction. + * To be called whenever the user invokes a discrete action via a slice. + * + * <P> + * Use this for discrete events like a tap or the end of a drag, + * not for a continuous streams of events, such as the motion during a gesture. + * </P> + * + * @see androidx.slice.widget.EventInfo#actionType + * + * @param actionType The type of the event. + * @param subSlice The URI of the sub-slice that is the subject of the interaction. */ - public void logTouch(@NonNull Uri subSlice) { + public void logTouch(int actionType, @NonNull Uri subSlice) { } } diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index 02f0ded3fa92..d4ec5d509fcd 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -266,8 +266,8 @@ import java.util.Set; * </ul> * * <p>For example, consider the Note Pad sample application that - * allows user to browse through a list of notes data and view details about - * individual items. Text in italics indicate places were you would replace a + * allows a user to browse through a list of notes data and view details about + * individual items. Text in italics indicates places where you would replace a * name with one specific to your own package.</p> * * <pre> <manifest xmlns:android="http://schemas.android.com/apk/res/android" diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java index e6aaab13e841..4279b1972058 100644 --- a/core/java/android/hardware/camera2/CameraCharacteristics.java +++ b/core/java/android/hardware/camera2/CameraCharacteristics.java @@ -1708,6 +1708,7 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * <li>{@link #REQUEST_AVAILABLE_CAPABILITIES_CONSTRAINED_HIGH_SPEED_VIDEO CONSTRAINED_HIGH_SPEED_VIDEO}</li> * <li>{@link #REQUEST_AVAILABLE_CAPABILITIES_MOTION_TRACKING MOTION_TRACKING}</li> * <li>{@link #REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA LOGICAL_MULTI_CAMERA}</li> + * <li>{@link #REQUEST_AVAILABLE_CAPABILITIES_MONOCHROME MONOCHROME}</li> * </ul></p> * <p>This key is available on all devices.</p> * @@ -1724,6 +1725,7 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * @see #REQUEST_AVAILABLE_CAPABILITIES_CONSTRAINED_HIGH_SPEED_VIDEO * @see #REQUEST_AVAILABLE_CAPABILITIES_MOTION_TRACKING * @see #REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA + * @see #REQUEST_AVAILABLE_CAPABILITIES_MONOCHROME */ @PublicKey public static final Key<int[]> REQUEST_AVAILABLE_CAPABILITIES = diff --git a/core/java/android/hardware/camera2/CameraMetadata.java b/core/java/android/hardware/camera2/CameraMetadata.java index 7467c3aecb50..1a5d3ac94114 100644 --- a/core/java/android/hardware/camera2/CameraMetadata.java +++ b/core/java/android/hardware/camera2/CameraMetadata.java @@ -862,6 +862,13 @@ public abstract class CameraMetadata<TKey> { */ public static final int REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA = 11; + /** + * <p>The camera device is a monochrome camera that doesn't contain a color filter array, + * and the pixel values on U and Y planes are all 128.</p> + * @see CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES + */ + public static final int REQUEST_AVAILABLE_CAPABILITIES_MONOCHROME = 12; + // // Enumeration values for CameraCharacteristics#SCALER_CROPPING_TYPE // diff --git a/core/java/android/hardware/camera2/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java index d36785a7cfb6..22525719adac 100644 --- a/core/java/android/hardware/camera2/CaptureRequest.java +++ b/core/java/android/hardware/camera2/CaptureRequest.java @@ -2853,6 +2853,8 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> * of points can be less than max (that is, the request doesn't have to * always provide a curve with number of points equivalent to * {@link CameraCharacteristics#TONEMAP_MAX_CURVE_POINTS android.tonemap.maxCurvePoints}).</p> + * <p>For devices with MONOCHROME capability, only red channel is used. Green and blue channels + * are ignored.</p> * <p>A few examples, and their corresponding graphical mappings; these * only specify the red channel and the precision is limited to 4 * digits, for conciseness.</p> @@ -2915,6 +2917,8 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> * of points can be less than max (that is, the request doesn't have to * always provide a curve with number of points equivalent to * {@link CameraCharacteristics#TONEMAP_MAX_CURVE_POINTS android.tonemap.maxCurvePoints}).</p> + * <p>For devices with MONOCHROME capability, only red channel is used. Green and blue channels + * are ignored.</p> * <p>A few examples, and their corresponding graphical mappings; these * only specify the red channel and the precision is limited to 4 * digits, for conciseness.</p> diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java index bb8226082f03..8df54472492a 100644 --- a/core/java/android/hardware/camera2/CaptureResult.java +++ b/core/java/android/hardware/camera2/CaptureResult.java @@ -4096,6 +4096,8 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * of points can be less than max (that is, the request doesn't have to * always provide a curve with number of points equivalent to * {@link CameraCharacteristics#TONEMAP_MAX_CURVE_POINTS android.tonemap.maxCurvePoints}).</p> + * <p>For devices with MONOCHROME capability, only red channel is used. Green and blue channels + * are ignored.</p> * <p>A few examples, and their corresponding graphical mappings; these * only specify the red channel and the precision is limited to 4 * digits, for conciseness.</p> @@ -4158,6 +4160,8 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * of points can be less than max (that is, the request doesn't have to * always provide a curve with number of points equivalent to * {@link CameraCharacteristics#TONEMAP_MAX_CURVE_POINTS android.tonemap.maxCurvePoints}).</p> + * <p>For devices with MONOCHROME capability, only red channel is used. Green and blue channels + * are ignored.</p> * <p>A few examples, and their corresponding graphical mappings; these * only specify the red channel and the precision is limited to 4 * digits, for conciseness.</p> diff --git a/core/java/android/privacy/internal/longitudinalreporting/LongitudinalReportingEncoder.java b/core/java/android/privacy/internal/longitudinalreporting/LongitudinalReportingEncoder.java index 219868d663d2..dd97f1ef6a02 100644 --- a/core/java/android/privacy/internal/longitudinalreporting/LongitudinalReportingEncoder.java +++ b/core/java/android/privacy/internal/longitudinalreporting/LongitudinalReportingEncoder.java @@ -19,6 +19,7 @@ package android.privacy.internal.longitudinalreporting; import android.privacy.DifferentialPrivacyEncoder; import android.privacy.internal.rappor.RapporConfig; import android.privacy.internal.rappor.RapporEncoder; +import android.util.Log; import com.android.internal.annotations.VisibleForTesting; @@ -48,6 +49,9 @@ import com.android.internal.annotations.VisibleForTesting; */ public class LongitudinalReportingEncoder implements DifferentialPrivacyEncoder { + private static final String TAG = "LongitudinalEncoder"; + private static final boolean DEBUG = false; + // Suffix that will be added to Rappor's encoder id. There's a (relatively) small risk some // other Rappor encoder may re-use the same encoder id. private static final String PRR1_ENCODER_ID = "prr1_encoder_id"; @@ -121,11 +125,18 @@ public class LongitudinalReportingEncoder implements DifferentialPrivacyEncoder @Override public byte[] encodeBoolean(boolean original) { + if (DEBUG) { + Log.d(TAG, "encodeBoolean, encoderId:" + mConfig.getEncoderId() + ", original: " + + original); + } if (mFakeValue != null) { // Use the fake value generated in PRR. original = mFakeValue.booleanValue(); + if (DEBUG) Log.d(TAG, "Use fake value: " + original); } - return mIRREncoder.encodeBoolean(original); + byte[] result = mIRREncoder.encodeBoolean(original); + if (DEBUG) Log.d(TAG, "result: " + ((result[0] & 0x1) != 0)); + return result; } @Override diff --git a/core/java/android/view/textclassifier/DefaultLogger.java b/core/java/android/view/textclassifier/DefaultLogger.java index b2f4e399da5b..46ff44246280 100644 --- a/core/java/android/view/textclassifier/DefaultLogger.java +++ b/core/java/android/view/textclassifier/DefaultLogger.java @@ -39,7 +39,7 @@ import java.util.StringJoiner; public final class DefaultLogger extends Logger { private static final String LOG_TAG = "DefaultLogger"; - private static final String CLASSIFIER_ID = "androidtc"; + static final String CLASSIFIER_ID = "androidtc"; private static final int START_EVENT_DELTA = MetricsEvent.FIELD_SELECTION_SINCE_START; private static final int PREV_EVENT_DELTA = MetricsEvent.FIELD_SELECTION_SINCE_PREVIOUS; diff --git a/core/java/android/view/textclassifier/Logger.java b/core/java/android/view/textclassifier/Logger.java index 9c92fd4543e2..c29d3e64a8b7 100644 --- a/core/java/android/view/textclassifier/Logger.java +++ b/core/java/android/view/textclassifier/Logger.java @@ -18,56 +18,25 @@ package android.view.textclassifier; import android.annotation.NonNull; import android.annotation.Nullable; -import android.annotation.StringDef; import android.content.Context; -import android.util.Log; import com.android.internal.util.Preconditions; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; import java.text.BreakIterator; import java.util.Locale; import java.util.Objects; -import java.util.UUID; /** * A helper for logging TextClassifier related events. + * @hide */ public abstract class Logger { - /** - * Use this to specify an indeterminate positive index. - */ - public static final int OUT_OF_BOUNDS = Integer.MAX_VALUE; - - /** - * Use this to specify an indeterminate negative index. - */ - public static final int OUT_OF_BOUNDS_NEGATIVE = Integer.MIN_VALUE; - private static final String LOG_TAG = "Logger"; /* package */ static final boolean DEBUG_LOG_ENABLED = true; private static final String NO_SIGNATURE = ""; - /** @hide */ - @Retention(RetentionPolicy.SOURCE) - @StringDef({WIDGET_TEXTVIEW, WIDGET_WEBVIEW, WIDGET_EDITTEXT, - WIDGET_EDIT_WEBVIEW, WIDGET_CUSTOM_TEXTVIEW, WIDGET_CUSTOM_EDITTEXT, - WIDGET_CUSTOM_UNSELECTABLE_TEXTVIEW, WIDGET_UNKNOWN}) - public @interface WidgetType {} - - public static final String WIDGET_TEXTVIEW = "textview"; - public static final String WIDGET_EDITTEXT = "edittext"; - public static final String WIDGET_UNSELECTABLE_TEXTVIEW = "nosel-textview"; - public static final String WIDGET_WEBVIEW = "webview"; - public static final String WIDGET_EDIT_WEBVIEW = "edit-webview"; - public static final String WIDGET_CUSTOM_TEXTVIEW = "customview"; - public static final String WIDGET_CUSTOM_EDITTEXT = "customedit"; - public static final String WIDGET_CUSTOM_UNSELECTABLE_TEXTVIEW = "nosel-customview"; - public static final String WIDGET_UNKNOWN = "unknown"; - private @SelectionEvent.InvocationMethod int mInvocationMethod; private SelectionEvent mPrevEvent; private SelectionEvent mSmartEvent; @@ -108,7 +77,6 @@ public abstract class Logger { return false; } - /** * Returns a token iterator for tokenizing text for logging purposes. */ @@ -299,6 +267,9 @@ public abstract class Logger { // Selection did not change. Ignore event. return; } + break; + default: + // do nothing. } event.setEventTime(now); @@ -325,9 +296,9 @@ public abstract class Logger { } } - private String startNewSession() { + private TextClassificationSessionId startNewSession() { endSession(); - return UUID.randomUUID().toString(); + return new TextClassificationSessionId(); } private void endSession() { @@ -372,12 +343,12 @@ public abstract class Logger { /** * @param context Context of the widget the logger logs for * @param widgetType a name for the widget being logged for. e.g. - * {@link #WIDGET_TEXTVIEW} + * {@link TextClassifier#WIDGET_TYPE_TEXTVIEW} * @param widgetVersion a string version info for the widget the logger logs for */ public Config( @NonNull Context context, - @WidgetType String widgetType, + @TextClassifier.WidgetType String widgetType, @Nullable String widgetVersion) { mPackageName = Preconditions.checkNotNull(context).getPackageName(); mWidgetType = widgetType; @@ -392,7 +363,8 @@ public abstract class Logger { } /** - * Returns the name for the widget being logged for. e.g. {@link #WIDGET_TEXTVIEW}. + * Returns the name for the widget being logged for. e.g. + * {@link TextClassifier#WIDGET_TYPE_TEXTVIEW}. */ public String getWidgetType() { return mWidgetType; diff --git a/core/java/android/view/textclassifier/SelectionEvent.java b/core/java/android/view/textclassifier/SelectionEvent.java index 7ac094eda031..5a4d2cf1d481 100644 --- a/core/java/android/view/textclassifier/SelectionEvent.java +++ b/core/java/android/view/textclassifier/SelectionEvent.java @@ -17,10 +17,12 @@ package android.view.textclassifier; import android.annotation.IntDef; +import android.annotation.NonNull; import android.annotation.Nullable; import android.os.Parcel; import android.os.Parcelable; import android.view.textclassifier.TextClassifier.EntityType; +import android.view.textclassifier.TextClassifier.WidgetType; import com.android.internal.util.Preconditions; @@ -103,30 +105,33 @@ public final class SelectionEvent implements Parcelable { /** @hide */ @Retention(RetentionPolicy.SOURCE) - @IntDef({INVOCATION_MANUAL, INVOCATION_LINK}) + @IntDef({INVOCATION_MANUAL, INVOCATION_LINK, INVOCATION_UNKNOWN}) public @interface InvocationMethod {} /** Selection was invoked by the user long pressing, double tapping, or dragging to select. */ public static final int INVOCATION_MANUAL = 1; /** Selection was invoked by the user tapping on a link. */ public static final int INVOCATION_LINK = 2; + /** Unknown invocation method */ + public static final int INVOCATION_UNKNOWN = 0; + + private static final String NO_SIGNATURE = ""; private final int mAbsoluteStart; private final int mAbsoluteEnd; - private final @EventType int mEventType; private final @EntityType String mEntityType; - @Nullable private final String mWidgetVersion; - private final String mPackageName; - private final String mWidgetType; - private final @InvocationMethod int mInvocationMethod; - // These fields should only be set by creator of a SelectionEvent. - private String mSignature; + private @EventType int mEventType; + private String mPackageName = ""; + private String mWidgetType = TextClassifier.WIDGET_TYPE_UNKNOWN; + private @InvocationMethod int mInvocationMethod; + @Nullable private String mWidgetVersion; + private String mSignature; // TODO: Rename to resultId. private long mEventTime; private long mDurationSinceSessionStart; private long mDurationSincePreviousEvent; private int mEventIndex; - @Nullable private String mSessionId; + @Nullable private TextClassificationSessionId mSessionId; private int mStart; private int mEnd; private int mSmartStart; @@ -135,20 +140,29 @@ public final class SelectionEvent implements Parcelable { SelectionEvent( int start, int end, @EventType int eventType, @EntityType String entityType, - @InvocationMethod int invocationMethod, String signature, Logger.Config config) { + @InvocationMethod int invocationMethod, String signature) { Preconditions.checkArgument(end >= start, "end cannot be less than start"); mAbsoluteStart = start; mAbsoluteEnd = end; mEventType = eventType; mEntityType = Preconditions.checkNotNull(entityType); mSignature = Preconditions.checkNotNull(signature); - Preconditions.checkNotNull(config); - mWidgetVersion = config.getWidgetVersion(); - mPackageName = Preconditions.checkNotNull(config.getPackageName()); - mWidgetType = Preconditions.checkNotNull(config.getWidgetType()); mInvocationMethod = invocationMethod; } + SelectionEvent( + int start, int end, + @EventType int eventType, @EntityType String entityType, + @InvocationMethod int invocationMethod, String signature, Logger.Config config) { + this(start, end, eventType, entityType, invocationMethod, signature); + Preconditions.checkNotNull(config); + setTextClassificationSessionContext( + new TextClassificationContext.Builder( + config.getPackageName(), config.getWidgetType()) + .setWidgetVersion(config.getWidgetVersion()) + .build()); + } + private SelectionEvent(Parcel in) { mAbsoluteStart = in.readInt(); mAbsoluteEnd = in.readInt(); @@ -163,7 +177,8 @@ public final class SelectionEvent implements Parcelable { mDurationSinceSessionStart = in.readLong(); mDurationSincePreviousEvent = in.readLong(); mEventIndex = in.readInt(); - mSessionId = in.readInt() > 0 ? in.readString() : null; + mSessionId = in.readInt() > 0 + ? TextClassificationSessionId.CREATOR.createFromParcel(in) : null; mStart = in.readInt(); mEnd = in.readInt(); mSmartStart = in.readInt(); @@ -190,7 +205,7 @@ public final class SelectionEvent implements Parcelable { dest.writeInt(mEventIndex); dest.writeInt(mSessionId != null ? 1 : 0); if (mSessionId != null) { - dest.writeString(mSessionId); + mSessionId.writeToParcel(dest, flags); } dest.writeInt(mStart); dest.writeInt(mEnd); @@ -203,6 +218,156 @@ public final class SelectionEvent implements Parcelable { return 0; } + /** + * Creates a "selection started" event. + * + * @param invocationMethod the way the selection was triggered + * @param start the index of the selected text + */ + @NonNull + public static SelectionEvent createSelectionStartedEvent( + @SelectionEvent.InvocationMethod int invocationMethod, int start) { + return new SelectionEvent( + start, start + 1, SelectionEvent.EVENT_SELECTION_STARTED, + TextClassifier.TYPE_UNKNOWN, invocationMethod, NO_SIGNATURE); + } + + /** + * Creates a "selection modified" event. + * Use when the user modifies the selection. + * + * @param start the start (inclusive) index of the selection + * @param end the end (exclusive) index of the selection + * + * @throws IllegalArgumentException if end is less than start + */ + @NonNull + public static SelectionEvent createSelectionModifiedEvent(int start, int end) { + Preconditions.checkArgument(end >= start, "end cannot be less than start"); + return new SelectionEvent( + start, end, SelectionEvent.EVENT_SELECTION_MODIFIED, + TextClassifier.TYPE_UNKNOWN, INVOCATION_UNKNOWN, NO_SIGNATURE); + } + + /** + * Creates a "selection modified" event. + * Use when the user modifies the selection and the selection's entity type is known. + * + * @param start the start (inclusive) index of the selection + * @param end the end (exclusive) index of the selection + * @param classification the TextClassification object returned by the TextClassifier that + * classified the selected text + * + * @throws IllegalArgumentException if end is less than start + */ + @NonNull + public static SelectionEvent createSelectionModifiedEvent( + int start, int end, @NonNull TextClassification classification) { + Preconditions.checkArgument(end >= start, "end cannot be less than start"); + Preconditions.checkNotNull(classification); + final String entityType = classification.getEntityCount() > 0 + ? classification.getEntity(0) + : TextClassifier.TYPE_UNKNOWN; + return new SelectionEvent( + start, end, SelectionEvent.EVENT_SELECTION_MODIFIED, + entityType, INVOCATION_UNKNOWN, classification.getSignature()); + } + + /** + * Creates a "selection modified" event. + * Use when a TextClassifier modifies the selection. + * + * @param start the start (inclusive) index of the selection + * @param end the end (exclusive) index of the selection + * @param selection the TextSelection object returned by the TextClassifier for the + * specified selection + * + * @throws IllegalArgumentException if end is less than start + */ + @NonNull + public static SelectionEvent createSelectionModifiedEvent( + int start, int end, @NonNull TextSelection selection) { + Preconditions.checkArgument(end >= start, "end cannot be less than start"); + Preconditions.checkNotNull(selection); + final String entityType = selection.getEntityCount() > 0 + ? selection.getEntity(0) + : TextClassifier.TYPE_UNKNOWN; + return new SelectionEvent( + start, end, SelectionEvent.EVENT_AUTO_SELECTION, + entityType, INVOCATION_UNKNOWN, selection.getSignature()); + } + + /** + * Creates an event specifying an action taken on a selection. + * Use when the user clicks on an action to act on the selected text. + * + * @param start the start (inclusive) index of the selection + * @param end the end (exclusive) index of the selection + * @param actionType the action that was performed on the selection + * + * @throws IllegalArgumentException if end is less than start + */ + @NonNull + public static SelectionEvent createSelectionActionEvent( + int start, int end, @SelectionEvent.ActionType int actionType) { + Preconditions.checkArgument(end >= start, "end cannot be less than start"); + checkActionType(actionType); + return new SelectionEvent( + start, end, actionType, TextClassifier.TYPE_UNKNOWN, INVOCATION_UNKNOWN, + NO_SIGNATURE); + } + + /** + * Creates an event specifying an action taken on a selection. + * Use when the user clicks on an action to act on the selected text and the selection's + * entity type is known. + * + * @param start the start (inclusive) index of the selection + * @param end the end (exclusive) index of the selection + * @param actionType the action that was performed on the selection + * @param classification the TextClassification object returned by the TextClassifier that + * classified the selected text + * + * @throws IllegalArgumentException if end is less than start + * @throws IllegalArgumentException If actionType is not a valid SelectionEvent actionType + */ + @NonNull + public static SelectionEvent createSelectionActionEvent( + int start, int end, @SelectionEvent.ActionType int actionType, + @NonNull TextClassification classification) { + Preconditions.checkArgument(end >= start, "end cannot be less than start"); + Preconditions.checkNotNull(classification); + checkActionType(actionType); + final String entityType = classification.getEntityCount() > 0 + ? classification.getEntity(0) + : TextClassifier.TYPE_UNKNOWN; + return new SelectionEvent(start, end, actionType, entityType, INVOCATION_UNKNOWN, + classification.getSignature()); + } + + /** + * @throws IllegalArgumentException If eventType is not an {@link SelectionEvent.ActionType} + */ + private static void checkActionType(@SelectionEvent.EventType int eventType) + throws IllegalArgumentException { + switch (eventType) { + case SelectionEvent.ACTION_OVERTYPE: // fall through + case SelectionEvent.ACTION_COPY: // fall through + case SelectionEvent.ACTION_PASTE: // fall through + case SelectionEvent.ACTION_CUT: // fall through + case SelectionEvent.ACTION_SHARE: // fall through + case SelectionEvent.ACTION_SMART_SHARE: // fall through + case SelectionEvent.ACTION_DRAG: // fall through + case SelectionEvent.ACTION_ABANDON: // fall through + case SelectionEvent.ACTION_SELECT_ALL: // fall through + case SelectionEvent.ACTION_RESET: // fall through + return; + default: + throw new IllegalArgumentException( + String.format(Locale.US, "%d is not an eventType", eventType)); + } + } + int getAbsoluteStart() { return mAbsoluteStart; } @@ -214,15 +379,24 @@ public final class SelectionEvent implements Parcelable { /** * Returns the type of event that was triggered. e.g. {@link #ACTION_COPY}. */ + @EventType public int getEventType() { return mEventType; } /** + * Sets the event type. + */ + void setEventType(@EventType int eventType) { + mEventType = eventType; + } + + /** * Returns the type of entity that is associated with this event. e.g. * {@link android.view.textclassifier.TextClassifier#TYPE_EMAIL}. */ @EntityType + @NonNull public String getEntityType() { return mEntityType; } @@ -230,6 +404,7 @@ public final class SelectionEvent implements Parcelable { /** * Returns the package name of the app that this event originated in. */ + @NonNull public String getPackageName() { return mPackageName; } @@ -237,6 +412,8 @@ public final class SelectionEvent implements Parcelable { /** * Returns the type of widget that was involved in triggering this event. */ + @WidgetType + @NonNull public String getWidgetType() { return mWidgetType; } @@ -244,11 +421,21 @@ public final class SelectionEvent implements Parcelable { /** * Returns a string version info for the widget this event was triggered in. */ + @Nullable public String getWidgetVersion() { return mWidgetVersion; } /** + * Sets the {@link TextClassificationContext} for this event. + */ + void setTextClassificationSessionContext(TextClassificationContext context) { + mPackageName = context.getPackageName(); + mWidgetType = context.getWidgetType(); + mWidgetVersion = context.getWidgetVersion(); + } + + /** * Returns the way the selection mode was invoked. */ public @InvocationMethod int getInvocationMethod() { @@ -256,6 +443,13 @@ public final class SelectionEvent implements Parcelable { } /** + * Sets the invocationMethod for this event. + */ + void setInvocationMethod(@InvocationMethod int invocationMethod) { + mInvocationMethod = invocationMethod; + } + + /** * Returns the signature of the text classifier result associated with this event. */ public String getSignature() { @@ -320,17 +514,18 @@ public final class SelectionEvent implements Parcelable { /** * Returns the selection session id. */ - public String getSessionId() { + @Nullable + public TextClassificationSessionId getSessionId() { return mSessionId; } - SelectionEvent setSessionId(String id) { + SelectionEvent setSessionId(TextClassificationSessionId id) { mSessionId = id; return this; } /** - * Returns the start index of this events token relative to the index of the start selection + * Returns the start index of this events relative to the index of the start selection * event in the selection session. */ public int getStart() { @@ -343,7 +538,7 @@ public final class SelectionEvent implements Parcelable { } /** - * Returns the end index of this events token relative to the index of the start selection + * Returns the end index of this events relative to the index of the start selection * event in the selection session. */ public int getEnd() { @@ -356,7 +551,7 @@ public final class SelectionEvent implements Parcelable { } /** - * Returns the start index of this events token relative to the index of the smart selection + * Returns the start index of this events relative to the index of the smart selection * event in the selection session. */ public int getSmartStart() { @@ -369,7 +564,7 @@ public final class SelectionEvent implements Parcelable { } /** - * Returns the end index of this events token relative to the index of the smart selection + * Returns the end index of this events relative to the index of the smart selection * event in the selection session. */ public int getSmartEnd() { @@ -382,7 +577,15 @@ public final class SelectionEvent implements Parcelable { } boolean isTerminal() { - switch (mEventType) { + return isTerminal(mEventType); + } + + /** + * Returns true if the eventType is a terminal event type. Otherwise returns false. + * A terminal event is an event that ends a selection interaction. + */ + public static boolean isTerminal(@EventType int eventType) { + switch (eventType) { case ACTION_OVERTYPE: // fall through case ACTION_COPY: // fall through case ACTION_PASTE: // fall through diff --git a/core/java/android/view/textclassifier/TextClassificationContext.java b/core/java/android/view/textclassifier/TextClassificationContext.java new file mode 100644 index 000000000000..a88f2f66d359 --- /dev/null +++ b/core/java/android/view/textclassifier/TextClassificationContext.java @@ -0,0 +1,123 @@ +/* + * 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.NonNull; +import android.annotation.Nullable; +import android.view.textclassifier.TextClassifier.WidgetType; + +import com.android.internal.util.Preconditions; + +import java.util.Locale; + +/** + * A representation of the context in which text classification would be performed. + * @see TextClassificationManager#createTextClassificationSession(TextClassificationContext) + */ +public final class TextClassificationContext { + + private final String mPackageName; + private final String mWidgetType; + @Nullable private final String mWidgetVersion; + + private TextClassificationContext( + String packageName, + String widgetType, + String widgetVersion) { + mPackageName = Preconditions.checkNotNull(packageName); + mWidgetType = Preconditions.checkNotNull(widgetType); + mWidgetVersion = widgetVersion; + } + + /** + * Returns the package name for the calling package. + */ + @NonNull + public String getPackageName() { + return mPackageName; + } + + /** + * Returns the widget type for this classification context. + */ + @NonNull + @WidgetType + public String getWidgetType() { + return mWidgetType; + } + + /** + * Returns a custom version string for the widget type. + * + * @see #getWidgetType() + */ + @Nullable + public String getWidgetVersion() { + return mWidgetVersion; + } + + @Override + public String toString() { + return String.format(Locale.US, "TextClassificationContext{" + + "packageName=%s, widgetType=%s, widgetVersion=%s}", + mPackageName, mWidgetType, mWidgetVersion); + } + + /** + * A builder for building a TextClassification context. + */ + public static final class Builder { + + private final String mPackageName; + private final String mWidgetType; + + @Nullable private String mWidgetVersion; + + /** + * Initializes a new builder for text classification context objects. + * + * @param packageName the name of the calling package + * @param widgetType the type of widget e.g. {@link TextClassifier#WIDGET_TYPE_TEXTVIEW} + * + * @return this builder + */ + public Builder(@NonNull String packageName, @NonNull @WidgetType String widgetType) { + mPackageName = Preconditions.checkNotNull(packageName); + mWidgetType = Preconditions.checkNotNull(widgetType); + } + + /** + * Sets an optional custom version string for the widget type. + * + * @return this builder + */ + public Builder setWidgetVersion(@Nullable String widgetVersion) { + mWidgetVersion = widgetVersion; + return this; + } + + /** + * Builds the text classification context object. + * + * @return the built TextClassificationContext object + */ + @NonNull + public TextClassificationContext build() { + return new TextClassificationContext(mPackageName, mWidgetType, mWidgetVersion); + } + } +} diff --git a/core/java/android/view/textclassifier/TextClassificationManager.java b/core/java/android/view/textclassifier/TextClassificationManager.java index a7f1ca1aa239..262d9b852b82 100644 --- a/core/java/android/view/textclassifier/TextClassificationManager.java +++ b/core/java/android/view/textclassifier/TextClassificationManager.java @@ -16,6 +16,7 @@ package android.view.textclassifier; +import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemService; import android.content.Context; @@ -36,6 +37,9 @@ public final class TextClassificationManager { private static final String LOG_TAG = "TextClassificationManager"; private final Object mLock = new Object(); + private final TextClassificationSessionFactory mDefaultSessionFactory = + classificationContext -> new TextClassificationSession( + classificationContext, getTextClassifier()); private final Context mContext; private final TextClassificationConstants mSettings; @@ -46,12 +50,15 @@ public final class TextClassificationManager { private TextClassifier mLocalTextClassifier; @GuardedBy("mLock") private TextClassifier mSystemTextClassifier; + @GuardedBy("mLock") + private TextClassificationSessionFactory mSessionFactory; /** @hide */ public TextClassificationManager(Context context) { mContext = Preconditions.checkNotNull(context); mSettings = TextClassificationConstants.loadFromString(Settings.Global.getString( context.getContentResolver(), Settings.Global.TEXT_CLASSIFIER_CONSTANTS)); + mSessionFactory = mDefaultSessionFactory; } /** @@ -61,6 +68,7 @@ public final class TextClassificationManager { * * @see #setTextClassifier(TextClassifier) */ + @NonNull public TextClassifier getTextClassifier() { synchronized (mLock) { if (mTextClassifier == null) { @@ -93,7 +101,6 @@ public final class TextClassificationManager { * @see TextClassifier#SYSTEM * @hide */ - // TODO: Expose as system API. public TextClassifier getTextClassifier(@TextClassifierType int type) { switch (type) { case TextClassifier.LOCAL: @@ -108,6 +115,61 @@ public final class TextClassificationManager { return mSettings; } + /** + * Call this method to start a text classification session with the given context. + * A session is created with a context helping the classifier better understand + * what the user needs and consists of queries and feedback events. The queries + * are directly related to providing useful functionality to the user and the events + * are a feedback loop back to the classifier helping it learn and better serve + * future queries. + * + * <p> All interactions with the returned classifier are considered part of a single + * session and are logically grouped. For example, when a text widget is focused + * all user interactions around text editing (selection, editing, etc) can be + * grouped together to allow the classifier get better. + * + * @param classificationContext The context in which classification would occur + * + * @return An instance to perform classification in the given context + */ + @NonNull + public TextClassifier createTextClassificationSession( + @NonNull TextClassificationContext classificationContext) { + Preconditions.checkNotNull(classificationContext); + final TextClassifier textClassifier = + mSessionFactory.createTextClassificationSession(classificationContext); + Preconditions.checkNotNull(textClassifier, "Session Factory should never return null"); + return textClassifier; + } + + /** + * @see #createTextClassificationSession(TextClassificationContext, TextClassifier) + * @hide + */ + public TextClassifier createTextClassificationSession( + TextClassificationContext classificationContext, TextClassifier textClassifier) { + Preconditions.checkNotNull(classificationContext); + Preconditions.checkNotNull(textClassifier); + return new TextClassificationSession(classificationContext, textClassifier); + } + + /** + * Sets a TextClassificationSessionFactory to be used to create session-aware TextClassifiers. + * + * @param factory the textClassification session factory. If this is null, the default factory + * will be used. + */ + public void setTextClassificationSessionFactory( + @Nullable TextClassificationSessionFactory factory) { + synchronized (mLock) { + if (factory != null) { + mSessionFactory = factory; + } else { + mSessionFactory = mDefaultSessionFactory; + } + } + } + private TextClassifier getSystemTextClassifier() { synchronized (mLock) { if (mSystemTextClassifier == null && isSystemTextClassifierEnabled()) { diff --git a/core/java/android/view/textclassifier/TextClassificationSession.java b/core/java/android/view/textclassifier/TextClassificationSession.java new file mode 100644 index 000000000000..6938e1ae11c4 --- /dev/null +++ b/core/java/android/view/textclassifier/TextClassificationSession.java @@ -0,0 +1,217 @@ +/* + * 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.WorkerThread; +import android.view.textclassifier.DefaultLogger.SignatureParser; +import android.view.textclassifier.SelectionEvent.InvocationMethod; + +import com.android.internal.util.Preconditions; + +/** + * Session-aware TextClassifier. + */ +@WorkerThread +final class TextClassificationSession implements TextClassifier { + + /* package */ static final boolean DEBUG_LOG_ENABLED = true; + private static final String LOG_TAG = "TextClassificationSession"; + + private final TextClassifier mDelegate; + private final SelectionEventHelper mEventHelper; + + private boolean mDestroyed; + + TextClassificationSession(TextClassificationContext context, TextClassifier delegate) { + mDelegate = Preconditions.checkNotNull(delegate); + mEventHelper = new SelectionEventHelper(new TextClassificationSessionId(), context); + } + + @Override + public TextSelection suggestSelection(CharSequence text, int selectionStartIndex, + int selectionEndIndex, TextSelection.Options options) { + checkDestroyed(); + return mDelegate.suggestSelection(text, selectionStartIndex, selectionEndIndex, options); + } + + @Override + public TextClassification classifyText(CharSequence text, int startIndex, int endIndex, + TextClassification.Options options) { + checkDestroyed(); + return mDelegate.classifyText(text, startIndex, endIndex, options); + } + + @Override + public TextLinks generateLinks(CharSequence text, TextLinks.Options options) { + checkDestroyed(); + return mDelegate.generateLinks(text, options); + } + + @Override + public void onSelectionEvent(SelectionEvent event) { + checkDestroyed(); + Preconditions.checkNotNull(event); + if (mEventHelper.sanitizeEvent(event)) { + mDelegate.onSelectionEvent(event); + } + } + + @Override + public void destroy() { + mEventHelper.endSession(); + mDestroyed = true; + } + + @Override + public boolean isDestroyed() { + return mDestroyed; + } + + /** + * @throws IllegalStateException if this TextClassification session has been destroyed. + * @see #isDestroyed() + * @see #destroy() + */ + private void checkDestroyed() { + if (mDestroyed) { + throw new IllegalStateException("This TextClassification session has been destroyed"); + } + } + + /** + * Helper class for updating SelectionEvent fields. + */ + private static final class SelectionEventHelper { + + private final TextClassificationSessionId mSessionId; + private final TextClassificationContext mContext; + + @InvocationMethod + private int mInvocationMethod = SelectionEvent.INVOCATION_UNKNOWN; + private SelectionEvent mPrevEvent; + private SelectionEvent mSmartEvent; + private SelectionEvent mStartEvent; + + SelectionEventHelper( + TextClassificationSessionId sessionId, TextClassificationContext context) { + mSessionId = Preconditions.checkNotNull(sessionId); + mContext = Preconditions.checkNotNull(context); + } + + /** + * Updates the necessary fields in the event for the current session. + * + * @return true if the event should be reported. false if the event should be ignored + */ + boolean sanitizeEvent(SelectionEvent event) { + updateInvocationMethod(event); + modifyAutoSelectionEventType(event); + + if (event.getEventType() != SelectionEvent.EVENT_SELECTION_STARTED + && mStartEvent == null) { + if (DEBUG_LOG_ENABLED) { + Log.d(LOG_TAG, "Selection session not yet started. Ignoring event"); + } + return false; + } + + final long now = System.currentTimeMillis(); + switch (event.getEventType()) { + case SelectionEvent.EVENT_SELECTION_STARTED: + Preconditions.checkArgument( + event.getAbsoluteEnd() == event.getAbsoluteStart() + 1); + event.setSessionId(mSessionId); + mStartEvent = event; + break; + case SelectionEvent.EVENT_SMART_SELECTION_SINGLE: // fall through + case SelectionEvent.EVENT_SMART_SELECTION_MULTI: + mSmartEvent = event; + break; + case SelectionEvent.EVENT_SELECTION_MODIFIED: // fall through + case SelectionEvent.EVENT_AUTO_SELECTION: + if (mPrevEvent != null + && mPrevEvent.getAbsoluteStart() == event.getAbsoluteStart() + && mPrevEvent.getAbsoluteEnd() == event.getAbsoluteEnd()) { + // Selection did not change. Ignore event. + return false; + } + break; + default: + // do nothing. + } + + event.setEventTime(now); + if (mStartEvent != null) { + event.setSessionId(mStartEvent.getSessionId()) + .setDurationSinceSessionStart(now - mStartEvent.getEventTime()) + .setStart(event.getAbsoluteStart() - mStartEvent.getAbsoluteStart()) + .setEnd(event.getAbsoluteEnd() - mStartEvent.getAbsoluteStart()); + } + if (mSmartEvent != null) { + event.setSignature(mSmartEvent.getSignature()) + .setSmartStart( + mSmartEvent.getAbsoluteStart() - mStartEvent.getAbsoluteStart()) + .setSmartEnd(mSmartEvent.getAbsoluteEnd() - mStartEvent.getAbsoluteStart()); + } + if (mPrevEvent != null) { + event.setDurationSincePreviousEvent(now - mPrevEvent.getEventTime()) + .setEventIndex(mPrevEvent.getEventIndex() + 1); + } + mPrevEvent = event; + return true; + } + + void endSession() { + mPrevEvent = null; + mSmartEvent = null; + mStartEvent = null; + } + + private void updateInvocationMethod(SelectionEvent event) { + event.setTextClassificationSessionContext(mContext); + if (event.getInvocationMethod() == SelectionEvent.INVOCATION_UNKNOWN) { + event.setInvocationMethod(mInvocationMethod); + } else { + mInvocationMethod = event.getInvocationMethod(); + } + } + + private void modifyAutoSelectionEventType(SelectionEvent event) { + switch (event.getEventType()) { + case SelectionEvent.EVENT_SMART_SELECTION_SINGLE: // fall through + case SelectionEvent.EVENT_SMART_SELECTION_MULTI: // fall through + case SelectionEvent.EVENT_AUTO_SELECTION: + if (isPlatformLocalTextClassifierSmartSelection(event.getSignature())) { + if (event.getAbsoluteEnd() - event.getAbsoluteStart() > 1) { + event.setEventType(SelectionEvent.EVENT_SMART_SELECTION_MULTI); + } else { + event.setEventType(SelectionEvent.EVENT_SMART_SELECTION_SINGLE); + } + } else { + event.setEventType(SelectionEvent.EVENT_AUTO_SELECTION); + } + return; + default: + return; + } + } + + private static boolean isPlatformLocalTextClassifierSmartSelection(String signature) { + return DefaultLogger.CLASSIFIER_ID.equals(SignatureParser.getClassifierId(signature)); + } + } +} diff --git a/core/java/android/view/textclassifier/TextClassificationSessionFactory.java b/core/java/android/view/textclassifier/TextClassificationSessionFactory.java new file mode 100644 index 000000000000..c0914b6c943a --- /dev/null +++ b/core/java/android/view/textclassifier/TextClassificationSessionFactory.java @@ -0,0 +1,36 @@ +/* + * 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.NonNull; + +/** + * An interface for creating a session-aware TextClassifier. + * + * @see TextClassificationManager#createTextClassificationSession(TextClassificationContext) + */ +public interface TextClassificationSessionFactory { + + /** + * Creates and returns a session-aware TextClassifier. + * + * @param classificationContext the classification context + */ + @NonNull + TextClassifier createTextClassificationSession( + @NonNull TextClassificationContext classificationContext); +} diff --git a/core/java/android/view/textclassifier/TextClassificationSessionId.java b/core/java/android/view/textclassifier/TextClassificationSessionId.java new file mode 100644 index 000000000000..3e4dc1c52b4d --- /dev/null +++ b/core/java/android/view/textclassifier/TextClassificationSessionId.java @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2013 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.NonNull; +import android.os.Parcel; +import android.os.Parcelable; + +import com.android.internal.util.Preconditions; + +import java.util.UUID; + +/** + * This class represents the id of a text classification session. + */ +public final class TextClassificationSessionId implements Parcelable { + private final @NonNull String mValue; + + /** + * Creates a new instance. + * + * @hide + */ + public TextClassificationSessionId() { + this(UUID.randomUUID().toString()); + } + + /** + * Creates a new instance. + * + * @param value The internal value. + * + * @hide + */ + public TextClassificationSessionId(@NonNull String value) { + mValue = value; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + mValue.hashCode(); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + TextClassificationSessionId other = (TextClassificationSessionId) obj; + if (!mValue.equals(other.mValue)) { + return false; + } + return true; + } + + @Override + public void writeToParcel(Parcel parcel, int flags) { + parcel.writeString(mValue); + } + + @Override + public int describeContents() { + return 0; + } + + /** + * Flattens this id to a string. + * + * @return The flattened id. + * + * @hide + */ + public @NonNull String flattenToString() { + return mValue; + } + + /** + * Unflattens a print job id from a string. + * + * @param string The string. + * @return The unflattened id, or null if the string is malformed. + * + * @hide + */ + public static @NonNull TextClassificationSessionId unflattenFromString(@NonNull String string) { + return new TextClassificationSessionId(string); + } + + public static final Parcelable.Creator<TextClassificationSessionId> CREATOR = + new Parcelable.Creator<TextClassificationSessionId>() { + @Override + public TextClassificationSessionId createFromParcel(Parcel parcel) { + return new TextClassificationSessionId( + Preconditions.checkNotNull(parcel.readString())); + } + + @Override + public TextClassificationSessionId[] newArray(int size) { + return new TextClassificationSessionId[size]; + } + }; +} diff --git a/core/java/android/view/textclassifier/TextClassifier.java b/core/java/android/view/textclassifier/TextClassifier.java index 98fa574f076b..2048f2b49182 100644 --- a/core/java/android/view/textclassifier/TextClassifier.java +++ b/core/java/android/view/textclassifier/TextClassifier.java @@ -112,6 +112,38 @@ public interface TextClassifier { @StringDef(prefix = { "HINT_" }, value = {HINT_TEXT_IS_EDITABLE, HINT_TEXT_IS_NOT_EDITABLE}) @interface Hints {} + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @StringDef({WIDGET_TYPE_TEXTVIEW, WIDGET_TYPE_WEBVIEW, WIDGET_TYPE_EDITTEXT, + WIDGET_TYPE_EDIT_WEBVIEW, WIDGET_TYPE_CUSTOM_TEXTVIEW, WIDGET_TYPE_CUSTOM_EDITTEXT, + WIDGET_TYPE_CUSTOM_UNSELECTABLE_TEXTVIEW, WIDGET_TYPE_UNKNOWN}) + @interface WidgetType {} + + /** The widget involved in the text classification session is a standard + * {@link android.widget.TextView}. */ + String WIDGET_TYPE_TEXTVIEW = "textview"; + /** The widget involved in the text classification session is a standard + * {@link android.widget.EditText}. */ + String WIDGET_TYPE_EDITTEXT = "edittext"; + /** The widget involved in the text classification session is a standard non-selectable + * {@link android.widget.TextView}. */ + String WIDGET_TYPE_UNSELECTABLE_TEXTVIEW = "nosel-textview"; + /** The widget involved in the text classification session is a standard + * {@link android.webkit.WebView}. */ + String WIDGET_TYPE_WEBVIEW = "webview"; + /** The widget involved in the text classification session is a standard editable + * {@link android.webkit.WebView}. */ + String WIDGET_TYPE_EDIT_WEBVIEW = "edit-webview"; + /** The widget involved in the text classification session is a custom text widget. */ + String WIDGET_TYPE_CUSTOM_TEXTVIEW = "customview"; + /** The widget involved in the text classification session is a custom editable text widget. */ + String WIDGET_TYPE_CUSTOM_EDITTEXT = "customedit"; + /** The widget involved in the text classification session is a custom non-selectable text + * widget. */ + String WIDGET_TYPE_CUSTOM_UNSELECTABLE_TEXTVIEW = "nosel-customview"; + /** The widget involved in the text classification session is of an unknown/unspecified type. */ + String WIDGET_TYPE_UNKNOWN = "unknown"; + /** * No-op TextClassifier. * This may be used to turn off TextClassifier features. @@ -124,6 +156,9 @@ public interface TextClassifier { * * <p><strong>NOTE: </strong>Call on a worker thread. * + * <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 selected text (which is specified * by the sub sequence starting at selectionStartIndex and ending at selectionEndIndex) * @param selectionStartIndex start index of the selected part of text @@ -156,6 +191,9 @@ public interface TextClassifier { * * <p><strong>NOTE: </strong>Call on a worker thread. * + * <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 selected text (which is specified * by the sub sequence starting at selectionStartIndex and ending at selectionEndIndex) * @param selectionStartIndex start index of the selected part of text @@ -185,6 +223,9 @@ public interface TextClassifier { * <p><b>NOTE:</b> Do not implement. The default implementation of this method calls * {@link #suggestSelection(CharSequence, int, int, TextSelection.Options)}. 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 + * throw an {@link IllegalStateException}. See {@link #isDestroyed()}. */ @WorkerThread @NonNull @@ -205,6 +246,9 @@ public interface TextClassifier { * * <p><strong>NOTE: </strong>Call on a worker thread. * + * <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 * by the sub sequence starting at startIndex and ending at endIndex) * @param startIndex start index of the text to classify @@ -237,6 +281,9 @@ public interface TextClassifier { * {@link #classifyText(CharSequence, int, int, TextClassification.Options)}. 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 + * throw an {@link IllegalStateException}. See {@link #isDestroyed()}. + * * @param text text providing context for the text to classify (which is specified * by the sub sequence starting at startIndex and ending at endIndex) * @param startIndex start index of the text to classify @@ -265,6 +312,9 @@ public interface TextClassifier { * <p><b>NOTE:</b> Do not implement. The default implementation of this method calls * {@link #classifyText(CharSequence, int, int, TextClassification.Options)}. 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 + * throw an {@link IllegalStateException}. See {@link #isDestroyed()}. */ @WorkerThread @NonNull @@ -285,6 +335,9 @@ public interface TextClassifier { * * <p><strong>NOTE: </strong>Call on a worker thread. * + * <strong>NOTE: </strong>If a TextClassifier has been destroyed, calls to this method should + * throw an {@link IllegalStateException}. See {@link #isDestroyed()}. + * * @param text the text to generate annotations for * @param options configuration for link generation * @@ -295,6 +348,7 @@ public interface TextClassifier { * @see #getMaxGenerateLinksTextLength() */ @WorkerThread + @NonNull default TextLinks generateLinks( @NonNull CharSequence text, @Nullable TextLinks.Options options) { Utils.validate(text, false); @@ -311,6 +365,9 @@ public interface TextClassifier { * {@link #generateLinks(CharSequence, TextLinks.Options)}. 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 + * throw an {@link IllegalStateException}. See {@link #isDestroyed()}. + * * @param text the text to generate annotations for * * @throws IllegalArgumentException if text is null or the text is too long for the @@ -320,6 +377,7 @@ public interface TextClassifier { * @see #getMaxGenerateLinksTextLength() */ @WorkerThread + @NonNull default TextLinks generateLinks(@NonNull CharSequence text) { return generateLinks(text, null); } @@ -327,6 +385,9 @@ public interface TextClassifier { /** * 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 + * throw an {@link IllegalStateException}. See {@link #isDestroyed()}. + * * @see #generateLinks(CharSequence) * @see #generateLinks(CharSequence, TextLinks.Options) */ @@ -339,6 +400,7 @@ public interface TextClassifier { * Returns a helper for logging TextClassifier related events. * * @param config logger configuration + * @hide */ @WorkerThread default Logger getLogger(@NonNull Logger.Config config) { @@ -347,6 +409,37 @@ public interface TextClassifier { } /** + * Reports a selection event. + * + * <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) {} + + /** + * Destroys this TextClassifier. + * + * <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. + */ + default void destroy() {} + + /** + * Returns whether or not this TextClassifier has been destroyed. + * + * <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}. + * + * @see #destroy() + */ + default boolean isDestroyed() { + return false; + } + + /** * Configuration object for specifying what entities to identify. * * Configs are initially based on a predefined preset, and can be modified from there. diff --git a/core/java/android/view/textclassifier/TextClassifierImpl.java b/core/java/android/view/textclassifier/TextClassifierImpl.java index a0f4d5c2054e..5ba470afd961 100644 --- a/core/java/android/view/textclassifier/TextClassifierImpl.java +++ b/core/java/android/view/textclassifier/TextClassifierImpl.java @@ -94,6 +94,8 @@ public final class TextClassifierImpl implements TextClassifier { private Logger.Config mLoggerConfig; @GuardedBy("mLoggerLock") // Do not access outside this lock. private Logger mLogger; + @GuardedBy("mLoggerLock") // Do not access outside this lock. + private Logger mLogger2; // This is the new logger. Will replace mLogger. private final TextClassificationConstants mSettings; @@ -300,6 +302,18 @@ public final class TextClassifierImpl implements TextClassifier { return mLogger; } + @Override + public void onSelectionEvent(SelectionEvent event) { + Preconditions.checkNotNull(event); + synchronized (mLoggerLock) { + if (mLogger2 == null) { + mLogger2 = new DefaultLogger( + new Logger.Config(mContext, WIDGET_TYPE_UNKNOWN, null)); + } + mLogger2.writeEvent(event); + } + } + private TextClassifierImplNative getNative(LocaleList localeList) throws FileNotFoundException { synchronized (mLock) { diff --git a/core/java/android/widget/SelectionActionModeHelper.java b/core/java/android/widget/SelectionActionModeHelper.java index 05204d03f60a..8b49ccbaafdf 100644 --- a/core/java/android/widget/SelectionActionModeHelper.java +++ b/core/java/android/widget/SelectionActionModeHelper.java @@ -35,6 +35,7 @@ import android.util.Log; import android.view.ActionMode; import android.view.textclassifier.Logger; import android.view.textclassifier.SelectionEvent; +import android.view.textclassifier.SelectionEvent.InvocationMethod; import android.view.textclassifier.TextClassification; import android.view.textclassifier.TextClassificationConstants; import android.view.textclassifier.TextClassificationManager; @@ -86,7 +87,7 @@ public final class SelectionActionModeHelper { mTextClassificationSettings = TextClassificationManager.getSettings(mTextView.getContext()); mTextClassificationHelper = new TextClassificationHelper( mTextView.getContext(), - mTextView.getTextClassifier(), + mTextView::getTextClassifier, getText(mTextView), 0, 1, mTextView.getTextLocales()); mSelectionTracker = new SelectionTracker(mTextView); @@ -221,7 +222,7 @@ public final class SelectionActionModeHelper { private boolean skipTextClassification() { // No need to make an async call for a no-op TextClassifier. - final boolean noOpTextClassifier = mTextView.getTextClassifier() == TextClassifier.NO_OP; + final boolean noOpTextClassifier = mTextView.usesNoOpTextClassifier(); // Do not call the TextClassifier if there is no selection. final boolean noSelection = mTextView.getSelectionEnd() == mTextView.getSelectionStart(); // Do not call the TextClassifier if this is a password field. @@ -447,7 +448,7 @@ public final class SelectionActionModeHelper { selectionEnd = mTextView.getSelectionEnd(); } mTextClassificationHelper.init( - mTextView.getTextClassifier(), + mTextView::getTextClassifier, getText(mTextView), selectionStart, selectionEnd, mTextView.getTextLocales()); @@ -660,6 +661,7 @@ public final class SelectionActionModeHelper { private static final String LOG_TAG = "SelectionMetricsLogger"; private static final Pattern PATTERN_WHITESPACE = Pattern.compile("\\s+"); + private final Supplier<TextClassifier> mTextClassificationSession; private final Logger mLogger; private final boolean mEditTextLogger; private final BreakIterator mTokenIterator; @@ -668,26 +670,27 @@ public final class SelectionActionModeHelper { SelectionMetricsLogger(TextView textView) { Preconditions.checkNotNull(textView); + mTextClassificationSession = textView::getTextClassificationSession; mLogger = textView.getTextClassifier().getLogger( new Logger.Config(textView.getContext(), getWidetType(textView), null)); mEditTextLogger = textView.isTextEditable(); mTokenIterator = mLogger.getTokenIterator(textView.getTextLocale()); } - @Logger.WidgetType + @TextClassifier.WidgetType private static String getWidetType(TextView textView) { if (textView.isTextEditable()) { - return Logger.WIDGET_EDITTEXT; + return TextClassifier.WIDGET_TYPE_EDITTEXT; } if (textView.isTextSelectable()) { - return Logger.WIDGET_TEXTVIEW; + return TextClassifier.WIDGET_TYPE_TEXTVIEW; } - return Logger.WIDGET_UNSELECTABLE_TEXTVIEW; + return TextClassifier.WIDGET_TYPE_UNSELECTABLE_TEXTVIEW; } public void logSelectionStarted( CharSequence text, int index, - @SelectionEvent.InvocationMethod int invocationMethod) { + @InvocationMethod int invocationMethod) { try { Preconditions.checkNotNull(text); Preconditions.checkArgumentInRange(index, 0, text.length(), "index"); @@ -697,9 +700,12 @@ public final class SelectionActionModeHelper { mTokenIterator.setText(mText); mStartIndex = index; mLogger.logSelectionStartedEvent(invocationMethod, 0); + // TODO: Remove the above legacy logging. + mTextClassificationSession.get().onSelectionEvent( + SelectionEvent.createSelectionStartedEvent(invocationMethod, 0)); } catch (Exception e) { // Avoid crashes due to logging. - Log.d(LOG_TAG, e.getMessage()); + Log.e(LOG_TAG, "" + e.getMessage(), e); } } @@ -712,16 +718,28 @@ public final class SelectionActionModeHelper { if (selection != null) { mLogger.logSelectionModifiedEvent( wordIndices[0], wordIndices[1], selection); + // TODO: Remove the above legacy logging. + mTextClassificationSession.get().onSelectionEvent( + SelectionEvent.createSelectionModifiedEvent( + wordIndices[0], wordIndices[1], selection)); } else if (classification != null) { mLogger.logSelectionModifiedEvent( wordIndices[0], wordIndices[1], classification); + // TODO: Remove the above legacy logging. + mTextClassificationSession.get().onSelectionEvent( + SelectionEvent.createSelectionModifiedEvent( + wordIndices[0], wordIndices[1], classification)); } else { mLogger.logSelectionModifiedEvent( wordIndices[0], wordIndices[1]); + // TODO: Remove the above legacy logging. + mTextClassificationSession.get().onSelectionEvent( + SelectionEvent.createSelectionModifiedEvent( + wordIndices[0], wordIndices[1])); } } catch (Exception e) { // Avoid crashes due to logging. - Log.d(LOG_TAG, e.getMessage()); + Log.e(LOG_TAG, "" + e.getMessage(), e); } } @@ -736,13 +754,25 @@ public final class SelectionActionModeHelper { if (classification != null) { mLogger.logSelectionActionEvent( wordIndices[0], wordIndices[1], action, classification); + // TODO: Remove the above legacy logging. + mTextClassificationSession.get().onSelectionEvent( + SelectionEvent.createSelectionActionEvent( + wordIndices[0], wordIndices[1], action, classification)); } else { mLogger.logSelectionActionEvent( wordIndices[0], wordIndices[1], action); + // TODO: Remove the above legacy logging. + mTextClassificationSession.get().onSelectionEvent( + SelectionEvent.createSelectionActionEvent( + wordIndices[0], wordIndices[1], action)); } } catch (Exception e) { // Avoid crashes due to logging. - Log.d(LOG_TAG, e.getMessage()); + Log.e(LOG_TAG, "" + e.getMessage(), e); + } finally { + if (SelectionEvent.isTerminal(action)) { + mTextClassificationSession.get().destroy(); + } } } @@ -887,7 +917,7 @@ public final class SelectionActionModeHelper { private final Context mContext; private final boolean mDarkLaunchEnabled; - private TextClassifier mTextClassifier; + private Supplier<TextClassifier> mTextClassifier; /** The original TextView text. **/ private String mText; @@ -919,7 +949,7 @@ public final class SelectionActionModeHelper { /** Whether the TextClassifier has been initialized. */ private boolean mHot; - TextClassificationHelper(Context context, TextClassifier textClassifier, + TextClassificationHelper(Context context, Supplier<TextClassifier> textClassifier, CharSequence text, int selectionStart, int selectionEnd, LocaleList locales) { init(textClassifier, text, selectionStart, selectionEnd, locales); mContext = Preconditions.checkNotNull(context); @@ -928,7 +958,7 @@ public final class SelectionActionModeHelper { } @UiThread - public void init(TextClassifier textClassifier, CharSequence text, + public void init(Supplier<TextClassifier> textClassifier, CharSequence text, int selectionStart, int selectionEnd, LocaleList locales) { mTextClassifier = Preconditions.checkNotNull(textClassifier); mText = Preconditions.checkNotNull(text).toString(); @@ -953,11 +983,11 @@ public final class SelectionActionModeHelper { trimText(); final TextSelection selection; if (mContext.getApplicationInfo().targetSdkVersion > Build.VERSION_CODES.O_MR1) { - selection = mTextClassifier.suggestSelection( + selection = mTextClassifier.get().suggestSelection( mTrimmedText, mRelativeStart, mRelativeEnd, mSelectionOptions); } else { // Use old APIs. - selection = mTextClassifier.suggestSelection( + selection = mTextClassifier.get().suggestSelection( mTrimmedText, mRelativeStart, mRelativeEnd, mSelectionOptions.getDefaultLocales()); } @@ -1006,11 +1036,11 @@ public final class SelectionActionModeHelper { trimText(); final TextClassification classification; if (mContext.getApplicationInfo().targetSdkVersion > Build.VERSION_CODES.O_MR1) { - classification = mTextClassifier.classifyText( + classification = mTextClassifier.get().classifyText( mTrimmedText, mRelativeStart, mRelativeEnd, mClassificationOptions); } else { // Use old APIs. - classification = mTextClassifier.classifyText( + classification = mTextClassifier.get().classifyText( mTrimmedText, mRelativeStart, mRelativeEnd, mClassificationOptions.getDefaultLocales()); } diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 39755aaa2c44..8bf497e0e90d 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -163,6 +163,7 @@ import android.view.inputmethod.ExtractedTextRequest; import android.view.inputmethod.InputConnection; import android.view.inputmethod.InputMethodManager; import android.view.textclassifier.TextClassification; +import android.view.textclassifier.TextClassificationContext; import android.view.textclassifier.TextClassificationManager; import android.view.textclassifier.TextClassifier; import android.view.textclassifier.TextLinks; @@ -427,6 +428,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener private boolean mPreDrawListenerDetached; private TextClassifier mTextClassifier; + private TextClassifier mTextClassificationSession; // A flag to prevent repeated movements from escaping the enclosing text view. The idea here is // that if a user is holding down a movement key to traverse text, we shouldn't also traverse @@ -11527,18 +11529,63 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener @NonNull public TextClassifier getTextClassifier() { if (mTextClassifier == null) { - TextClassificationManager tcm = + final TextClassificationManager tcm = mContext.getSystemService(TextClassificationManager.class); if (tcm != null) { - mTextClassifier = tcm.getTextClassifier(); - } else { - mTextClassifier = TextClassifier.NO_OP; + return tcm.getTextClassifier(); } + return TextClassifier.NO_OP; } return mTextClassifier; } /** + * Returns a session-aware text classifier. + */ + @NonNull + TextClassifier getTextClassificationSession() { + if (mTextClassificationSession == null || mTextClassificationSession.isDestroyed()) { + final TextClassificationManager tcm = + mContext.getSystemService(TextClassificationManager.class); + if (tcm != null) { + final String widgetType; + if (isTextEditable()) { + widgetType = TextClassifier.WIDGET_TYPE_EDITTEXT; + } else if (isTextSelectable()) { + widgetType = TextClassifier.WIDGET_TYPE_TEXTVIEW; + } else { + widgetType = TextClassifier.WIDGET_TYPE_UNSELECTABLE_TEXTVIEW; + } + // TODO: Tagged this widgetType with a * so it we can monitor if it reports + // SelectionEvents exactly as the older Logger does. Remove once investigations + // are complete. + final TextClassificationContext textClassificationContext = + new TextClassificationContext.Builder( + mContext.getPackageName(), "*" + widgetType) + .build(); + if (mTextClassifier != null) { + mTextClassificationSession = tcm.createTextClassificationSession( + textClassificationContext, mTextClassifier); + } else { + mTextClassificationSession = tcm.createTextClassificationSession( + textClassificationContext); + } + } else { + mTextClassificationSession = TextClassifier.NO_OP; + } + } + return mTextClassificationSession; + } + + /** + * Returns true if this TextView uses a no-op TextClassifier. + */ + boolean usesNoOpTextClassifier() { + return getTextClassifier() == TextClassifier.NO_OP; + } + + + /** * Starts an ActionMode for the specified TextLinkSpan. * * @return Whether or not we're attempting to start the action mode. diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java index 8117bf7254fd..89f615683fe7 100644 --- a/core/java/com/android/internal/os/BatteryStatsImpl.java +++ b/core/java/com/android/internal/os/BatteryStatsImpl.java @@ -5666,29 +5666,7 @@ public class BatteryStatsImpl extends BatteryStats { mBluetoothScanTimer.startRunningLocked(elapsedRealtime); } mBluetoothScanNesting++; - - if (workChain != null) { - StatsLog.write(StatsLog.BLE_SCAN_STATE_CHANGED, - workChain.getUids(), workChain.getTags(), - StatsLog.BLE_SCAN_STATE_CHANGED__STATE__ON); - if (isUnoptimized) { - StatsLog.write(StatsLog.BLE_UNOPTIMIZED_SCAN_STATE_CHANGED, - workChain.getUids(), workChain.getTags(), - StatsLog.BLE_UNOPTIMIZED_SCAN_STATE_CHANGED__STATE__ON); - } - } else { - StatsLog.write_non_chained(StatsLog.BLE_SCAN_STATE_CHANGED, uid, null, - StatsLog.BLE_SCAN_STATE_CHANGED__STATE__ON); - if (isUnoptimized) { - StatsLog.write_non_chained(StatsLog.BLE_UNOPTIMIZED_SCAN_STATE_CHANGED, uid, null, - StatsLog.BLE_UNOPTIMIZED_SCAN_STATE_CHANGED__STATE__ON); - } - } - getUidStatsLocked(uid).noteBluetoothScanStartedLocked(elapsedRealtime, isUnoptimized); - if (workChain != null) { - getUidStatsLocked(uid).addBluetoothWorkChain(workChain, isUnoptimized); - } } public void noteBluetoothScanStartedFromSourceLocked(WorkSource ws, boolean isUnoptimized) { @@ -5718,29 +5696,7 @@ public class BatteryStatsImpl extends BatteryStats { addHistoryRecordLocked(elapsedRealtime, uptime); mBluetoothScanTimer.stopRunningLocked(elapsedRealtime); } - - if (workChain != null) { - StatsLog.write( - StatsLog.BLE_SCAN_STATE_CHANGED, workChain.getUids(), workChain.getTags(), - StatsLog.BLE_SCAN_STATE_CHANGED__STATE__OFF); - if (isUnoptimized) { - StatsLog.write(StatsLog.BLE_UNOPTIMIZED_SCAN_STATE_CHANGED, - workChain.getUids(), workChain.getTags(), - StatsLog.BLE_UNOPTIMIZED_SCAN_STATE_CHANGED__STATE__OFF); - } - } else { - StatsLog.write_non_chained(StatsLog.BLE_SCAN_STATE_CHANGED, uid, null, - StatsLog.BLE_SCAN_STATE_CHANGED__STATE__OFF); - if (isUnoptimized) { - StatsLog.write_non_chained(StatsLog.BLE_UNOPTIMIZED_SCAN_STATE_CHANGED, uid, null, - StatsLog.BLE_UNOPTIMIZED_SCAN_STATE_CHANGED__STATE__OFF); - } - } - getUidStatsLocked(uid).noteBluetoothScanStoppedLocked(elapsedRealtime, isUnoptimized); - if (workChain != null) { - getUidStatsLocked(uid).removeBluetoothWorkChain(workChain, isUnoptimized); - } } private int getAttributionUid(int uid, WorkChain workChain) { @@ -5775,33 +5731,9 @@ public class BatteryStatsImpl extends BatteryStats { + Integer.toHexString(mHistoryCur.states2)); addHistoryRecordLocked(elapsedRealtime, uptime); mBluetoothScanTimer.stopAllRunningLocked(elapsedRealtime); - - for (int i=0; i<mUidStats.size(); i++) { BatteryStatsImpl.Uid uid = mUidStats.valueAt(i); uid.noteResetBluetoothScanLocked(elapsedRealtime); - - List<WorkChain> allWorkChains = uid.getAllBluetoothWorkChains(); - if (allWorkChains != null) { - for (int j = 0; j < allWorkChains.size(); ++j) { - StatsLog.write(StatsLog.BLE_SCAN_STATE_CHANGED, - allWorkChains.get(j).getUids(), - allWorkChains.get(j).getTags(), - StatsLog.BLE_SCAN_STATE_CHANGED__STATE__OFF); - } - allWorkChains.clear(); - } - - List<WorkChain> unoptimizedWorkChains = uid.getUnoptimizedBluetoothWorkChains(); - if (unoptimizedWorkChains != null) { - for (int j = 0; j < unoptimizedWorkChains.size(); ++j) { - StatsLog.write(StatsLog.BLE_UNOPTIMIZED_SCAN_STATE_CHANGED, - unoptimizedWorkChains.get(j).getUids(), - unoptimizedWorkChains.get(j).getTags(), - StatsLog.BLE_UNOPTIMIZED_SCAN_STATE_CHANGED__STATE__OFF); - } - unoptimizedWorkChains.clear(); - } } } } @@ -6873,15 +6805,6 @@ public class BatteryStatsImpl extends BatteryStats { */ final SparseArray<Pid> mPids = new SparseArray<>(); - /** - * The list of WorkChains associated with active bluetooth scans. - * - * NOTE: This is a hack and it only needs to exist because there's a "reset" API that is - * supposed to stop and log all WorkChains that were currently active. - */ - ArrayList<WorkChain> mAllBluetoothChains = null; - ArrayList<WorkChain> mUnoptimizedBluetoothChains = null; - public Uid(BatteryStatsImpl bsi, int uid) { mBsi = bsi; mUid = uid; @@ -7410,40 +7333,6 @@ public class BatteryStatsImpl extends BatteryStats { } } - public void addBluetoothWorkChain(WorkChain workChain, boolean isUnoptimized) { - if (mAllBluetoothChains == null) { - mAllBluetoothChains = new ArrayList<WorkChain>(4); - } - - if (isUnoptimized && mUnoptimizedBluetoothChains == null) { - mUnoptimizedBluetoothChains = new ArrayList<WorkChain>(4); - } - - mAllBluetoothChains.add(workChain); - if (isUnoptimized) { - mUnoptimizedBluetoothChains.add(workChain); - } - } - - public void removeBluetoothWorkChain(WorkChain workChain, boolean isUnoptimized) { - if (mAllBluetoothChains != null) { - mAllBluetoothChains.remove(workChain); - } - - if (isUnoptimized && mUnoptimizedBluetoothChains != null) { - mUnoptimizedBluetoothChains.remove(workChain); - } - } - - public List<WorkChain> getAllBluetoothWorkChains() { - return mAllBluetoothChains; - } - - public List<WorkChain> getUnoptimizedBluetoothWorkChains() { - return mUnoptimizedBluetoothChains; - } - - public void noteResetBluetoothScanLocked(long elapsedRealtimeMs) { if (mBluetoothScanTimer != null) { mBluetoothScanTimer.stopAllRunningLocked(elapsedRealtimeMs); diff --git a/core/java/com/android/internal/widget/LockPatternView.java b/core/java/com/android/internal/widget/LockPatternView.java index 51dd92961f54..957c784087af 100644 --- a/core/java/com/android/internal/widget/LockPatternView.java +++ b/core/java/com/android/internal/widget/LockPatternView.java @@ -1008,6 +1008,9 @@ public class LockPatternView extends View { mDrawingProfilingStarted = false; } } + if (mFadePattern) { + clearPattern(); + } } private void cancelLineAnimations() { diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml index 7ff96fa7def2..058185609a92 100644 --- a/core/res/res/values/dimens.xml +++ b/core/res/res/values/dimens.xml @@ -643,6 +643,9 @@ --> <dimen name="autofill_save_icon_max_size">300dp</dimen> + <!-- Maximum number of datasets that are visible in the UX picker without scrolling --> + <integer name="autofill_max_visible_datasets">3</integer> + <!-- Size of a slice shortcut view --> <dimen name="slice_shortcut_size">56dp</dimen> <!-- Size of action icons in a slice --> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index c73a7cb443f7..354880cad6d2 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -3032,12 +3032,12 @@ <java-symbol type="layout" name="autofill_dataset_picker_fullscreen"/> <java-symbol type="layout" name="autofill_dataset_picker_header_footer"/> <java-symbol type="layout" name="autofill_dataset_picker_header_footer_fullscreen"/> + <java-symbol type="id" name="autofill" /> <java-symbol type="id" name="autofill_dataset_container"/> <java-symbol type="id" name="autofill_dataset_footer"/> <java-symbol type="id" name="autofill_dataset_header"/> <java-symbol type="id" name="autofill_dataset_list"/> <java-symbol type="id" name="autofill_dataset_picker"/> - <java-symbol type="id" name="autofill" /> <java-symbol type="id" name="autofill_save_custom_subtitle" /> <java-symbol type="id" name="autofill_save_icon" /> <java-symbol type="id" name="autofill_save_no" /> @@ -3067,6 +3067,7 @@ <java-symbol type="dimen" name="autofill_dataset_picker_max_height"/> <java-symbol type="dimen" name="autofill_save_custom_subtitle_max_height"/> <java-symbol type="dimen" name="autofill_save_icon_max_size"/> + <java-symbol type="integer" name="autofill_max_visible_datasets" /> <java-symbol type="dimen" name="notification_big_picture_max_height"/> <java-symbol type="dimen" name="notification_big_picture_max_width"/> diff --git a/core/tests/coretests/src/android/app/backup/FullBackupTest.java b/core/tests/coretests/src/android/app/backup/FullBackupTest.java index 58ee7a783589..5db416b33af7 100644 --- a/core/tests/coretests/src/android/app/backup/FullBackupTest.java +++ b/core/tests/coretests/src/android/app/backup/FullBackupTest.java @@ -16,9 +16,6 @@ package android.app.backup; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - import android.app.backup.FullBackup.BackupScheme.PathWithRequiredFlags; import android.content.Context; import android.support.test.filters.LargeTest; @@ -102,6 +99,28 @@ public class FullBackupTest extends AndroidTestCase { include.getRequiredFlags()); } + public void testParseBackupSchemeFromXml_onlyIncludeRequireFakeEncryptionFlag() + throws Exception { + mXpp.setInput(new StringReader( + "<full-backup-content>" + + "<include path=\"onlyInclude.txt\" domain=\"file\"" + + " requireFlags=\"fakeClientSideEncryption\"/>" + + "</full-backup-content>")); + + FullBackup.BackupScheme bs = FullBackup.getBackupSchemeForTest(mContext); + bs.parseBackupSchemeFromXmlLocked(mXpp, excludesSet, includeMap); + + Set<PathWithRequiredFlags> fileDomainIncludes = includeMap.get(FullBackup.FILES_TREE_TOKEN); + assertEquals("Didn't find expected file domain include.", 1, fileDomainIncludes.size()); + PathWithRequiredFlags include = fileDomainIncludes.iterator().next(); + assertEquals("Invalid path parsed for <include/>", + new File(mContext.getFilesDir(), "onlyInclude.txt").getCanonicalPath(), + include.getPath()); + assertEquals("Invalid requireFlags parsed for <include/>", + BackupAgent.FLAG_FAKE_CLIENT_SIDE_ENCRYPTION_ENABLED, + include.getRequiredFlags()); + } + public void testparseBackupSchemeFromXml_onlyIncludeRequireD2DFlag() throws Exception { mXpp.setInput(new StringReader( "<full-backup-content>" + diff --git a/core/tests/coretests/src/android/view/textclassifier/SelectionEventTest.java b/core/tests/coretests/src/android/view/textclassifier/SelectionEventTest.java index b77982b6d137..861a43ad346b 100644 --- a/core/tests/coretests/src/android/view/textclassifier/SelectionEventTest.java +++ b/core/tests/coretests/src/android/view/textclassifier/SelectionEventTest.java @@ -33,8 +33,11 @@ public class SelectionEventTest { @Test public void testParcel() { final SelectionEvent[] captured = new SelectionEvent[1]; - final Logger logger = new Logger(new Logger.Config( - InstrumentationRegistry.getTargetContext(), Logger.WIDGET_TEXTVIEW, null)) { + final Logger logger = new Logger( + new Logger.Config( + InstrumentationRegistry.getTargetContext(), + TextClassifier.WIDGET_TYPE_TEXTVIEW, + null)) { @Override public void writeEvent(SelectionEvent event) { captured[0] = event; diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsBackgroundStatsTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsBackgroundStatsTest.java index acf30225ea94..992b46f96339 100644 --- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsBackgroundStatsTest.java +++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsBackgroundStatsTest.java @@ -273,53 +273,6 @@ public class BatteryStatsBackgroundStatsTest extends TestCase { } @SmallTest - public void testAppBluetoothScan_workChainAccounting() throws Exception { - final MockClocks clocks = new MockClocks();MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks); - long curr = 0; // realtime in us - - // On battery - curr = 1000 * (clocks.realtime = clocks.uptime = 100); - bi.updateTimeBasesLocked(true, Display.STATE_ON, curr, curr); // on battery - - // App in foreground - bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND); - - WorkSource ws = new WorkSource(); - ws.createWorkChain().addNode(500, "foo"); - ws.createWorkChain().addNode(500, "bar"); - - // Test start / stop and reset with isUnoptimized == false. - bi.noteBluetoothScanStartedFromSourceLocked(ws, false); - BatteryStatsImpl.Uid stats = (BatteryStatsImpl.Uid) bi.getUidStats().get(500); - assertEquals(ws.getWorkChains(), stats.getAllBluetoothWorkChains()); - assertNull(stats.getUnoptimizedBluetoothWorkChains()); - - bi.noteBluetoothScanStoppedFromSourceLocked(ws, false); - assertTrue(stats.getAllBluetoothWorkChains().isEmpty()); - assertNull(stats.getUnoptimizedBluetoothWorkChains()); - - bi.noteBluetoothScanStartedFromSourceLocked(ws, false); - bi.noteResetBluetoothScanLocked(); - assertTrue(stats.getAllBluetoothWorkChains().isEmpty()); - assertNull(stats.getUnoptimizedBluetoothWorkChains()); - - // Test start / stop and reset with isUnoptimized == true. - bi.noteBluetoothScanStartedFromSourceLocked(ws, true); - stats = (BatteryStatsImpl.Uid) bi.getUidStats().get(500); - assertEquals(ws.getWorkChains(), stats.getAllBluetoothWorkChains()); - assertEquals(ws.getWorkChains(), stats.getUnoptimizedBluetoothWorkChains()); - - bi.noteBluetoothScanStoppedFromSourceLocked(ws, true); - assertTrue(stats.getAllBluetoothWorkChains().isEmpty()); - assertTrue(stats.getUnoptimizedBluetoothWorkChains().isEmpty()); - - bi.noteBluetoothScanStartedFromSourceLocked(ws, true); - bi.noteResetBluetoothScanLocked(); - assertTrue(stats.getAllBluetoothWorkChains().isEmpty()); - assertTrue(stats.getUnoptimizedBluetoothWorkChains().isEmpty()); - } - - @SmallTest public void testJob() throws Exception { final MockClocks clocks = new MockClocks(); MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks); diff --git a/location/java/com/android/internal/location/gnssmetrics/GnssMetrics.java b/location/java/com/android/internal/location/gnssmetrics/GnssMetrics.java index 98e67c21c1a1..3643ca4a02f7 100644 --- a/location/java/com/android/internal/location/gnssmetrics/GnssMetrics.java +++ b/location/java/com/android/internal/location/gnssmetrics/GnssMetrics.java @@ -224,7 +224,9 @@ public class GnssMetrics { s.append("GNSS_KPI_END").append("\n"); GpsBatteryStats stats = mGnssPowerMetrics.getGpsBatteryStats(); if (stats != null) { - s.append("Power Metrics").append('\n'); + s.append("Power Metrics").append("\n"); + s.append(" Time on battery (min): " + + stats.getLoggingDurationMs() / ((double) DateUtils.MINUTE_IN_MILLIS)).append("\n"); long[] t = stats.getTimeInGpsSignalQualityLevel(); if (t != null && t.length == NUM_GPS_SIGNAL_QUALITY_LEVELS) { s.append(" Amount of time (while on battery) Top 4 Avg CN0 > " + diff --git a/packages/PrintSpooler/res/layout/select_printer_activity.xml b/packages/PrintSpooler/res/layout/select_printer_activity.xml index 564802ae11ab..91beff6993d2 100644 --- a/packages/PrintSpooler/res/layout/select_printer_activity.xml +++ b/packages/PrintSpooler/res/layout/select_printer_activity.xml @@ -42,7 +42,7 @@ android:layout_height="wrap_content" android:layout_marginBottom="12dip" android:src="@*android:drawable/ic_grayedout_printer" - android:contentDescription="@string/print_searching_for_printers"> + android:importantForAccessibility="no"> </ImageView> <TextView diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtils.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtils.java index 7728f667ea51..aeb4a856e0d8 100644 --- a/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtils.java +++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtils.java @@ -378,7 +378,7 @@ public class RestrictedLockUtils { final DevicePolicyManager dpm = (DevicePolicyManager) context.getSystemService( Context.DEVICE_POLICY_SERVICE); - return dpm.isMeteredDataDisabledForUser(enforcedAdmin.component, packageName, userId) + return dpm.isMeteredDataDisabledPackageForUser(enforcedAdmin.component, packageName, userId) ? enforcedAdmin : null; } diff --git a/packages/SystemUI/Android.mk b/packages/SystemUI/Android.mk index 68293d964601..df2115147fb2 100644 --- a/packages/SystemUI/Android.mk +++ b/packages/SystemUI/Android.mk @@ -56,7 +56,8 @@ LOCAL_STATIC_JAVA_LIBRARIES := \ SystemUI-tags \ SystemUI-proto -LOCAL_JAVA_LIBRARIES := telephony-common +LOCAL_JAVA_LIBRARIES := telephony-common \ + android.car LOCAL_PACKAGE_NAME := SystemUI LOCAL_PRIVATE_PLATFORM_APIS := true diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index 3d49e5c37cf8..3488168b651b 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -205,6 +205,9 @@ <!-- Listen app op changes --> <uses-permission android:name="android.permission.WATCH_APPOPS" /> + <!-- to read and change hvac values in a car --> + <uses-permission android:name="android.car.permission.ADJUST_CAR_CLIMATE" /> + <application android:name=".SystemUIApplication" android:persistent="true" diff --git a/packages/SystemUI/res/drawable/qs_header_status_dot.xml b/packages/SystemUI/res/drawable/qs_header_status_dot.xml new file mode 100644 index 000000000000..69bfd49255fc --- /dev/null +++ b/packages/SystemUI/res/drawable/qs_header_status_dot.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2018 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<shape xmlns:android="http://schemas.android.com/apk/res/android" + android:shape="oval"> + <solid android:color="@android:color/white"/> +</shape> diff --git a/packages/SystemUI/res/layout/car_facet_button.xml b/packages/SystemUI/res/layout/car_facet_button.xml index f432d366e926..ad8604935628 100644 --- a/packages/SystemUI/res/layout/car_facet_button.xml +++ b/packages/SystemUI/res/layout/car_facet_button.xml @@ -42,6 +42,7 @@ android:layout_height="wrap_content" android:layout_width="match_parent" android:animateLayoutChanges="true" + android:src="@drawable/car_ic_arrow" android:background="@android:color/transparent" android:scaleType="fitCenter"> </com.android.keyguard.AlphaOptimizedImageButton> diff --git a/packages/SystemUI/res/layout/car_left_navigation_bar_unprovisioned.xml b/packages/SystemUI/res/layout/car_left_navigation_bar_unprovisioned.xml new file mode 100644 index 000000000000..a65ff1693797 --- /dev/null +++ b/packages/SystemUI/res/layout/car_left_navigation_bar_unprovisioned.xml @@ -0,0 +1,62 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +** +** Copyright 2018, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ +--> + +<com.android.systemui.statusbar.car.CarNavigationBarView + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:systemui="http://schemas.android.com/apk/res-auto" + android:layout_height="match_parent" + android:layout_width="match_parent" + android:orientation="vertical" + android:background="@drawable/system_bar_background"> + + <LinearLayout + android:layout_height="match_parent" + android:layout_width="match_parent" + android:id="@+id/nav_buttons" + android:orientation="vertical" + android:gravity="top" + android:paddingTop="30dp" + android:layout_weight="1" + android:background="@drawable/system_bar_background" + android:animateLayoutChanges="true"> + + <com.android.systemui.statusbar.car.CarNavigationButton + android:id="@+id/home" + android:layout_height="wrap_content" + android:layout_width="match_parent" + systemui:intent="intent:#Intent;action=android.intent.action.MAIN;category=android.intent.category.HOME;end" + android:src="@drawable/car_ic_overview" + android:background="?android:attr/selectableItemBackground" + android:paddingTop="30dp" + android:paddingBottom="30dp" + /> + + <com.android.systemui.statusbar.car.CarNavigationButton + android:id="@+id/hvac" + android:layout_height="wrap_content" + android:layout_width="match_parent" + systemui:intent="intent:#Intent;action=android.car.intent.action.TOGGLE_HVAC_CONTROLS;end" + systemui:broadcast="true" + android:src="@drawable/car_ic_hvac" + android:background="?android:attr/selectableItemBackground" + android:paddingTop="30dp" + android:paddingBottom="30dp" + /> + </LinearLayout> +</com.android.systemui.statusbar.car.CarNavigationBarView> diff --git a/packages/SystemUI/res/layout/car_navigation_bar_unprovisioned.xml b/packages/SystemUI/res/layout/car_navigation_bar_unprovisioned.xml new file mode 100644 index 000000000000..b0488ae6382b --- /dev/null +++ b/packages/SystemUI/res/layout/car_navigation_bar_unprovisioned.xml @@ -0,0 +1,61 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +** +** Copyright 2018, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ +--> + +<com.android.systemui.statusbar.car.CarNavigationBarView + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:systemui="http://schemas.android.com/apk/res-auto" + android:layout_height="match_parent" + android:layout_width="match_parent" + android:background="@drawable/system_bar_background"> + + <LinearLayout + android:layout_height="match_parent" + android:layout_width="wrap_content" + android:orientation="horizontal" + android:id="@+id/nav_buttons" + android:gravity="left" + android:paddingLeft="30dp" + android:layout_weight="1" + android:animateLayoutChanges="true"> + + <com.android.systemui.statusbar.car.CarNavigationButton + android:id="@+id/home" + android:layout_height="match_parent" + android:layout_width="wrap_content" + systemui:intent="intent:#Intent;action=android.intent.action.MAIN;category=android.intent.category.HOME;end" + android:src="@drawable/car_ic_overview" + android:background="?android:attr/selectableItemBackground" + android:paddingLeft="30dp" + android:paddingRight="30dp" + /> + + <com.android.systemui.statusbar.car.CarNavigationButton + android:id="@+id/hvac" + android:layout_height="match_parent" + android:layout_width="wrap_content" + systemui:intent="intent:#Intent;action=android.car.intent.action.TOGGLE_HVAC_CONTROLS;end" + systemui:broadcast="true" + android:src="@drawable/car_ic_hvac" + android:background="?android:attr/selectableItemBackground" + android:paddingLeft="30dp" + android:paddingRight="30dp" + /> + </LinearLayout> +</com.android.systemui.statusbar.car.CarNavigationBarView> + diff --git a/packages/SystemUI/res/layout/car_right_navigation_bar_unprovisioned.xml b/packages/SystemUI/res/layout/car_right_navigation_bar_unprovisioned.xml new file mode 100644 index 000000000000..a65ff1693797 --- /dev/null +++ b/packages/SystemUI/res/layout/car_right_navigation_bar_unprovisioned.xml @@ -0,0 +1,62 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +** +** Copyright 2018, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ +--> + +<com.android.systemui.statusbar.car.CarNavigationBarView + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:systemui="http://schemas.android.com/apk/res-auto" + android:layout_height="match_parent" + android:layout_width="match_parent" + android:orientation="vertical" + android:background="@drawable/system_bar_background"> + + <LinearLayout + android:layout_height="match_parent" + android:layout_width="match_parent" + android:id="@+id/nav_buttons" + android:orientation="vertical" + android:gravity="top" + android:paddingTop="30dp" + android:layout_weight="1" + android:background="@drawable/system_bar_background" + android:animateLayoutChanges="true"> + + <com.android.systemui.statusbar.car.CarNavigationButton + android:id="@+id/home" + android:layout_height="wrap_content" + android:layout_width="match_parent" + systemui:intent="intent:#Intent;action=android.intent.action.MAIN;category=android.intent.category.HOME;end" + android:src="@drawable/car_ic_overview" + android:background="?android:attr/selectableItemBackground" + android:paddingTop="30dp" + android:paddingBottom="30dp" + /> + + <com.android.systemui.statusbar.car.CarNavigationButton + android:id="@+id/hvac" + android:layout_height="wrap_content" + android:layout_width="match_parent" + systemui:intent="intent:#Intent;action=android.car.intent.action.TOGGLE_HVAC_CONTROLS;end" + systemui:broadcast="true" + android:src="@drawable/car_ic_hvac" + android:background="?android:attr/selectableItemBackground" + android:paddingTop="30dp" + android:paddingBottom="30dp" + /> + </LinearLayout> +</com.android.systemui.statusbar.car.CarNavigationBarView> diff --git a/packages/SystemUI/res/layout/car_status_bar_header.xml b/packages/SystemUI/res/layout/car_status_bar_header.xml index 158907e03541..f2ef30180bc0 100644 --- a/packages/SystemUI/res/layout/car_status_bar_header.xml +++ b/packages/SystemUI/res/layout/car_status_bar_header.xml @@ -13,7 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. --> -<!-- Extends RelativeLayout --> +<!-- Extends LinearLayout --> <com.android.systemui.qs.car.CarStatusBarHeader xmlns:android="http://schemas.android.com/apk/res/android" xmlns:systemui="http://schemas.android.com/apk/res-auto" @@ -23,22 +23,21 @@ android:paddingStart="8dp" android:paddingEnd="8dp" > - <include - layout="@layout/system_icons" - android:layout_width="wrap_content" - android:layout_height="match_parent" - android:layout_alignParentStart="true" - android:layout_centerVertical="true" /> + <include layout="@layout/system_icons" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:gravity="center_vertical|end" + android:layout_weight="1" + /> <com.android.systemui.statusbar.policy.Clock android:id="@+id/clock" android:textAppearance="@style/TextAppearance.StatusBar.Clock" android:layout_width="wrap_content" android:layout_height="match_parent" - android:layout_alignParentEnd="true" - android:layout_centerVertical="true" android:singleLine="true" android:paddingStart="@dimen/status_bar_clock_starting_padding" android:paddingEnd="@dimen/status_bar_clock_end_padding" - systemui:showDark="false" /> + android:gravity="center_vertical|end" + /> </com.android.systemui.qs.car.CarStatusBarHeader> diff --git a/packages/SystemUI/res/layout/car_top_navigation_bar.xml b/packages/SystemUI/res/layout/car_top_navigation_bar.xml new file mode 100644 index 000000000000..e16014bb8945 --- /dev/null +++ b/packages/SystemUI/res/layout/car_top_navigation_bar.xml @@ -0,0 +1,38 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/** +** Copyright 2018, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ +--> + +<com.android.systemui.statusbar.car.CarNavigationBarView + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_height="match_parent" + android:layout_width="match_parent" + android:background="@drawable/system_bar_background"> + + <com.android.systemui.statusbar.policy.Clock + android:id="@+id/clock" + android:textAppearance="@style/TextAppearance.StatusBar.Clock" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:singleLine="true" + android:paddingStart="@dimen/status_bar_clock_starting_padding" + android:paddingEnd="@dimen/status_bar_clock_end_padding" + android:gravity="center_vertical" + /> + +</com.android.systemui.statusbar.car.CarNavigationBarView> + diff --git a/packages/SystemUI/res/layout/quick_settings_header_info.xml b/packages/SystemUI/res/layout/quick_settings_header_info.xml index 7ec437c94728..03e8451e59dc 100644 --- a/packages/SystemUI/res/layout/quick_settings_header_info.xml +++ b/packages/SystemUI/res/layout/quick_settings_header_info.xml @@ -33,7 +33,7 @@ android:visibility="invisible" /> <LinearLayout - android:id="@+id/next_alarm" + android:id="@+id/status_container" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal|bottom" @@ -41,6 +41,7 @@ android:visibility="invisible"> <ImageView + android:id="@+id/next_alarm_icon" android:layout_width="@dimen/qs_header_alarm_icon_size" android:layout_height="@dimen/qs_header_alarm_icon_size" android:src="@drawable/stat_sys_alarm" @@ -53,6 +54,29 @@ android:layout_marginStart="@dimen/qs_header_alarm_text_margin_start" android:textAppearance="@style/TextAppearance.QS.TileLabel" /> + <ImageView + android:id="@+id/status_separator" + android:layout_width="2dp" + android:layout_height="2dp" + android:layout_marginStart="8dp" + android:layout_marginEnd="8dp" + android:layout_gravity="center_vertical" + android:src="@drawable/qs_header_status_dot" + android:tint="?android:attr/textColorPrimary" /> + + <ImageView + android:id="@+id/ringer_mode_icon" + android:layout_width="@dimen/qs_header_alarm_icon_size" + android:layout_height="@dimen/qs_header_alarm_icon_size" + android:tint="?android:attr/textColorPrimary" /> + + <TextView + android:id="@+id/ringer_mode_text" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginStart="@dimen/qs_header_alarm_text_margin_start" + android:textAppearance="@style/TextAppearance.QS.TileLabel" /> + </LinearLayout> </FrameLayout> diff --git a/packages/SystemUI/res/layout/status_bar_notification_footer.xml b/packages/SystemUI/res/layout/status_bar_notification_footer.xml index aa0d4a05f6cc..22f1618f5474 100644 --- a/packages/SystemUI/res/layout/status_bar_notification_footer.xml +++ b/packages/SystemUI/res/layout/status_bar_notification_footer.xml @@ -44,7 +44,6 @@ android:focusable="true" android:contentDescription="@string/accessibility_clear_all" android:text="@string/clear_all_notifications_text" - android:textColor="?attr/wallpaperTextColor" - android:textAllCaps="true"/> + android:textColor="?attr/wallpaperTextColor"/> </FrameLayout> </com.android.systemui.statusbar.FooterView> diff --git a/packages/SystemUI/res/layout/status_bar_notification_row.xml b/packages/SystemUI/res/layout/status_bar_notification_row.xml index 2e7ab7fe8904..f15ca9e972c2 100644 --- a/packages/SystemUI/res/layout/status_bar_notification_row.xml +++ b/packages/SystemUI/res/layout/status_bar_notification_row.xml @@ -55,19 +55,6 @@ android:paddingStart="8dp" /> - <!-- TODO: remove --> - <ImageButton - android:id="@+id/helper" - android:layout_width="48dp" - android:layout_height="@*android:dimen/notification_header_height" - android:layout_gravity="top|end" - android:layout_marginEnd="6dp" - android:src="@drawable/ic_dnd" - android:tint="#FF0000" - android:background="@drawable/ripple_drawable" - android:visibility="visible" - /> - <ViewStub android:layout="@layout/notification_children_container" android:id="@+id/child_container_stub" diff --git a/packages/SystemUI/res/values/attrs_car.xml b/packages/SystemUI/res/values/attrs_car.xml index b1097c39363c..5e4bd79bcc04 100644 --- a/packages/SystemUI/res/values/attrs_car.xml +++ b/packages/SystemUI/res/values/attrs_car.xml @@ -27,6 +27,13 @@ <attr name="categories" format="string"/> <!-- package names that will be added as extras to the fired intents --> <attr name="packages" format="string" /> + <!-- Alpha value to used when in selected state. Defaults 1f --> + <attr name="selectedAlpha" format="float" /> + <!-- Alpha value to used when in un-selected state. Defaults 0.7f --> + <attr name="unselectedAlpha" format="float" /> + <!-- Render a "more" icon. Defaults true --> + <attr name="useMoreIcon" format="boolean" /> + </declare-styleable> @@ -39,4 +46,11 @@ <!-- start the intent as a broad cast instead of an activity if true--> <attr name="broadcast" format="boolean"/> </declare-styleable> + + <!-- Custom attributes to configure hvac values --> + <declare-styleable name="TemperatureView"> + <attr name="hvacAreaId" format="integer"/> + <attr name="hvacPropertyId" format="integer"/> + <attr name="hvacTempFormat" format="string"/> + </declare-styleable> </resources> diff --git a/packages/SystemUI/src/com/android/systemui/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/OverviewProxyService.java index 2983df6ec937..816c598daf4f 100644 --- a/packages/SystemUI/src/com/android/systemui/OverviewProxyService.java +++ b/packages/SystemUI/src/com/android/systemui/OverviewProxyService.java @@ -50,6 +50,7 @@ import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; +import static com.android.systemui.shared.system.NavigationBarCompat.FLAG_DISABLE_SWIPE_UP; import static com.android.systemui.shared.system.NavigationBarCompat.InteractionType; /** @@ -249,6 +250,10 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis mConnectionCallbacks.remove(listener); } + public boolean shouldShowSwipeUpUI() { + return getProxy() != null && ((mInteractionFlags & FLAG_DISABLE_SWIPE_UP) == 0); + } + public IOverviewProxy getProxy() { return mOverviewProxy; } diff --git a/packages/SystemUI/src/com/android/systemui/car/CarSystemUIFactory.java b/packages/SystemUI/src/com/android/systemui/car/CarSystemUIFactory.java index 245d240017e1..9459ce1ba827 100644 --- a/packages/SystemUI/src/com/android/systemui/car/CarSystemUIFactory.java +++ b/packages/SystemUI/src/com/android/systemui/car/CarSystemUIFactory.java @@ -21,6 +21,8 @@ import android.util.ArrayMap; import com.android.systemui.Dependency.DependencyProvider; import com.android.systemui.SystemUIFactory; import com.android.systemui.statusbar.NotificationEntryManager; +import com.android.systemui.statusbar.car.CarFacetButtonController; +import com.android.systemui.statusbar.car.hvac.HvacController; /** * Class factory to provide car specific SystemUI components. @@ -32,5 +34,7 @@ public class CarSystemUIFactory extends SystemUIFactory { super.injectDependencies(providers, context); providers.put(NotificationEntryManager.class, () -> new CarNotificationEntryManager(context)); + providers.put(CarFacetButtonController.class, () -> new CarFacetButtonController(context)); + providers.put(HvacController.class, () -> new HvacController(context)); } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java index eb779a5253f1..ca88d704fd87 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java @@ -21,22 +21,25 @@ import android.animation.AnimatorListenerAdapter; import android.annotation.ColorInt; import android.app.ActivityManager; import android.app.AlarmManager; +import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; +import android.content.IntentFilter; import android.content.res.Configuration; import android.content.res.Resources; import android.graphics.Color; import android.graphics.Rect; +import android.media.AudioManager; import android.os.Handler; import android.provider.AlarmClock; import android.support.annotation.VisibleForTesting; -import android.text.TextUtils; import android.text.format.DateUtils; import android.util.AttributeSet; import android.util.Log; import android.util.Pair; import android.view.View; import android.view.WindowInsets; +import android.widget.ImageView; import android.widget.RelativeLayout; import android.widget.TextView; @@ -95,21 +98,36 @@ public class QuickStatusBarHeader extends RelativeLayout implements CommandQueue private View mQuickQsStatusIcons; private View mDate; private View mHeaderTextContainerView; - /** View corresponding to the next alarm info (including the icon). */ - private View mNextAlarmView; + /** View containing the next alarm and ringer mode info. */ + private View mStatusContainer; /** Tooltip for educating users that they can long press on icons to see more details. */ private View mLongPressTooltipView; + + private int mRingerMode = AudioManager.RINGER_MODE_NORMAL; + private AlarmManager.AlarmClockInfo mNextAlarm; + + private ImageView mNextAlarmIcon; /** {@link TextView} containing the actual text indicating when the next alarm will go off. */ private TextView mNextAlarmTextView; + private View mStatusSeparator; + private ImageView mRingerModeIcon; + private TextView mRingerModeTextView; private BatteryMeterView mBatteryMeterView; private Clock mClockView; private DateView mDateView; private NextAlarmController mAlarmController; - private String mNextAlarmText; /** Counts how many times the long press tooltip has been shown to the user. */ private int mShownCount; + private final BroadcastReceiver mRingerReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + mRingerMode = intent.getIntExtra(AudioManager.EXTRA_RINGER_MODE, -1); + updateStatusText(); + } + }; + /** * Runnable for automatically fading out the long press tooltip (as if it were animating away). */ @@ -117,7 +135,6 @@ public class QuickStatusBarHeader extends RelativeLayout implements CommandQueue public QuickStatusBarHeader(Context context, AttributeSet attrs) { super(context, attrs); - mAlarmController = Dependency.get(NextAlarmController.class); mShownCount = getStoredShownCount(); } @@ -136,8 +153,12 @@ public class QuickStatusBarHeader extends RelativeLayout implements CommandQueue // Views corresponding to the header info section (e.g. tooltip and next alarm). mHeaderTextContainerView = findViewById(R.id.header_text_container); mLongPressTooltipView = findViewById(R.id.long_press_tooltip); - mNextAlarmView = findViewById(R.id.next_alarm); + mStatusContainer = findViewById(R.id.status_container); + mStatusSeparator = findViewById(R.id.status_separator); + mNextAlarmIcon = findViewById(R.id.next_alarm_icon); mNextAlarmTextView = findViewById(R.id.next_alarm_text); + mRingerModeIcon = findViewById(R.id.ringer_mode_icon); + mRingerModeTextView = findViewById(R.id.ringer_mode_text); updateResources(); @@ -159,6 +180,32 @@ public class QuickStatusBarHeader extends RelativeLayout implements CommandQueue mDateView = findViewById(R.id.date); } + private void updateStatusText() { + boolean ringerVisible = false; + if (mRingerMode == AudioManager.RINGER_MODE_VIBRATE) { + mRingerModeIcon.setImageResource(R.drawable.stat_sys_ringer_vibrate); + mRingerModeTextView.setText(R.string.volume_ringer_status_vibrate); + ringerVisible = true; + } else if (mRingerMode == AudioManager.RINGER_MODE_SILENT) { + mRingerModeIcon.setImageResource(R.drawable.stat_sys_ringer_silent); + mRingerModeTextView.setText(R.string.volume_ringer_status_silent); + ringerVisible = true; + } + mRingerModeIcon.setVisibility(ringerVisible ? View.VISIBLE : View.GONE); + mRingerModeTextView.setVisibility(ringerVisible ? View.VISIBLE : View.GONE); + + boolean alarmVisible = false; + if (mNextAlarm != null) { + alarmVisible = true; + mNextAlarmTextView.setText(formatNextAlarm(mNextAlarm)); + } + mNextAlarmIcon.setVisibility(alarmVisible ? View.VISIBLE : View.GONE); + mNextAlarmTextView.setVisibility(alarmVisible ? View.VISIBLE : View.GONE); + mStatusSeparator.setVisibility(alarmVisible && ringerVisible ? View.VISIBLE : View.GONE); + updateTooltipShow(); + } + + private void applyDarkness(int id, Rect tintArea, float intensity, int color) { View v = findViewById(id); if (v instanceof DarkReceiver) { @@ -323,8 +370,11 @@ public class QuickStatusBarHeader extends RelativeLayout implements CommandQueue if (listening) { mAlarmController.addCallback(this); + mContext.registerReceiver(mRingerReceiver, + new IntentFilter(AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION)); } else { mAlarmController.removeCallback(this); + mContext.unregisterReceiver(mRingerReceiver); } } @@ -338,23 +388,31 @@ public class QuickStatusBarHeader extends RelativeLayout implements CommandQueue @Override public void onNextAlarmChanged(AlarmManager.AlarmClockInfo nextAlarm) { - mNextAlarmText = nextAlarm != null ? formatNextAlarm(nextAlarm) : null; + mNextAlarm = nextAlarm; + updateStatusText(); + } - if (mNextAlarmText != null) { - hideLongPressTooltip(true /* shouldFadeInAlarmText */); + private void updateTooltipShow() { + if (hasStatusText()) { + hideLongPressTooltip(true /* shouldShowStatusText */); } else { - hideAlarmText(); + hideStatusText(); } updateHeaderTextContainerAlphaAnimator(); } + private boolean hasStatusText() { + return mNextAlarmTextView.getVisibility() == View.VISIBLE + || mRingerModeTextView.getVisibility() == View.VISIBLE; + } + /** * Animates in the long press tooltip (as long as the next alarm text isn't currently occupying * the space). */ public void showLongPressTooltip() { - // If we have alarm text to show, don't bother fading in the tooltip. - if (!TextUtils.isEmpty(mNextAlarmText)) { + // If we have status text to show, don't bother fading in the tooltip. + if (hasStatusText()) { return; } @@ -384,11 +442,11 @@ public class QuickStatusBarHeader extends RelativeLayout implements CommandQueue /** * Fades out the long press tooltip if it's partially visible - short circuits any running - * animation. Additionally has the ability to fade in the alarm info text. + * animation. Additionally has the ability to fade in the status info text. * - * @param shouldShowAlarmText whether we should fade in the next alarm text + * @param shouldShowStatusText whether we should fade in the status text */ - private void hideLongPressTooltip(boolean shouldShowAlarmText) { + private void hideLongPressTooltip(boolean shouldShowStatusText) { mLongPressTooltipView.animate().cancel(); if (mLongPressTooltipView.getVisibility() == View.VISIBLE && mLongPressTooltipView.getAlpha() != 0f) { @@ -402,44 +460,40 @@ public class QuickStatusBarHeader extends RelativeLayout implements CommandQueue if (DEBUG) Log.d(TAG, "hideLongPressTooltip: Hid long press tip"); mLongPressTooltipView.setVisibility(View.INVISIBLE); - if (shouldShowAlarmText) { - showAlarmText(); + if (shouldShowStatusText) { + showStatus(); } } }) .start(); } else { mLongPressTooltipView.setVisibility(View.INVISIBLE); - if (shouldShowAlarmText) { - showAlarmText(); + if (shouldShowStatusText) { + showStatus(); } } } /** - * Fades in the updated alarm text. Note that if there's already an alarm showing, this will - * immediately hide it and fade in the updated time. + * Fades in the updated status text. Note that if there's already a status showing, this will + * immediately hide it and fade in the updated status. */ - private void showAlarmText() { - mNextAlarmView.setAlpha(0f); - mNextAlarmView.setVisibility(View.VISIBLE); - mNextAlarmTextView.setText(mNextAlarmText); + private void showStatus() { + mStatusContainer.setAlpha(0f); + mStatusContainer.setVisibility(View.VISIBLE); // Animate the alarm back in. Make sure to clear the animator listener for the animation! - mNextAlarmView.animate() + mStatusContainer.animate() .alpha(1f) .setDuration(FADE_ANIMATION_DURATION_MS) .setListener(null) .start(); } - /** - * Fades out and hides the next alarm text. This also resets the text contents to null in - * preparation for the next alarm update. - */ - private void hideAlarmText() { - if (mNextAlarmView.getVisibility() == View.VISIBLE) { - mNextAlarmView.animate() + /** Fades out and hides the status text. */ + private void hideStatusText() { + if (mStatusContainer.getVisibility() == View.VISIBLE) { + mStatusContainer.animate() .alpha(0f) .setListener(new AnimatorListenerAdapter() { @Override @@ -448,15 +502,11 @@ public class QuickStatusBarHeader extends RelativeLayout implements CommandQueue // Reset the alpha regardless of how the animation ends for the next // time we show this view/want to animate it. - mNextAlarmView.setVisibility(View.INVISIBLE); - mNextAlarmView.setAlpha(1f); - mNextAlarmTextView.setText(null); + mStatusContainer.setVisibility(View.INVISIBLE); + mStatusContainer.setAlpha(1f); } }) .start(); - } else { - // Next alarm view is already hidden, only need to clear the text. - mNextAlarmTextView.setText(null); } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/car/CarStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/car/CarStatusBarHeader.java index 6797bb9dbe82..ec183769c763 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/car/CarStatusBarHeader.java +++ b/packages/SystemUI/src/com/android/systemui/qs/car/CarStatusBarHeader.java @@ -19,7 +19,7 @@ import android.graphics.Rect; import android.support.annotation.IdRes; import android.util.AttributeSet; import android.view.View; -import android.widget.RelativeLayout; +import android.widget.LinearLayout; import com.android.settingslib.Utils; import com.android.systemui.BatteryMeterView; @@ -30,7 +30,7 @@ import com.android.systemui.statusbar.policy.DarkIconDispatcher; * A view that forms the header of the notification panel. This view will ensure that any * status icons that are displayed are tinted accordingly to the current theme. */ -public class CarStatusBarHeader extends RelativeLayout { +public class CarStatusBarHeader extends LinearLayout { public CarStatusBarHeader(Context context, AttributeSet attrs) { super(context, attrs); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java index 05a5a8ea3484..03b263d2ae95 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java @@ -183,7 +183,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView private AboveShelfChangedListener mAboveShelfChangedListener; private HeadsUpManager mHeadsUpManager; private Consumer<Boolean> mHeadsUpAnimatingAwayListener; - private View mHelperButton; private boolean mChildIsExpanding; private boolean mJustClicked; @@ -401,8 +400,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView updateLimits(); updateIconVisibilities(); updateShelfIconColor(); - - showBlockingHelperButton(mEntry.userSentiment == USER_SENTIMENT_NEGATIVE); updateRippleAllowed(); } @@ -1426,10 +1423,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView requestLayout(); } - public void showBlockingHelperButton(boolean show) { - mHelperButton.setVisibility(show ? View.VISIBLE : View.GONE); - } - public void showAppOpsIcons(ArraySet<Integer> activeOps) { if (mIsSummaryWithChildren && mChildrenContainer.getHeaderView() != null) { mChildrenContainer.getHeaderView().showAppOpsIcons(activeOps); @@ -1459,11 +1452,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView mPrivateLayout = (NotificationContentView) findViewById(R.id.expanded); mLayouts = new NotificationContentView[] {mPrivateLayout, mPublicLayout}; - mHelperButton = findViewById(R.id.helper); - mHelperButton.setOnClickListener(view -> { - doLongClickCallback(); - }); - for (NotificationContentView l : mLayouts) { l.setExpandClickListener(mExpandClickListener); l.setContainingNotification(this); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInfo.java index a2f336eb6398..82ad74e26f0c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInfo.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInfo.java @@ -165,6 +165,7 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G mIsForeground = (mSbn.getNotification().flags & Notification.FLAG_FOREGROUND_SERVICE) != 0; mIsForBlockingHelper = isForBlockingHelper; + mAppUid = mSbn.getUid(); int numTotalChannels = mINotificationManager.getNumNotificationChannelsForPackage( pkg, mAppUid, false /* includeDeleted */); @@ -173,9 +174,9 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G } else { // Special behavior for the Default channel if no other channels have been defined. mIsSingleDefaultChannel = mNumNotificationChannels == 1 - && mSingleNotificationChannel.getId() - .equals(NotificationChannel.DEFAULT_CHANNEL_ID) - && numTotalChannels <= 1; + && mSingleNotificationChannel.getId().equals( + NotificationChannel.DEFAULT_CHANNEL_ID) + && numTotalChannels == 1; } try { @@ -210,7 +211,6 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G | PackageManager.MATCH_DIRECT_BOOT_UNAWARE | PackageManager.MATCH_DIRECT_BOOT_AWARE); if (info != null) { - mAppUid = mSbn.getUid(); mAppName = String.valueOf(mPm.getApplicationLabel(info)); pkgicon = mPm.getApplicationIcon(info); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java index b1e08b81b6e0..fd3a9d5e2bd4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java @@ -350,9 +350,6 @@ public class NotificationViewHierarchyManager { } } - row.showBlockingHelperButton(entry.userSentiment == - NotificationListenerService.Ranking.USER_SENTIMENT_NEGATIVE); - row.showAppOpsIcons(entry.mActiveAppOps); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarFacetButton.java b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarFacetButton.java index 53101a5bd61f..5f3e2e358306 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarFacetButton.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarFacetButton.java @@ -3,6 +3,7 @@ package com.android.systemui.statusbar.car; import android.content.Context; import android.content.Intent; import android.content.res.TypedArray; +import android.graphics.drawable.Drawable; import android.util.AttributeSet; import android.util.Log; import android.view.View; @@ -10,6 +11,7 @@ import android.widget.ImageView; import android.widget.LinearLayout; import com.android.keyguard.AlphaOptimizedImageButton; +import com.android.systemui.Dependency; import com.android.systemui.R; /** @@ -21,9 +23,6 @@ import com.android.systemui.R; * other music apps installed. */ public class CarFacetButton extends LinearLayout { - private static final float SELECTED_ALPHA = 1f; - private static final float UNSELECTED_ALPHA = 0.7f; - private static final String FACET_FILTER_DELIMITER = ";"; /** * Extra information to be sent to a helper to make the decision of what app to launch when @@ -42,6 +41,10 @@ public class CarFacetButton extends LinearLayout { private String[] mFacetCategories; /** App packages that are allowed to be used with this widget */ private String[] mFacetPackages; + private int mIconResourceId; + private boolean mUseMoreIcon = true; + private float mSelectedAlpha = 1f; + private float mUnselectedAlpha = 1f; public CarFacetButton(Context context, AttributeSet attrs) { @@ -53,6 +56,10 @@ public class CarFacetButton extends LinearLayout { TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CarFacetButton); setupIntents(typedArray); setupIcons(typedArray); + CarFacetButtonController carFacetButtonController = Dependency.get( + CarFacetButtonController.class); + carFacetButtonController.addFacetButton(this); + } /** @@ -96,21 +103,25 @@ public class CarFacetButton extends LinearLayout { private void setupIcons(TypedArray styledAttributes) { + mSelectedAlpha = styledAttributes.getFloat( + R.styleable.CarFacetButton_selectedAlpha, mSelectedAlpha); + mUnselectedAlpha = styledAttributes.getFloat( + R.styleable.CarFacetButton_unselectedAlpha, mUnselectedAlpha); mIcon = findViewById(R.id.car_nav_button_icon); mIcon.setScaleType(ImageView.ScaleType.CENTER); mIcon.setClickable(false); - mIcon.setAlpha(UNSELECTED_ALPHA); - int iconResourceId = styledAttributes.getResourceId(R.styleable.CarFacetButton_icon, 0); - if (iconResourceId == 0) { + mIcon.setAlpha(mUnselectedAlpha); + mIconResourceId = styledAttributes.getResourceId(R.styleable.CarFacetButton_icon, 0); + if (mIconResourceId == 0) { throw new RuntimeException("specified icon resource was not found and is required"); } - mIcon.setImageResource(iconResourceId); + mIcon.setImageResource(mIconResourceId); mMoreIcon = findViewById(R.id.car_nav_button_more_icon); mMoreIcon.setClickable(false); - mMoreIcon.setImageDrawable(getContext().getDrawable(R.drawable.car_ic_arrow)); - mMoreIcon.setAlpha(UNSELECTED_ALPHA); + mMoreIcon.setAlpha(mSelectedAlpha); mMoreIcon.setVisibility(GONE); + mUseMoreIcon = styledAttributes.getBoolean(R.styleable.CarFacetButton_useMoreIcon, true); } /** @@ -145,17 +156,27 @@ public class CarFacetButton extends LinearLayout { /** * Updates the visual state to let the user know if it's been selected. * @param selected true if should update the alpha of the icon to selected, false otherwise - * @param showMoreIcon true if the "more icon" should be shown, false otherwise + * @param showMoreIcon true if the "more icon" should be shown, false otherwise. Note this + * is ignored if the attribute useMoreIcon is set to false */ public void setSelected(boolean selected, boolean showMoreIcon) { mSelected = selected; if (selected) { - mMoreIcon.setVisibility(showMoreIcon ? VISIBLE : GONE); - mMoreIcon.setAlpha(SELECTED_ALPHA); - mIcon.setAlpha(SELECTED_ALPHA); + if (mUseMoreIcon) { + mMoreIcon.setVisibility(showMoreIcon ? VISIBLE : GONE); + } + mIcon.setAlpha(mSelectedAlpha); } else { mMoreIcon.setVisibility(GONE); - mIcon.setAlpha(UNSELECTED_ALPHA); + mIcon.setAlpha(mUnselectedAlpha); + } + } + + public void setIcon(Drawable d) { + if (d != null) { + mIcon.setImageDrawable(d); + } else { + mIcon.setImageResource(mIconResourceId); } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarFacetButtonController.java b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarFacetButtonController.java index e8c9a5e5693a..284113624cc1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarFacetButtonController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarFacetButtonController.java @@ -5,8 +5,6 @@ import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; -import android.view.View; -import android.view.ViewGroup; import java.util.HashMap; import java.util.List; @@ -29,39 +27,25 @@ public class CarFacetButtonController { } /** - * Goes through the supplied CarNavigationBarView and keeps track of all the CarFacetButtons - * such that it can select and unselect them based on running task chages - * @param bar that may contain CarFacetButtons + * Add facet button to this controller. The expected use is for the facet button + * to get a reference to this controller via {@link com.android.systemui.Dependency} + * and self add. + * @param facetButton */ - public void addCarNavigationBar(CarNavigationBarView bar) { - findFacets(bar); - } - - private void findFacets(ViewGroup root) { - final int childCount = root.getChildCount(); - - for (int i = 0; i < childCount; ++i) { - final View v = root.getChildAt(i); - if (v instanceof CarFacetButton) { - CarFacetButton facetButton = (CarFacetButton) v; - String[] categories = facetButton.getCategories(); - for (int j = 0; j < categories.length; j++) { - String category = categories[j]; - mButtonsByCategory.put(category, facetButton); - } + public void addFacetButton(CarFacetButton facetButton) { + String[] categories = facetButton.getCategories(); + for (int j = 0; j < categories.length; j++) { + String category = categories[j]; + mButtonsByCategory.put(category, facetButton); + } - String[] facetPackages = facetButton.getFacetPackages(); - for (int j = 0; j < facetPackages.length; j++) { - String facetPackage = facetPackages[j]; - mButtonsByPackage.put(facetPackage, facetButton); - } - } else if (v instanceof ViewGroup) { - findFacets((ViewGroup) v); - } + String[] facetPackages = facetButton.getFacetPackages(); + for (int j = 0; j < facetPackages.length; j++) { + String facetPackage = facetPackages[j]; + mButtonsByPackage.put(facetPackage, facetButton); } } - /** * This will unselect the currently selected CarFacetButton and determine which one should be * selected next. It does this by reading the properties on the CarFacetButton and seeing if diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarNavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarNavigationBarView.java index 1d9ef616d98d..e73b1736f33a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarNavigationBarView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarNavigationBarView.java @@ -22,6 +22,7 @@ import android.util.AttributeSet; import android.util.Log; import android.view.View; import android.widget.LinearLayout; +import android.widget.TextView; import com.android.keyguard.AlphaOptimizedImageButton; import com.android.systemui.R; @@ -36,9 +37,11 @@ class CarNavigationBarView extends LinearLayout { private LinearLayout mNavButtons; private AlphaOptimizedImageButton mNotificationsButton; private CarStatusBar mCarStatusBar; + private Context mContext; public CarNavigationBarView(Context context, AttributeSet attrs) { super(context, attrs); + mContext = context; } @Override @@ -46,7 +49,9 @@ class CarNavigationBarView extends LinearLayout { mNavButtons = findViewById(R.id.nav_buttons); mNotificationsButton = findViewById(R.id.notifications); - mNotificationsButton.setOnClickListener(this::onNotificationsClick); + if (mNotificationsButton != null) { + mNotificationsButton.setOnClickListener(this::onNotificationsClick); + } } void setStatusBar(CarStatusBar carStatusBar) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java index c15a01330534..a95d0a4dc7b8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java @@ -17,13 +17,9 @@ package com.android.systemui.statusbar.car; import android.app.ActivityManager; -import android.app.ActivityOptions; -import android.content.Intent; import android.graphics.PixelFormat; import android.graphics.drawable.Drawable; -import android.os.Bundle; -import android.os.RemoteException; -import android.os.UserHandle; +import android.os.SystemProperties; import android.util.Log; import android.view.Gravity; import android.view.View; @@ -45,8 +41,8 @@ import com.android.systemui.recents.Recents; import com.android.systemui.recents.misc.SysUiTaskStackChangeListener; import com.android.systemui.shared.system.ActivityManagerWrapper; import com.android.systemui.statusbar.StatusBarState; +import com.android.systemui.statusbar.car.hvac.HvacController; import com.android.systemui.statusbar.phone.CollapsedStatusBarFragment; -import com.android.systemui.statusbar.phone.NavigationBarView; import com.android.systemui.statusbar.phone.StatusBar; import com.android.systemui.statusbar.policy.BatteryController; import com.android.systemui.statusbar.policy.UserSwitcherController; @@ -60,6 +56,8 @@ import java.util.Map; public class CarStatusBar extends StatusBar implements CarBatteryController.BatteryViewHandler { private static final String TAG = "CarStatusBar"; + public static final boolean ENABLE_HVAC_CONNECTION + = !SystemProperties.getBoolean("android.car.hvac.demo", true); private TaskStackListenerImpl mTaskStackListener; @@ -93,6 +91,11 @@ public class CarStatusBar extends StatusBar implements createBatteryController(); mCarBatteryController.startListening(); + + if (ENABLE_HVAC_CONNECTION) { + Log.d(TAG, "Connecting to HVAC service"); + Dependency.get(HvacController.class).connectToCarService(); + } } @Override @@ -164,7 +167,7 @@ public class CarStatusBar extends StatusBar implements @Override protected void createNavigationBar() { - mCarFacetButtonController = new CarFacetButtonController(mContext); + mCarFacetButtonController = Dependency.get(CarFacetButtonController.class); if (mNavigationBarView != null) { return; } @@ -225,7 +228,6 @@ public class CarStatusBar extends StatusBar implements lp.windowAnimations = 0; - mCarFacetButtonController.addCarNavigationBar(mNavigationBarView); mWindowManager.addView(mNavigationBarWindow, lp); } @@ -243,7 +245,6 @@ public class CarStatusBar extends StatusBar implements throw new RuntimeException("Unable to build left nav bar due to missing layout"); } mLeftNavigationBarView.setStatusBar(this); - mCarFacetButtonController.addCarNavigationBar(mLeftNavigationBarView); WindowManager.LayoutParams leftlp = new WindowManager.LayoutParams( widthForSides, LayoutParams.MATCH_PARENT, @@ -275,7 +276,6 @@ public class CarStatusBar extends StatusBar implements throw new RuntimeException("Unable to build right nav bar due to missing layout"); } mRightNavigationBarView.setStatusBar(this); - mCarFacetButtonController.addCarNavigationBar(mRightNavigationBarView); WindowManager.LayoutParams rightlp = new WindowManager.LayoutParams( widthForSides, LayoutParams.MATCH_PARENT, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/car/hvac/HvacController.java b/packages/SystemUI/src/com/android/systemui/statusbar/car/hvac/HvacController.java new file mode 100644 index 000000000000..23bf88796da3 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/car/hvac/HvacController.java @@ -0,0 +1,205 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.car.hvac; + +import android.car.Car; +import android.car.CarNotConnectedException; +import android.car.hardware.CarPropertyValue; +import android.car.hardware.hvac.CarHvacManager; +import android.car.hardware.hvac.CarHvacManager.CarHvacEventCallback; +import android.content.ComponentName; +import android.content.Context; +import android.content.ServiceConnection; +import android.os.Handler; +import android.os.IBinder; +import android.util.Log; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Objects; + +/** + * Manages the connection to the Car service and delegates value changes to the registered + * {@link TemperatureView}s + */ +public class HvacController { + + public static final String TAG = "HvacController"; + public final static int BIND_TO_HVAC_RETRY_DELAY = 5000; + + private Context mContext; + private Handler mHandler; + private Car mCar; + private CarHvacManager mHvacManager; + private HashMap<HvacKey, TemperatureView> mTempComponents = new HashMap<>(); + + public HvacController(Context context) { + mContext = context; + } + + /** + * Create connection to the Car service. Note: call backs from the Car service + * ({@link CarHvacManager}) will happen on the same thread this method was called from. + */ + public void connectToCarService() { + mHandler = new Handler(); + mCar = Car.createCar(mContext, mServiceConnection, mHandler); + if (mCar != null) { + // note: this connect call handles the retries + mCar.connect(); + } + } + + /** + * Registers callbacks and initializes components upon connection. + */ + private ServiceConnection mServiceConnection = new ServiceConnection() { + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + try { + service.linkToDeath(mRestart, 0); + mHvacManager = (CarHvacManager) mCar.getCarManager(Car.HVAC_SERVICE); + mHvacManager.registerCallback(mHardwareCallback); + initComponents(); + } catch (Exception e) { + Log.e(TAG, "Failed to correctly connect to HVAC", e); + } + } + + @Override + public void onServiceDisconnected(ComponentName name) { + destroyHvacManager(); + } + }; + + private void destroyHvacManager() { + if (mHvacManager != null) { + mHvacManager.unregisterCallback(mHardwareCallback); + mHvacManager = null; + } + } + + /** + * If the connection to car service goes away then restart it. + */ + private final IBinder.DeathRecipient mRestart = new IBinder.DeathRecipient() { + @Override + public void binderDied() { + Log.d(TAG, "Death of HVAC triggering a restart"); + if (mCar != null) { + mCar.disconnect(); + } + destroyHvacManager(); + mHandler.postDelayed(() -> mCar.connect(), BIND_TO_HVAC_RETRY_DELAY); + } + }; + + /** + * Add component to list and initialize it if the connection is up. + * @param temperatureView + */ + public void addHvacTextView(TemperatureView temperatureView) { + mTempComponents.put( + new HvacKey(temperatureView.getPropertyId(), temperatureView.getAreaId()), + temperatureView); + initComponent(temperatureView); + } + + private void initComponents() { + Iterator<Map.Entry<HvacKey, TemperatureView>> iterator = + mTempComponents.entrySet().iterator(); + while (iterator.hasNext()) { + Map.Entry<HvacKey, TemperatureView> next = iterator.next(); + initComponent(next.getValue()); + } + } + + + private void initComponent(TemperatureView view) { + int id = view.getPropertyId(); + int zone = view.getAreaId(); + try { + if (mHvacManager == null || !mHvacManager.isPropertyAvailable(id, zone)) { + view.setTemp(Float.NaN); + return; + } + view.setTemp(mHvacManager.getFloatProperty(id, zone)); + } catch (CarNotConnectedException e) { + view.setTemp(Float.NaN); + Log.e(TAG, "Failed to get value from hvac service", e); + } + } + + /** + * Callback for getting changes from {@link CarHvacManager} and setting the UI elements to + * match. + */ + private final CarHvacEventCallback mHardwareCallback = new CarHvacEventCallback() { + @Override + public void onChangeEvent(final CarPropertyValue val) { + try { + int areaId = val.getAreaId(); + int propertyId = val.getPropertyId(); + TemperatureView temperatureView = mTempComponents.get( + new HvacKey(propertyId, areaId)); + if (temperatureView != null) { + float value = (float) val.getValue(); + temperatureView.setTemp(value); + } // else the data is not of interest + } catch (Exception e) { + // catch all so we don't take down the sysui if a new data type is + // introduced. + Log.e(TAG, "Failed handling hvac change event", e); + } + } + + @Override + public void onErrorEvent(final int propertyId, final int zone) { + Log.d(TAG, "HVAC error event, propertyId: " + propertyId + + " zone: " + zone); + } + }; + + /** + * Key for storing {@link TemperatureView}s in a hash map + */ + private static class HvacKey { + + int mPropertyId; + int mAreaId; + + public HvacKey(int propertyId, int areaId) { + mPropertyId = propertyId; + mAreaId = areaId; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + HvacKey hvacKey = (HvacKey) o; + return mPropertyId == hvacKey.mPropertyId && + mAreaId == hvacKey.mAreaId; + } + + @Override + public int hashCode() { + return Objects.hash(mPropertyId, mAreaId); + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/car/hvac/TemperatureView.java b/packages/SystemUI/src/com/android/systemui/statusbar/car/hvac/TemperatureView.java new file mode 100644 index 000000000000..4049ec3aa385 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/car/hvac/TemperatureView.java @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.car.hvac; + +import android.content.Context; +import android.content.res.TypedArray; +import android.util.AttributeSet; +import android.widget.TextView; + +import com.android.systemui.Dependency; +import com.android.systemui.R; + +/** + * Simple text display of HVAC properties, It is designed to show temperature and is configured in + * the XML. + * XML properties: + * hvacPropertyId - Example: CarHvacManager.ID_ZONED_TEMP_SETPOINT (16385) + * hvacAreaId - Example: VehicleSeat.SEAT_ROW_1_LEFT (1) + * hvacTempFormat - Example: "%.1f\u00B0" (1 decimal and the degree symbol) + * + * Note: It registers itself with {@link HvacController} + */ +public class TemperatureView extends TextView { + + private final int mAreaId; + private final int mPropertyId; + private final String mTempFormat; + + public TemperatureView(Context context, AttributeSet attrs) { + super(context, attrs); + TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.TemperatureView); + mAreaId = typedArray.getInt(R.styleable.TemperatureView_hvacAreaId,-1); + mPropertyId = typedArray.getInt(R.styleable.TemperatureView_hvacPropertyId, -1); + String format = typedArray.getString(R.styleable.TemperatureView_hvacTempFormat); + mTempFormat = (format == null) ? "%.1f\u00B0" : format; + + // register with controller + HvacController hvacController = Dependency.get(HvacController.class); + hvacController.addHvacTextView(this); + } + + /** + * Formats the float for display + * @param temp - The current temp or NaN + */ + public void setTemp(float temp) { + if (Float.isNaN(temp)) { + setText("--"); + return; + } + setText(String.format(mTempFormat, temp)); + } + + /** + * @return propertiyId Example: CarHvacManager.ID_ZONED_TEMP_SETPOINT (16385) + */ + public int getPropertyId() { + return mPropertyId; + } + + /** + * @return hvac AreaId - Example: VehicleSeat.SEAT_ROW_1_LEFT (1) + */ + public int getAreaId() { + return mAreaId; + } +} + diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java index b4cb088621fd..84582b0278f2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java @@ -79,7 +79,6 @@ import java.io.PrintWriter; import java.util.function.Consumer; import static com.android.systemui.shared.system.NavigationBarCompat.FLAG_DISABLE_QUICK_SCRUB; -import static com.android.systemui.shared.system.NavigationBarCompat.FLAG_DISABLE_SWIPE_UP; import static com.android.systemui.shared.system.NavigationBarCompat.FLAG_HIDE_BACK_BUTTON; import static com.android.systemui.shared.system.NavigationBarCompat.FLAG_SHOW_OVERVIEW_BUTTON; import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_OVERVIEW; @@ -385,17 +384,13 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav } public boolean isQuickStepSwipeUpEnabled() { - return mOverviewProxyService.getProxy() != null - && isOverviewEnabled() - && ((mOverviewProxyService.getInteractionFlags() - & FLAG_DISABLE_SWIPE_UP) == 0); + return mOverviewProxyService.shouldShowSwipeUpUI() && isOverviewEnabled(); } public boolean isQuickScrubEnabled() { return SystemProperties.getBoolean("persist.quickstep.scrub.enabled", true) && mOverviewProxyService.getProxy() != null && isOverviewEnabled() - && ((mOverviewProxyService.getInteractionFlags() - & FLAG_DISABLE_QUICK_SCRUB) == 0); + && ((mOverviewProxyService.getInteractionFlags() & FLAG_DISABLE_QUICK_SCRUB) == 0); } private void updateCarModeIcons(Context ctx) { @@ -468,7 +463,7 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav private KeyButtonDrawable chooseNavigationIconDrawable(Context ctx, @DrawableRes int iconLight, @DrawableRes int iconDark, @DrawableRes int quickStepIconLight, @DrawableRes int quickStepIconDark) { - final boolean quickStepEnabled = isQuickStepSwipeUpEnabled() || isQuickScrubEnabled(); + final boolean quickStepEnabled = mOverviewProxyService.shouldShowSwipeUpUI(); return quickStepEnabled ? getDrawable(ctx, quickStepIconLight, quickStepIconDark) : getDrawable(ctx, iconLight, iconDark); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonRipple.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonRipple.java index cc7943b85bc1..8e32a0b44c28 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonRipple.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonRipple.java @@ -26,9 +26,12 @@ import android.graphics.ColorFilter; import android.graphics.Paint; import android.graphics.PixelFormat; import android.graphics.drawable.Drawable; +import android.os.Handler; +import android.os.SystemProperties; import android.view.DisplayListCanvas; import android.view.RenderNodeAnimator; import android.view.View; +import android.view.ViewConfiguration; import android.view.animation.Interpolator; import com.android.systemui.Interpolators; @@ -56,14 +59,17 @@ public class KeyButtonRipple extends Drawable { private float mGlowAlpha = 0f; private float mGlowScale = 1f; private boolean mPressed; + private boolean mVisible; private boolean mDrawingHardwareGlow; private int mMaxWidth; private boolean mLastDark; private boolean mDark; + private boolean mDelayTouchFeedback; private final Interpolator mInterpolator = new LogInterpolator(); private boolean mSupportHardware; private final View mTargetView; + private final Handler mHandler = new Handler(); private final HashSet<Animator> mRunningAnimations = new HashSet<>(); private final ArrayList<Animator> mTmpArray = new ArrayList<>(); @@ -77,6 +83,10 @@ public class KeyButtonRipple extends Drawable { mDark = darkIntensity >= 0.5f; } + public void setDelayTouchFeedback(boolean delay) { + mDelayTouchFeedback = delay; + } + private Paint getRipplePaint() { if (mRipplePaint == null) { mRipplePaint = new Paint(); @@ -211,7 +221,16 @@ public class KeyButtonRipple extends Drawable { } } + /** + * Abort the ripple while it is delayed and before shown used only when setShouldDelayStartTouch + * is enabled. + */ + public void abortDelayedRipple() { + mHandler.removeCallbacksAndMessages(null); + } + private void cancelAnimations() { + mVisible = false; mTmpArray.addAll(mRunningAnimations); int size = mTmpArray.size(); for (int i = 0; i < size; i++) { @@ -220,11 +239,21 @@ public class KeyButtonRipple extends Drawable { } mTmpArray.clear(); mRunningAnimations.clear(); + mHandler.removeCallbacksAndMessages(null); } private void setPressedSoftware(boolean pressed) { if (pressed) { - enterSoftware(); + if (mDelayTouchFeedback) { + if (mRunningAnimations.isEmpty()) { + mHandler.removeCallbacksAndMessages(null); + mHandler.postDelayed(this::enterSoftware, ViewConfiguration.getTapTimeout()); + } else if (mVisible) { + enterSoftware(); + } + } else { + enterSoftware(); + } } else { exitSoftware(); } @@ -232,6 +261,7 @@ public class KeyButtonRipple extends Drawable { private void enterSoftware() { cancelAnimations(); + mVisible = true; mGlowAlpha = getMaxGlowAlpha(); ObjectAnimator scaleAnimator = ObjectAnimator.ofFloat(this, "glowScale", 0f, GLOW_MAX_SCALE_FACTOR); @@ -240,6 +270,12 @@ public class KeyButtonRipple extends Drawable { scaleAnimator.addListener(mAnimatorListener); scaleAnimator.start(); mRunningAnimations.add(scaleAnimator); + + // With the delay, it could eventually animate the enter animation with no pressed state, + // then immediately show the exit animation. If this is skipped there will be no ripple. + if (mDelayTouchFeedback && !mPressed) { + exitSoftware(); + } } private void exitSoftware() { @@ -253,7 +289,16 @@ public class KeyButtonRipple extends Drawable { private void setPressedHardware(boolean pressed) { if (pressed) { - enterHardware(); + if (mDelayTouchFeedback) { + if (mRunningAnimations.isEmpty()) { + mHandler.removeCallbacksAndMessages(null); + mHandler.postDelayed(this::enterHardware, ViewConfiguration.getTapTimeout()); + } else if (mVisible) { + enterHardware(); + } + } else { + enterHardware(); + } } else { exitHardware(); } @@ -302,6 +347,7 @@ public class KeyButtonRipple extends Drawable { private void enterHardware() { cancelAnimations(); + mVisible = true; mDrawingHardwareGlow = true; setExtendStart(CanvasProperty.createFloat(getExtendSize() / 2)); final RenderNodeAnimator startAnim = new RenderNodeAnimator(getExtendStart(), @@ -343,6 +389,12 @@ public class KeyButtonRipple extends Drawable { mRunningAnimations.add(endAnim); invalidateSelf(); + + // With the delay, it could eventually animate the enter animation with no pressed state, + // then immediately show the exit animation. If this is skipped there will be no ripple. + if (mDelayTouchFeedback && !mPressed) { + exitHardware(); + } } private void exitHardware() { @@ -366,6 +418,7 @@ public class KeyButtonRipple extends Drawable { public void onAnimationEnd(Animator animation) { mRunningAnimations.remove(animation); if (mRunningAnimations.isEmpty() && !mPressed) { + mVisible = false; mDrawingHardwareGlow = false; invalidateSelf(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java index e5fefd34ffb7..5d7e9383930a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java @@ -65,7 +65,6 @@ public class KeyButtonView extends ImageView implements ButtonInterface { private int mTouchSlop; private int mTouchDownX; private int mTouchDownY; - private boolean mIsPressed; private boolean mSupportsLongpress = true; private AudioManager mAudioManager; private boolean mGestureAborted; @@ -78,7 +77,7 @@ public class KeyButtonView extends ImageView implements ButtonInterface { private final Runnable mCheckLongPress = new Runnable() { public void run() { - if (mIsPressed) { + if (isPressed()) { // Log.d("KeyButtonView", "longpressed: " + this); if (isLongClickable()) { // Just an old-fashioned ImageView @@ -89,12 +88,6 @@ public class KeyButtonView extends ImageView implements ButtonInterface { sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_LONG_CLICKED); mLongClicked = true; } - - // Only when quick step is enabled, ripple will not be shown on touch down, then - // show the ripple on touch up or on long press - if (mLongClicked && mOverviewProxyService.getProxy() != null) { - setPressed(true); - } } } }; @@ -214,9 +207,7 @@ public class KeyButtonView extends ImageView implements ButtonInterface { mGestureAborted = false; } if (mGestureAborted) { - if (mIsPressed) { - setPressed(false); - } + setPressed(false); return false; } @@ -224,6 +215,7 @@ public class KeyButtonView extends ImageView implements ButtonInterface { case MotionEvent.ACTION_DOWN: mDownTime = SystemClock.uptimeMillis(); mLongClicked = false; + setPressed(true); // Use raw X and Y to detect gestures in case a parent changes the x and y values mTouchDownX = (int) ev.getRawX(); @@ -234,10 +226,8 @@ public class KeyButtonView extends ImageView implements ButtonInterface { // Provide the same haptic feedback that the system offers for virtual keys. performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY); } - mIsPressed = true; if (!isProxyConnected) { playSoundEffect(SoundEffectConstants.CLICK); - setPressed(mIsPressed); } removeCallbacks(mCheckLongPress); postDelayed(mCheckLongPress, ViewConfiguration.getLongPressTimeout()); @@ -250,10 +240,7 @@ public class KeyButtonView extends ImageView implements ButtonInterface { if (exceededTouchSlopX || exceededTouchSlopY) { // When quick step is enabled, prevent animating the ripple triggered by // setPressed and decide to run it on touch up - mIsPressed = false; - if (!isProxyConnected) { - setPressed(mIsPressed); - } + setPressed(false); removeCallbacks(mCheckLongPress); } break; @@ -265,12 +252,11 @@ public class KeyButtonView extends ImageView implements ButtonInterface { removeCallbacks(mCheckLongPress); break; case MotionEvent.ACTION_UP: - final boolean doIt = mIsPressed && !mLongClicked; + final boolean doIt = isPressed() && !mLongClicked; + setPressed(false); final boolean doHapticFeedback = (SystemClock.uptimeMillis() - mDownTime) > 150; if (isProxyConnected) { if (doIt) { - // Animate the ripple in on touch up with setPressed and then out later - setPressed(true); if (doHapticFeedback) { mVibratorHelper.vibrate(VibrationEffect.EFFECT_TICK); } @@ -281,7 +267,6 @@ public class KeyButtonView extends ImageView implements ButtonInterface { // and it feels weird to sometimes get a release haptic and other times not. performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY_RELEASE); } - setPressed(false); if (mCode != 0) { if (doIt) { // If there was a pending remote recents animation, then we need to @@ -311,12 +296,6 @@ public class KeyButtonView extends ImageView implements ButtonInterface { mAudioManager.playSoundEffect(soundConstant, ActivityManager.getCurrentUser()); } - @Override - public void setPressed(boolean pressed) { - mIsPressed = pressed; - super.setPressed(pressed); - } - public void sendEvent(int action, int flags) { sendEvent(action, flags, SystemClock.uptimeMillis()); } @@ -339,6 +318,7 @@ public class KeyButtonView extends ImageView implements ButtonInterface { @Override public void abortCurrentGesture() { setPressed(false); + mRipple.abortDelayedRipple(); mGestureAborted = true; } @@ -357,6 +337,7 @@ public class KeyButtonView extends ImageView implements ButtonInterface { @Override public void setDelayTouchFeedback(boolean shouldDelay) { + mRipple.setDelayTouchFeedback(shouldDelay); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java index d9cad0e4fa7c..71c7f80ef659 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java @@ -36,6 +36,7 @@ import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.res.ColorStateList; +import android.content.res.Configuration; import android.content.res.Resources; import android.graphics.Color; import android.graphics.PixelFormat; @@ -60,6 +61,7 @@ import android.view.View; import android.view.View.AccessibilityDelegate; import android.view.View.OnAttachStateChangeListener; import android.view.ViewGroup; +import android.view.ViewPropertyAnimator; import android.view.Window; import android.view.WindowManager; import android.view.accessibility.AccessibilityEvent; @@ -192,7 +194,7 @@ public class VolumeDialogImpl implements VolumeDialog { mDialog.setCanceledOnTouchOutside(true); mDialog.setContentView(R.layout.volume_dialog); mDialog.setOnShowListener(dialog -> { - mDialogView.setTranslationX(mDialogView.getWidth() / 2); + if (!isLandscape()) mDialogView.setTranslationX(mDialogView.getWidth() / 2); mDialogView.setAlpha(0); mDialogView.animate() .alpha(1) @@ -254,6 +256,11 @@ public class VolumeDialogImpl implements VolumeDialog { return ColorStateList.valueOf(mContext.getColor(colorResId)); } + private boolean isLandscape() { + return mContext.getResources().getConfiguration().orientation == + Configuration.ORIENTATION_LANDSCAPE; + } + public void setStreamImportant(int stream, boolean important) { mHandler.obtainMessage(H.SET_STREAM_IMPORTANT, stream, important ? 1 : 0).sendToTarget(); } @@ -509,16 +516,16 @@ public class VolumeDialogImpl implements VolumeDialog { mDialogView.setTranslationX(0); mDialogView.setAlpha(1); - mDialogView.animate() + ViewPropertyAnimator animator = mDialogView.animate() .alpha(0) - .translationX(mDialogView.getWidth() / 2) .setDuration(250) .setInterpolator(new SystemUIInterpolators.LogAccelerateInterpolator()) .withEndAction(() -> mHandler.postDelayed(() -> { if (D.BUG) Log.d(TAG, "mDialog.dismiss()"); mDialog.dismiss(); - }, 50)) - .start(); + }, 50)); + if (!isLandscape()) animator.translationX(mDialogView.getWidth() / 2); + animator.start(); Events.writeEvent(mContext, Events.EVENT_DISMISS_DIALOG, reason); mController.notifyVisible(false); diff --git a/packages/SystemUI/tests/Android.mk b/packages/SystemUI/tests/Android.mk index 107ce1eecf2f..a6b09ced2d81 100644 --- a/packages/SystemUI/tests/Android.mk +++ b/packages/SystemUI/tests/Android.mk @@ -75,7 +75,7 @@ LOCAL_JAVA_LIBRARIES := \ android.test.runner \ telephony-common \ android.test.base \ - + android.car LOCAL_AAPT_FLAGS := --extra-packages com.android.systemui:com.android.keyguard diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInfoTest.java index 7dda8b268b3e..c2cb5b9940eb 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInfoTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInfoTest.java @@ -218,6 +218,18 @@ public class NotificationInfoTest extends SysuiTestCase { } @Test + public void testBindNotification_DefaultChannelUsesChannelNameIfMoreChannelsExist() + throws Exception { + // Package has one channel by default. + when(mMockINotificationManager.getNumNotificationChannelsForPackage( + eq(TEST_PACKAGE_NAME), eq(TEST_UID), anyBoolean())).thenReturn(10); + mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, + TEST_PACKAGE_NAME, mDefaultNotificationChannel, 1, mSbn, null, null, null, null); + final TextView textView = mNotificationInfo.findViewById(R.id.channel_name); + assertEquals(VISIBLE, textView.getVisibility()); + } + + @Test public void testBindNotification_UnblockablePackageUsesChannelName() throws Exception { mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java index 9c421c633c30..d4ecd8b93859 100644 --- a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java +++ b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java @@ -81,7 +81,6 @@ import com.android.internal.util.Preconditions; import com.android.server.FgThread; import com.android.server.LocalServices; import com.android.server.SystemService; -import com.android.server.autofill.AutofillManagerService.PackageCompatState; import com.android.server.autofill.ui.AutoFillUI; import java.io.FileDescriptor; diff --git a/services/autofill/java/com/android/server/autofill/ui/FillUi.java b/services/autofill/java/com/android/server/autofill/ui/FillUi.java index 25d0d5c8c13f..7c0671ff5e1b 100644 --- a/services/autofill/java/com/android/server/autofill/ui/FillUi.java +++ b/services/autofill/java/com/android/server/autofill/ui/FillUi.java @@ -19,6 +19,7 @@ import static com.android.server.autofill.Helper.paramsToString; import static com.android.server.autofill.Helper.sDebug; import static com.android.server.autofill.Helper.sFullScreenMode; import static com.android.server.autofill.Helper.sVerbose; +import static com.android.server.autofill.Helper.sVisibleDatasetsMaxCount; import android.annotation.AttrRes; import android.annotation.NonNull; @@ -60,8 +61,6 @@ import com.android.internal.R; import com.android.server.UiThread; import com.android.server.autofill.Helper; -import static com.android.server.autofill.Helper.sVisibleDatasetsMaxCount; - import java.io.PrintWriter; import java.util.ArrayList; import java.util.Collections; @@ -131,6 +130,7 @@ final class FillUi { private @Nullable AnnounceFilterResult mAnnounceFilterResult; private final boolean mFullScreen; + private final int mVisibleDatasetsMaxCount; private int mContentWidth; private int mContentHeight; @@ -191,6 +191,16 @@ final class FillUi { } } + if (sVisibleDatasetsMaxCount > 0) { + mVisibleDatasetsMaxCount = sVisibleDatasetsMaxCount; + if (sVerbose) { + Slog.v(TAG, "overriding maximum visible datasets to " + mVisibleDatasetsMaxCount); + } + } else { + mVisibleDatasetsMaxCount = mContext.getResources() + .getInteger(com.android.internal.R.integer.autofill_max_visible_datasets); + } + final RemoteViews.OnClickHandler interceptionHandler = new RemoteViews.OnClickHandler() { @Override public boolean onClickHandler(View view, PendingIntent pendingIntent, @@ -247,7 +257,7 @@ final class FillUi { final int datasetCount = response.getDatasets().size(); if (sVerbose) { Slog.v(TAG, "Number datasets: " + datasetCount + " max visible: " - + sVisibleDatasetsMaxCount); + + mVisibleDatasetsMaxCount); } RemoteViews.OnClickHandler clickBlocker = null; @@ -386,7 +396,7 @@ final class FillUi { } requestShowFillUi(); } - if (mAdapter.getCount() > sVisibleDatasetsMaxCount) { + if (mAdapter.getCount() > mVisibleDatasetsMaxCount) { mListView.setVerticalScrollBarEnabled(true); mListView.onVisibilityAggregated(true); } else { @@ -492,7 +502,7 @@ final class FillUi { } } else { changed |= updateWidth(view, maxSize); - if (i < sVisibleDatasetsMaxCount) { + if (i < mVisibleDatasetsMaxCount) { changed |= updateHeight(view, maxSize); } } @@ -723,6 +733,8 @@ final class FillUi { public void dump(PrintWriter pw, String prefix) { pw.print(prefix); pw.print("mCallback: "); pw.println(mCallback != null); pw.print(prefix); pw.print("mFullScreen: "); pw.println(mFullScreen); + pw.print(prefix); pw.print("mVisibleDatasetsMaxCount: "); pw.println( + mVisibleDatasetsMaxCount); if (mHeader != null) { pw.print(prefix); pw.print("mHeader: "); pw.println(mHeader); } diff --git a/services/core/java/com/android/server/fingerprint/FingerprintService.java b/services/core/java/com/android/server/fingerprint/FingerprintService.java index 530b6f69545a..262a2f8ea817 100644 --- a/services/core/java/com/android/server/fingerprint/FingerprintService.java +++ b/services/core/java/com/android/server/fingerprint/FingerprintService.java @@ -62,7 +62,6 @@ import android.os.RemoteException; import android.os.SELinux; import android.os.ServiceManager; import android.os.SystemClock; -import android.os.SystemProperties; import android.os.UserHandle; import android.os.UserManager; import android.security.KeyStore; @@ -1488,10 +1487,7 @@ public class FingerprintService extends SystemService implements IHwBinder.Death userId = getUserOrWorkProfileId(clientPackage, userId); if (userId != mCurrentUserId) { File baseDir; - if (Build.VERSION.FIRST_SDK_INT <= Build.VERSION_CODES.O_MR1 - && !SystemProperties.getBoolean( - "ro.treble.supports_vendor_data", false)) { - // TODO(b/72405644) remove the override when possible. + if (Build.VERSION.FIRST_SDK_INT <= Build.VERSION_CODES.O_MR1) { baseDir = Environment.getUserSystemDirectory(userId); } else { baseDir = Environment.getDataVendorDeDirectory(userId); diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java index 8febecf0150d..ddb2a85eb233 100644 --- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java +++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java @@ -2338,7 +2338,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { } private void setUidPolicyUncheckedUL(int uid, int oldPolicy, int policy, boolean persist) { - setUidPolicyUncheckedUL(uid, policy, persist); + setUidPolicyUncheckedUL(uid, policy, false); final boolean notifyApp; if (!isUidValidForWhitelistRules(uid)) { @@ -2361,6 +2361,11 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { } mHandler.obtainMessage(MSG_POLICIES_CHANGED, uid, policy, Boolean.valueOf(notifyApp)) .sendToTarget(); + if (persist) { + synchronized (mNetworkPoliciesSecondLock) { + writePolicyAL(); + } + } } private void setUidPolicyUncheckedUL(int uid, int policy, boolean persist) { diff --git a/services/core/java/com/android/server/net/watchlist/NetworkWatchlistService.java b/services/core/java/com/android/server/net/watchlist/NetworkWatchlistService.java index 6907c58f13b0..29b1339e8022 100644 --- a/services/core/java/com/android/server/net/watchlist/NetworkWatchlistService.java +++ b/services/core/java/com/android/server/net/watchlist/NetworkWatchlistService.java @@ -171,7 +171,7 @@ public class NetworkWatchlistService extends INetworkWatchlistManager.Stub { Slog.w(TAG, "Only shell is allowed to call network watchlist shell commands"); return; } - (new NetworkWatchlistShellCommand(mContext)).exec(this, in, out, err, args, callback, + (new NetworkWatchlistShellCommand(this, mContext)).exec(this, in, out, err, args, callback, resultReceiver); } @@ -262,6 +262,21 @@ public class NetworkWatchlistService extends INetworkWatchlistManager.Stub { mNetworkWatchlistHandler.reportWatchlistIfNecessary(); } + /** + * Force generate watchlist report for testing. + * + * @param lastReportTime Watchlist report will cotain all records before this time. + * @return True if operation success. + */ + public boolean forceReportWatchlistForTest(long lastReportTime) { + if (mConfig.isConfigSecure()) { + // Should not force generate report under production config. + return false; + } + mNetworkWatchlistHandler.forceReportWatchlistForTest(lastReportTime); + return true; + } + @Override protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return; diff --git a/services/core/java/com/android/server/net/watchlist/NetworkWatchlistShellCommand.java b/services/core/java/com/android/server/net/watchlist/NetworkWatchlistShellCommand.java index 9533823df808..17c5868a53f7 100644 --- a/services/core/java/com/android/server/net/watchlist/NetworkWatchlistShellCommand.java +++ b/services/core/java/com/android/server/net/watchlist/NetworkWatchlistShellCommand.java @@ -19,9 +19,11 @@ package com.android.server.net.watchlist; import android.content.Context; import android.content.Intent; import android.net.NetworkWatchlistManager; +import android.os.Binder; import android.os.ParcelFileDescriptor; import android.os.RemoteException; import android.os.ShellCommand; +import android.provider.Settings; import java.io.FileInputStream; import java.io.IOException; @@ -34,10 +36,12 @@ import java.io.PrintWriter; */ class NetworkWatchlistShellCommand extends ShellCommand { - final NetworkWatchlistManager mNetworkWatchlistManager; + final Context mContext; + final NetworkWatchlistService mService; - NetworkWatchlistShellCommand(Context context) { - mNetworkWatchlistManager = new NetworkWatchlistManager(context); + NetworkWatchlistShellCommand(NetworkWatchlistService service, Context context) { + mContext = context; + mService = service; } @Override @@ -51,11 +55,13 @@ class NetworkWatchlistShellCommand extends ShellCommand { switch(cmd) { case "set-test-config": return runSetTestConfig(); + case "force-generate-report": + return runForceGenerateReport(); default: return handleDefaultCommands(cmd); } - } catch (RemoteException e) { - pw.println("Remote exception: " + e); + } catch (Exception e) { + pw.println("Exception: " + e); } return -1; } @@ -73,22 +79,44 @@ class NetworkWatchlistShellCommand extends ShellCommand { WatchlistConfig.getInstance().setTestMode(fileStream); } pw.println("Success!"); - } catch (RuntimeException | IOException ex) { + } catch (Exception ex) { pw.println("Error: " + ex.toString()); return -1; } return 0; } + private int runForceGenerateReport() throws RemoteException { + final PrintWriter pw = getOutPrintWriter(); + final long ident = Binder.clearCallingIdentity(); + try { + // Reset last report time + if (!WatchlistConfig.getInstance().isConfigSecure()) { + pw.println("Error: Cannot force generate report under production config"); + return -1; + } + Settings.Global.putLong(mContext.getContentResolver(), + Settings.Global.NETWORK_WATCHLIST_LAST_REPORT_TIME, 0L); + mService.forceReportWatchlistForTest(System.currentTimeMillis()); + pw.println("Success!"); + } catch (Exception ex) { + pw.println("Error: " + ex); + return -1; + } finally { + Binder.restoreCallingIdentity(ident); + } + return 0; + } + @Override public void onHelp() { final PrintWriter pw = getOutPrintWriter(); pw.println("Network watchlist manager commands:"); pw.println(" help"); pw.println(" Print this help text."); - pw.println(""); pw.println(" set-test-config your_watchlist_config.xml"); - pw.println(); - Intent.printIntentArgsHelp(pw , ""); + pw.println(" Set network watchlist test config file."); + pw.println(" force-generate-report"); + pw.println(" Force generate watchlist test report."); } } diff --git a/services/core/java/com/android/server/net/watchlist/PrivacyUtils.java b/services/core/java/com/android/server/net/watchlist/PrivacyUtils.java index c1231fa342e7..408a9ed31d18 100644 --- a/services/core/java/com/android/server/net/watchlist/PrivacyUtils.java +++ b/services/core/java/com/android/server/net/watchlist/PrivacyUtils.java @@ -19,6 +19,7 @@ package com.android.server.net.watchlist; import android.privacy.DifferentialPrivacyEncoder; import android.privacy.internal.longitudinalreporting.LongitudinalReportingConfig; import android.privacy.internal.longitudinalreporting.LongitudinalReportingEncoder; +import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; @@ -32,6 +33,7 @@ import java.util.Map; class PrivacyUtils { private static final String TAG = "PrivacyUtils"; + private static final boolean DEBUG = NetworkWatchlistService.DEBUG; /** * Parameters used for encoding watchlist reports. @@ -84,6 +86,7 @@ class PrivacyUtils { @VisibleForTesting static Map<String, Boolean> createDpEncodedReportMap(boolean isSecure, byte[] userSecret, List<String> appDigestList, WatchlistReportDbHelper.AggregatedResult aggregatedResult) { + if (DEBUG) Slog.i(TAG, "createDpEncodedReportMap start"); final int appDigestListSize = appDigestList.size(); final HashMap<String, Boolean> resultMap = new HashMap<>(appDigestListSize); for (int i = 0; i < appDigestListSize; i++) { @@ -93,6 +96,7 @@ class PrivacyUtils { ? createSecureDPEncoder(userSecret, appDigest) : createInsecureDPEncoderForTest(appDigest); final boolean visitedWatchlist = aggregatedResult.appDigestList.contains(appDigest); + if (DEBUG) Slog.i(TAG, appDigest + ": " + visitedWatchlist); // Get the least significant bit of first byte, and set result to True if it is 1 boolean encodedVisitedWatchlist = ((int) encoder.encodeBoolean(visitedWatchlist)[0] & 0x1) == 0x1; diff --git a/services/core/java/com/android/server/net/watchlist/WatchlistLoggingHandler.java b/services/core/java/com/android/server/net/watchlist/WatchlistLoggingHandler.java index e8b39c04a830..b331b9c0d9ae 100644 --- a/services/core/java/com/android/server/net/watchlist/WatchlistLoggingHandler.java +++ b/services/core/java/com/android/server/net/watchlist/WatchlistLoggingHandler.java @@ -43,6 +43,7 @@ import java.io.File; import java.io.IOException; import java.security.NoSuchAlgorithmException; import java.util.ArrayList; +import java.util.GregorianCalendar; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -61,6 +62,8 @@ class WatchlistLoggingHandler extends Handler { static final int LOG_WATCHLIST_EVENT_MSG = 1; @VisibleForTesting static final int REPORT_RECORDS_IF_NECESSARY_MSG = 2; + @VisibleForTesting + static final int FORCE_REPORT_RECORDS_NOW_FOR_TEST_MSG = 3; private static final long ONE_DAY_MS = TimeUnit.DAYS.toMillis(1); private static final String DROPBOX_TAG = "network_watchlist_report"; @@ -110,7 +113,15 @@ class WatchlistLoggingHandler extends Handler { break; } case REPORT_RECORDS_IF_NECESSARY_MSG: - tryAggregateRecords(); + tryAggregateRecords(getLastMidnightTime()); + break; + case FORCE_REPORT_RECORDS_NOW_FOR_TEST_MSG: + if (msg.obj instanceof Long) { + long lastRecordTime = (Long) msg.obj; + tryAggregateRecords(lastRecordTime); + } else { + Slog.e(TAG, "Msg.obj needs to be a Long object."); + } break; default: { Slog.d(TAG, "WatchlistLoggingHandler received an unknown of message."); @@ -146,6 +157,12 @@ class WatchlistLoggingHandler extends Handler { sendMessage(msg); } + public void forceReportWatchlistForTest(long lastReportTime) { + final Message msg = obtainMessage(FORCE_REPORT_RECORDS_NOW_FOR_TEST_MSG); + msg.obj = lastReportTime; + sendMessage(msg); + } + /** * Insert network traffic event to watchlist async queue processor. */ @@ -177,8 +194,14 @@ class WatchlistLoggingHandler extends Handler { } private boolean insertRecord(int uid, String cncHost, long timestamp) { + if (DEBUG) { + Slog.i(TAG, "trying to insert record with host: " + cncHost + ", uid: " + uid); + } if (!mConfig.isConfigSecure() && !isPackageTestOnly(uid)) { // Skip package if config is not secure and package is not TestOnly app. + if (DEBUG) { + Slog.i(TAG, "uid: " + uid + " is not test only package"); + } return true; } final byte[] digest = getDigestFromUid(uid); @@ -187,50 +210,56 @@ class WatchlistLoggingHandler extends Handler { return false; } final boolean result = mDbHelper.insertNewRecord(digest, cncHost, timestamp); - tryAggregateRecords(); return result; } - private boolean shouldReportNetworkWatchlist() { + private boolean shouldReportNetworkWatchlist(long lastRecordTime) { final long lastReportTime = Settings.Global.getLong(mResolver, Settings.Global.NETWORK_WATCHLIST_LAST_REPORT_TIME, 0L); - final long currentTimestamp = System.currentTimeMillis(); - if (currentTimestamp < lastReportTime) { + if (lastRecordTime < lastReportTime) { Slog.i(TAG, "Last report time is larger than current time, reset report"); - mDbHelper.cleanup(); + mDbHelper.cleanup(lastReportTime); return false; } - return currentTimestamp >= lastReportTime + ONE_DAY_MS; + return lastRecordTime >= lastReportTime + ONE_DAY_MS; } - private void tryAggregateRecords() { - // Check if it's necessary to generate watchlist report now. - if (!shouldReportNetworkWatchlist()) { - Slog.i(TAG, "No need to aggregate record yet."); - return; - } - Slog.i(TAG, "Start aggregating watchlist records."); - if (mDropBoxManager != null && mDropBoxManager.isTagEnabled(DROPBOX_TAG)) { - Settings.Global.putLong(mResolver, - Settings.Global.NETWORK_WATCHLIST_LAST_REPORT_TIME, - System.currentTimeMillis()); - final WatchlistReportDbHelper.AggregatedResult aggregatedResult = - mDbHelper.getAggregatedRecords(); - if (aggregatedResult == null) { - Slog.i(TAG, "Cannot get result from database"); + private void tryAggregateRecords(long lastRecordTime) { + long startTime = System.currentTimeMillis(); + try { + // Check if it's necessary to generate watchlist report now. + if (!shouldReportNetworkWatchlist(lastRecordTime)) { + Slog.i(TAG, "No need to aggregate record yet."); return; } - // Get all digests for watchlist report, it should include all installed - // application digests and previously recorded app digests. - final List<String> digestsForReport = getAllDigestsForReport(aggregatedResult); - final byte[] secretKey = mSettings.getPrivacySecretKey(); - final byte[] encodedResult = ReportEncoder.encodeWatchlistReport(mConfig, - secretKey, digestsForReport, aggregatedResult); - if (encodedResult != null) { - addEncodedReportToDropBox(encodedResult); + Slog.i(TAG, "Start aggregating watchlist records."); + if (mDropBoxManager != null && mDropBoxManager.isTagEnabled(DROPBOX_TAG)) { + Settings.Global.putLong(mResolver, + Settings.Global.NETWORK_WATCHLIST_LAST_REPORT_TIME, + lastRecordTime); + final WatchlistReportDbHelper.AggregatedResult aggregatedResult = + mDbHelper.getAggregatedRecords(lastRecordTime); + if (aggregatedResult == null) { + Slog.i(TAG, "Cannot get result from database"); + return; + } + // Get all digests for watchlist report, it should include all installed + // application digests and previously recorded app digests. + final List<String> digestsForReport = getAllDigestsForReport(aggregatedResult); + final byte[] secretKey = mSettings.getPrivacySecretKey(); + final byte[] encodedResult = ReportEncoder.encodeWatchlistReport(mConfig, + secretKey, digestsForReport, aggregatedResult); + if (encodedResult != null) { + addEncodedReportToDropBox(encodedResult); + } + } else { + Slog.w(TAG, "Network Watchlist dropbox tag is not enabled"); } + mDbHelper.cleanup(lastRecordTime); + } finally { + long endTime = System.currentTimeMillis(); + Slog.i(TAG, "Milliseconds spent on tryAggregateRecords(): " + (endTime - startTime)); } - mDbHelper.cleanup(); } /** @@ -379,4 +408,19 @@ class WatchlistLoggingHandler extends Handler { } return subDomainList.toArray(new String[0]); } + + static long getLastMidnightTime() { + return getMidnightTimestamp(0); + } + + static long getMidnightTimestamp(int daysBefore) { + java.util.Calendar date = new GregorianCalendar(); + // reset hour, minutes, seconds and millis + date.set(java.util.Calendar.HOUR_OF_DAY, 0); + date.set(java.util.Calendar.MINUTE, 0); + date.set(java.util.Calendar.SECOND, 0); + date.set(java.util.Calendar.MILLISECOND, 0); + date.add(java.util.Calendar.DAY_OF_MONTH, -daysBefore); + return date.getTimeInMillis(); + } } diff --git a/services/core/java/com/android/server/net/watchlist/WatchlistReportDbHelper.java b/services/core/java/com/android/server/net/watchlist/WatchlistReportDbHelper.java index 4b577bb9c919..632ab81b131e 100644 --- a/services/core/java/com/android/server/net/watchlist/WatchlistReportDbHelper.java +++ b/services/core/java/com/android/server/net/watchlist/WatchlistReportDbHelper.java @@ -141,11 +141,10 @@ class WatchlistReportDbHelper extends SQLiteOpenHelper { } /** - * Aggregate all records before most recent local midnight in database, and return a + * Aggregate all records in database before input timestamp, and return a * rappor encoded result. */ - public AggregatedResult getAggregatedRecords() { - final long lastMidnightTime = getLastMidnightTime(); + public AggregatedResult getAggregatedRecords(long untilTimestamp) { final String selectStatement = WhiteListReportContract.TIMESTAMP + " < ?"; final SQLiteDatabase db = getReadableDatabase(); @@ -153,7 +152,7 @@ class WatchlistReportDbHelper extends SQLiteOpenHelper { try { c = db.query(true /* distinct */, WhiteListReportContract.TABLE, DIGEST_DOMAIN_PROJECTION, selectStatement, - new String[]{"" + lastMidnightTime}, null, null, + new String[]{Long.toString(untilTimestamp)}, null, null, null, null); if (c == null) { return null; @@ -181,29 +180,13 @@ class WatchlistReportDbHelper extends SQLiteOpenHelper { } /** - * Remove all the records before most recent local midnight. + * Remove all the records before input timestamp. * * @return True if success. */ - public boolean cleanup() { + public boolean cleanup(long untilTimestamp) { final SQLiteDatabase db = getWritableDatabase(); - final long midnightTime = getLastMidnightTime(); - final String clause = WhiteListReportContract.TIMESTAMP + "< " + midnightTime; + final String clause = WhiteListReportContract.TIMESTAMP + "< " + untilTimestamp; return db.delete(WhiteListReportContract.TABLE, clause, null) != 0; } - - static long getLastMidnightTime() { - return getMidnightTimestamp(0); - } - - static long getMidnightTimestamp(int daysBefore) { - java.util.Calendar date = new GregorianCalendar(); - // reset hour, minutes, seconds and millis - date.set(java.util.Calendar.HOUR_OF_DAY, 0); - date.set(java.util.Calendar.MINUTE, 0); - date.set(java.util.Calendar.SECOND, 0); - date.set(java.util.Calendar.MILLISECOND, 0); - date.add(java.util.Calendar.DAY_OF_MONTH, -daysBefore); - return date.getTimeInMillis(); - } }
\ No newline at end of file diff --git a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java index c36227423066..9ca02bad50bd 100644 --- a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java +++ b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java @@ -559,7 +559,7 @@ public class UserRestrictionsUtils { android.provider.Settings.Global.AIRPLANE_MODE_ON, 0); // Post the intent. Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED); - intent.putExtra("state", 0); + intent.putExtra("state", false); context.sendBroadcastAsUser(intent, UserHandle.ALL); } } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java index 4020a5243d23..71c2ea18eb1b 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java @@ -122,12 +122,12 @@ abstract class BaseIDevicePolicyManager extends IDevicePolicyManager.Stub { } @Override - public List<String> setMeteredDataDisabled(ComponentName admin, List<String> packageNames) { + public List<String> setMeteredDataDisabledPackages(ComponentName admin, List<String> packageNames) { return packageNames; } @Override - public List<String> getMeteredDataDisabled(ComponentName admin) { + public List<String> getMeteredDataDisabledPackages(ComponentName admin) { return new ArrayList<>(); } @@ -163,7 +163,7 @@ abstract class BaseIDevicePolicyManager extends IDevicePolicyManager.Stub { } @Override - public boolean isMeteredDataDisabledForUser(ComponentName admin, + public boolean isMeteredDataDisabledPackageForUser(ComponentName admin, String packageName, int userId) { 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 1e216a3cff86..2fd776405b39 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -11448,7 +11448,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } @Override - public List<String> setMeteredDataDisabled(ComponentName who, List<String> packageNames) { + public List<String> setMeteredDataDisabledPackages(ComponentName who, List<String> packageNames) { Preconditions.checkNotNull(who); Preconditions.checkNotNull(packageNames); @@ -11498,7 +11498,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } @Override - public List<String> getMeteredDataDisabled(ComponentName who) { + public List<String> getMeteredDataDisabledPackages(ComponentName who) { Preconditions.checkNotNull(who); if (!mHasFeature) { @@ -11513,7 +11513,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } @Override - public boolean isMeteredDataDisabledForUser(ComponentName who, + public boolean isMeteredDataDisabledPackageForUser(ComponentName who, String packageName, int userId) { Preconditions.checkNotNull(who); diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java index a64efb793e5b..290de2b7d16a 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java @@ -2113,11 +2113,11 @@ public class DevicePolicyManagerTest extends DpmTestBase { } } - public void testSetGetMeteredDataDisabled() throws Exception { + public void testSetGetMeteredDataDisabledPackages() throws Exception { setAsProfileOwner(admin1); final ArrayList<String> emptyList = new ArrayList<>(); - assertEquals(emptyList, dpm.getMeteredDataDisabled(admin1)); + assertEquals(emptyList, dpm.getMeteredDataDisabledPackages(admin1)); // Setup final ArrayList<String> pkgsToRestrict = new ArrayList<>(); @@ -2127,40 +2127,40 @@ public class DevicePolicyManagerTest extends DpmTestBase { pkgsToRestrict.add(package2); setupPackageInPackageManager(package1, DpmMockContext.CALLER_USER_HANDLE, 123, 0); setupPackageInPackageManager(package2, DpmMockContext.CALLER_USER_HANDLE, 456, 0); - List<String> excludedPkgs = dpm.setMeteredDataDisabled(admin1, pkgsToRestrict); + List<String> excludedPkgs = dpm.setMeteredDataDisabledPackages(admin1, pkgsToRestrict); // Verify assertEquals(emptyList, excludedPkgs); - assertEquals(pkgsToRestrict, dpm.getMeteredDataDisabled(admin1)); + assertEquals(pkgsToRestrict, dpm.getMeteredDataDisabledPackages(admin1)); verify(getServices().networkPolicyManagerInternal).setMeteredRestrictedPackages( MockUtils.checkApps(pkgsToRestrict.toArray(new String[0])), eq(DpmMockContext.CALLER_USER_HANDLE)); // Setup pkgsToRestrict.remove(package1); - excludedPkgs = dpm.setMeteredDataDisabled(admin1, pkgsToRestrict); + excludedPkgs = dpm.setMeteredDataDisabledPackages(admin1, pkgsToRestrict); // Verify assertEquals(emptyList, excludedPkgs); - assertEquals(pkgsToRestrict, dpm.getMeteredDataDisabled(admin1)); + assertEquals(pkgsToRestrict, dpm.getMeteredDataDisabledPackages(admin1)); verify(getServices().networkPolicyManagerInternal).setMeteredRestrictedPackages( MockUtils.checkApps(pkgsToRestrict.toArray(new String[0])), eq(DpmMockContext.CALLER_USER_HANDLE)); } - public void testSetGetMeteredDataDisabled_deviceAdmin() { + public void testSetGetMeteredDataDisabledPackages_deviceAdmin() { mContext.callerPermissions.add(permission.MANAGE_DEVICE_ADMINS); dpm.setActiveAdmin(admin1, true); assertTrue(dpm.isAdminActive(admin1)); mContext.callerPermissions.remove(permission.MANAGE_DEVICE_ADMINS); assertExpectException(SecurityException.class, /* messageRegex= */ NOT_PROFILE_OWNER_MSG, - () -> dpm.setMeteredDataDisabled(admin1, new ArrayList<>())); + () -> dpm.setMeteredDataDisabledPackages(admin1, new ArrayList<>())); assertExpectException(SecurityException.class, /* messageRegex= */ NOT_PROFILE_OWNER_MSG, - () -> dpm.getMeteredDataDisabled(admin1)); + () -> dpm.getMeteredDataDisabledPackages(admin1)); } - public void testGetMeteredDataDisabledForUser() throws Exception { + public void testIsMeteredDataDisabledForUserPackage() throws Exception { setAsProfileOwner(admin1); // Setup @@ -2173,34 +2173,34 @@ public class DevicePolicyManagerTest extends DpmTestBase { pkgsToRestrict.add(package2); setupPackageInPackageManager(package1, DpmMockContext.CALLER_USER_HANDLE, 123, 0); setupPackageInPackageManager(package2, DpmMockContext.CALLER_USER_HANDLE, 456, 0); - List<String> excludedPkgs = dpm.setMeteredDataDisabled(admin1, pkgsToRestrict); + List<String> excludedPkgs = dpm.setMeteredDataDisabledPackages(admin1, pkgsToRestrict); // Verify assertEquals(emptyList, excludedPkgs); mContext.binder.callingUid = DpmMockContext.SYSTEM_UID; assertTrue(package1 + "should be restricted", - dpm.isMeteredDataDisabledForUser(admin1, package1, + dpm.isMeteredDataDisabledPackageForUser(admin1, package1, DpmMockContext.CALLER_USER_HANDLE)); assertTrue(package2 + "should be restricted", - dpm.isMeteredDataDisabledForUser(admin1, package2, + dpm.isMeteredDataDisabledPackageForUser(admin1, package2, DpmMockContext.CALLER_USER_HANDLE)); assertFalse(package3 + "should not be restricted", - dpm.isMeteredDataDisabledForUser(admin1, package3, + dpm.isMeteredDataDisabledPackageForUser(admin1, package3, DpmMockContext.CALLER_USER_HANDLE)); } - public void testGetMeteredDataDisabledForUser_nonSystemUidCaller() throws Exception { + public void testIsMeteredDataDisabledForUserPackage_nonSystemUidCaller() throws Exception { setAsProfileOwner(admin1); assertExpectException(SecurityException.class, /* messageRegex= */ "Only the system can query restricted pkgs", - () -> dpm.isMeteredDataDisabledForUser( + () -> dpm.isMeteredDataDisabledPackageForUser( admin1, "com.example.one", DpmMockContext.CALLER_USER_HANDLE)); dpm.clearProfileOwner(admin1); setDeviceOwner(); assertExpectException(SecurityException.class, /* messageRegex= */ "Only the system can query restricted pkgs", - () -> dpm.isMeteredDataDisabledForUser( + () -> dpm.isMeteredDataDisabledPackageForUser( admin1, "com.example.one", DpmMockContext.CALLER_USER_HANDLE)); clearDeviceOwner(); } diff --git a/telephony/java/android/telephony/MbmsDownloadSession.java b/telephony/java/android/telephony/MbmsDownloadSession.java index 9dc07c1659db..da04a0d1075c 100644 --- a/telephony/java/android/telephony/MbmsDownloadSession.java +++ b/telephony/java/android/telephony/MbmsDownloadSession.java @@ -920,11 +920,11 @@ public class MbmsDownloadSession implements AutoCloseable { try { if (!token.createNewFile()) { throw new RuntimeException("Failed to create download token for request " - + request); + + request + ". Token location is " + token.getPath()); } } catch (IOException e) { throw new RuntimeException("Failed to create download token for request " + request - + " due to IOException " + e); + + " due to IOException " + e + ". Attempted to write to " + token.getPath()); } } diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index da5bd84f9083..15e06321206a 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -7752,11 +7752,25 @@ public class TelephonyManager { */ public static final int INDICATION_FILTER_DATA_CALL_DORMANCY_CHANGED = 0x4; + /** + * The indication for link capacity estimate update. + * @hide + */ + public static final int INDICATION_FILTER_LINK_CAPACITY_ESTIMATE = 0x8; + + /** + * The indication for physical channel config update. + * @hide + */ + public static final int INDICATION_FILTER_PHYSICAL_CHANNEL_CONFIG = 0x10; + /** @hide */ @IntDef(flag = true, prefix = { "INDICATION_FILTER_" }, value = { INDICATION_FILTER_SIGNAL_STRENGTH, INDICATION_FILTER_FULL_NETWORK_STATE, - INDICATION_FILTER_DATA_CALL_DORMANCY_CHANGED + INDICATION_FILTER_DATA_CALL_DORMANCY_CHANGED, + INDICATION_FILTER_LINK_CAPACITY_ESTIMATE, + INDICATION_FILTER_PHYSICAL_CHANNEL_CONFIG }) @Retention(RetentionPolicy.SOURCE) public @interface IndicationFilters{} diff --git a/telephony/java/android/telephony/mbms/MbmsDownloadReceiver.java b/telephony/java/android/telephony/mbms/MbmsDownloadReceiver.java index b0c00c6284a6..fe7533f57b12 100644 --- a/telephony/java/android/telephony/mbms/MbmsDownloadReceiver.java +++ b/telephony/java/android/telephony/mbms/MbmsDownloadReceiver.java @@ -297,7 +297,9 @@ public class MbmsDownloadReceiver extends BroadcastReceiver { for (Uri tempFileUri : tempFiles) { if (verifyTempFilePath(context, request.getFileServiceId(), tempFileUri)) { File tempFile = new File(tempFileUri.getSchemeSpecificPart()); - tempFile.delete(); + if (!tempFile.delete()) { + Log.w(LOG_TAG, "Failed to delete temp file at " + tempFile.getPath()); + } } } } @@ -474,6 +476,8 @@ public class MbmsDownloadReceiver extends BroadcastReceiver { if (!MbmsUtils.isContainedIn( MbmsUtils.getEmbmsTempFileDirForService(context, serviceId), tempFile)) { + Log.w(LOG_TAG, "File at " + path + " is not contained in the temp file root," + + " which is " + MbmsUtils.getEmbmsTempFileDirForService(context, serviceId)); return false; } diff --git a/telephony/java/com/android/internal/telephony/RILConstants.java b/telephony/java/com/android/internal/telephony/RILConstants.java index ee7084ad86c0..d25fd3fdf60c 100644 --- a/telephony/java/com/android/internal/telephony/RILConstants.java +++ b/telephony/java/com/android/internal/telephony/RILConstants.java @@ -419,6 +419,8 @@ cat include/telephony/ril.h | \ int RIL_REQUEST_SET_LOGICAL_TO_PHYSICAL_SLOT_MAPPING = 145; int RIL_REQUEST_START_KEEPALIVE = 146; int RIL_REQUEST_STOP_KEEPALIVE = 147; + int RIL_REQUEST_SET_SIGNAL_STRENGTH_REPORTING_CRITERIA = 148; + int RIL_REQUEST_SET_LINK_CAPACITY_REPORTING_CRITERIA = 149; int RIL_RESPONSE_ACKNOWLEDGEMENT = 800; diff --git a/wifi/java/android/net/wifi/rtt/RangingResult.java b/wifi/java/android/net/wifi/rtt/RangingResult.java index 5518d35eb4c7..758a8d559e4e 100644 --- a/wifi/java/android/net/wifi/rtt/RangingResult.java +++ b/wifi/java/android/net/wifi/rtt/RangingResult.java @@ -170,7 +170,9 @@ public final class RangingResult implements Parcelable { /** * @return The standard deviation of the measured distance (in mm) to the device specified by * {@link #getMacAddress()} or {@link #getPeerHandle()}. The standard deviation is calculated - * over the measurements executed in a single RTT burst. + * over the measurements executed in a single RTT burst. The number of measurements is returned + * by {@link #getNumSuccessfulMeasurements()} - 0 successful measurements indicate that the + * standard deviation is not valid (a valid standard deviation requires at least 2 data points). * <p> * Only valid if {@link #getStatus()} returns {@link #STATUS_SUCCESS}, otherwise will throw an * exception. @@ -199,11 +201,12 @@ public final class RangingResult implements Parcelable { /** * @return The number of attempted measurements used in the RTT exchange resulting in this set - * of results. + * of results. The number of successful measurements is returned by + * {@link #getNumSuccessfulMeasurements()} which at most, if there are no errors, will be 1 less + * that the number of attempted measurements. * <p> * Only valid if {@link #getStatus()} returns {@link #STATUS_SUCCESS}, otherwise will throw an * exception. - * @hide */ public int getNumAttemptedMeasurements() { if (mStatus != STATUS_SUCCESS) { @@ -220,9 +223,12 @@ public final class RangingResult implements Parcelable { * returned by {@link #getDistanceStdDevMm()}, is not valid (a 0 is returned for the standard * deviation). * <p> + * The total number of measurement attempts is returned by + * {@link #getNumAttemptedMeasurements()}. The number of successful measurements will be at + * most 1 less then the number of attempted measurements. + * <p> * Only valid if {@link #getStatus()} returns {@link #STATUS_SUCCESS}, otherwise will throw an * exception. - * @hide */ public int getNumSuccessfulMeasurements() { if (mStatus != STATUS_SUCCESS) { |