summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--api/current.txt92
-rw-r--r--cmds/statsd/src/atoms.proto27
-rw-r--r--core/java/android/app/Activity.java2
-rw-r--r--core/java/android/app/admin/DevicePolicyManager.java14
-rw-r--r--core/java/android/app/admin/IDevicePolicyManager.aidl6
-rw-r--r--core/java/android/app/backup/FullBackup.java4
-rw-r--r--core/java/android/app/slice/Slice.java19
-rw-r--r--core/java/android/app/slice/SliceMetrics.java17
-rw-r--r--core/java/android/content/Intent.java4
-rw-r--r--core/java/android/hardware/camera2/CameraCharacteristics.java2
-rw-r--r--core/java/android/hardware/camera2/CameraMetadata.java7
-rw-r--r--core/java/android/hardware/camera2/CaptureRequest.java4
-rw-r--r--core/java/android/hardware/camera2/CaptureResult.java4
-rw-r--r--core/java/android/privacy/internal/longitudinalreporting/LongitudinalReportingEncoder.java13
-rw-r--r--core/java/android/view/textclassifier/DefaultLogger.java2
-rw-r--r--core/java/android/view/textclassifier/Logger.java48
-rw-r--r--core/java/android/view/textclassifier/SelectionEvent.java249
-rw-r--r--core/java/android/view/textclassifier/TextClassificationContext.java123
-rw-r--r--core/java/android/view/textclassifier/TextClassificationManager.java64
-rw-r--r--core/java/android/view/textclassifier/TextClassificationSession.java217
-rw-r--r--core/java/android/view/textclassifier/TextClassificationSessionFactory.java36
-rw-r--r--core/java/android/view/textclassifier/TextClassificationSessionId.java125
-rw-r--r--core/java/android/view/textclassifier/TextClassifier.java93
-rw-r--r--core/java/android/view/textclassifier/TextClassifierImpl.java14
-rw-r--r--core/java/android/widget/SelectionActionModeHelper.java66
-rw-r--r--core/java/android/widget/TextView.java55
-rw-r--r--core/java/com/android/internal/os/BatteryStatsImpl.java111
-rw-r--r--core/java/com/android/internal/widget/LockPatternView.java3
-rw-r--r--core/res/res/values/dimens.xml3
-rw-r--r--core/res/res/values/symbols.xml3
-rw-r--r--core/tests/coretests/src/android/app/backup/FullBackupTest.java25
-rw-r--r--core/tests/coretests/src/android/view/textclassifier/SelectionEventTest.java7
-rw-r--r--core/tests/coretests/src/com/android/internal/os/BatteryStatsBackgroundStatsTest.java47
-rw-r--r--location/java/com/android/internal/location/gnssmetrics/GnssMetrics.java4
-rw-r--r--packages/PrintSpooler/res/layout/select_printer_activity.xml2
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtils.java2
-rw-r--r--packages/SystemUI/Android.mk3
-rw-r--r--packages/SystemUI/AndroidManifest.xml3
-rw-r--r--packages/SystemUI/res/drawable/qs_header_status_dot.xml19
-rw-r--r--packages/SystemUI/res/layout/car_facet_button.xml1
-rw-r--r--packages/SystemUI/res/layout/car_left_navigation_bar_unprovisioned.xml62
-rw-r--r--packages/SystemUI/res/layout/car_navigation_bar_unprovisioned.xml61
-rw-r--r--packages/SystemUI/res/layout/car_right_navigation_bar_unprovisioned.xml62
-rw-r--r--packages/SystemUI/res/layout/car_status_bar_header.xml19
-rw-r--r--packages/SystemUI/res/layout/car_top_navigation_bar.xml38
-rw-r--r--packages/SystemUI/res/layout/quick_settings_header_info.xml26
-rw-r--r--packages/SystemUI/res/layout/status_bar_notification_footer.xml3
-rw-r--r--packages/SystemUI/res/layout/status_bar_notification_row.xml13
-rw-r--r--packages/SystemUI/res/values/attrs_car.xml14
-rw-r--r--packages/SystemUI/src/com/android/systemui/OverviewProxyService.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/car/CarSystemUIFactory.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java128
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/car/CarStatusBarHeader.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationInfo.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/car/CarFacetButton.java49
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/car/CarFacetButtonController.java44
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/car/CarNavigationBarView.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java20
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/car/hvac/HvacController.java205
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/car/hvac/TemperatureView.java82
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonRipple.java57
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java35
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java17
-rw-r--r--packages/SystemUI/tests/Android.mk2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInfoTest.java12
-rw-r--r--services/autofill/java/com/android/server/autofill/AutofillManagerService.java1
-rw-r--r--services/autofill/java/com/android/server/autofill/ui/FillUi.java22
-rw-r--r--services/core/java/com/android/server/fingerprint/FingerprintService.java6
-rw-r--r--services/core/java/com/android/server/net/NetworkPolicyManagerService.java7
-rw-r--r--services/core/java/com/android/server/net/watchlist/NetworkWatchlistService.java17
-rw-r--r--services/core/java/com/android/server/net/watchlist/NetworkWatchlistShellCommand.java46
-rw-r--r--services/core/java/com/android/server/net/watchlist/PrivacyUtils.java4
-rw-r--r--services/core/java/com/android/server/net/watchlist/WatchlistLoggingHandler.java106
-rw-r--r--services/core/java/com/android/server/net/watchlist/WatchlistReportDbHelper.java29
-rw-r--r--services/core/java/com/android/server/pm/UserRestrictionsUtils.java2
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java6
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java6
-rw-r--r--services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java34
-rw-r--r--telephony/java/android/telephony/MbmsDownloadSession.java4
-rw-r--r--telephony/java/android/telephony/TelephonyManager.java16
-rw-r--r--telephony/java/android/telephony/mbms/MbmsDownloadReceiver.java6
-rw-r--r--telephony/java/com/android/internal/telephony/RILConstants.java2
-rw-r--r--wifi/java/android/net/wifi/rtt/RangingResult.java14
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> &lt;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) {