Merge "Compare ringtones using IDs." into qt-dev
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index abcccf8..d69eced 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -299,6 +299,10 @@
CarPowerStateChanged car_power_state_changed = 203;
GarageModeInfo garage_mode_info = 204;
TestAtomReported test_atom_reported = 205 [(log_from_module) = "cts"];
+ ContentCaptureCallerMismatchReported content_capture_caller_mismatch_reported = 206;
+ ContentCaptureServiceEvents content_capture_service_events = 207;
+ ContentCaptureSessionEvents content_capture_session_events = 208;
+ ContentCaptureFlushed content_capture_flushed = 209;
}
// Pulled events will start at field 10000.
@@ -4831,6 +4835,95 @@
}
/**
+ * Logs information about mismatched caller for content capture.
+ *
+ * Logged from:
+ * frameworks/base/core/java/android/service/contentcapture/ContentCaptureService.java
+ */
+message ContentCaptureCallerMismatchReported {
+ optional string intended_package = 1;
+ optional string calling_package = 2;
+}
+
+/**
+ * Logs information about content capture service events.
+ *
+ * Logged from:
+ * frameworks/base/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureMetricsLogger.java
+ */
+message ContentCaptureServiceEvents {
+ // The type of event.
+ enum Event {
+ UNKNOWN = 0;
+ ON_CONNECTED = 1;
+ ON_DISCONNECTED = 2;
+ SET_WHITELIST = 3;
+ SET_DISABLED = 4;
+ ON_USER_DATA_REMOVED = 5;
+ }
+ optional Event event = 1;
+ // component/package of content capture service.
+ optional string service_info = 2;
+ // component/package of target.
+ // it's a concatenated list of component/package for SET_WHITELIST event
+ // separated by " ".
+ optional string target_info = 3;
+}
+
+/**
+ * Logs information about content capture session events.
+ *
+ * Logged from:
+ * frameworks/base/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureMetricsLogger.java
+ */
+message ContentCaptureSessionEvents {
+ // The type of event.
+ enum Event {
+ UNKNOWN = 0;
+ ON_SESSION_STARTED = 1;
+ ON_SESSION_FINISHED = 2;
+ SESSION_NOT_CREATED = 3;
+ }
+ optional int32 session_id = 1;
+ optional Event event = 2;
+ // (n/a on session finished)
+ optional int32 state_flags = 3;
+ // component/package of content capture service.
+ optional string service_info = 4;
+ // component/package of app.
+ // (n/a on session finished)
+ optional string app_info = 5;
+ optional bool is_child_session = 6;
+}
+
+/**
+ * Logs information about session being flushed.
+ *
+ * Logged from:
+ * frameworks/base/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureMetricsLogger.java
+ */
+message ContentCaptureFlushed {
+ optional int32 session_id = 1;
+ // component/package of content capture service.
+ optional string service_info = 2;
+ // component/package of app.
+ optional string app_info = 3;
+ // session start/finish events
+ optional int32 child_session_started = 4;
+ optional int32 child_session_finished = 5;
+ // count of view events.
+ optional int32 view_appeared_count = 6;
+ optional int32 view_disappeared_count = 7;
+ optional int32 view_text_changed_count = 8;
+
+ // Flush stats.
+ optional int32 max_events = 9;
+ optional int32 idle_flush_freq = 10;
+ optional int32 text_flush_freq = 11;
+ optional int32 flush_reason = 12;
+}
+
+/**
* Pulls on-device BatteryStats power use calculations for the overall device.
*/
message DeviceCalculatedPowerUse {
diff --git a/cmds/statsd/src/statsd_config.proto b/cmds/statsd/src/statsd_config.proto
index 4e419b6..a2fd9d4 100644
--- a/cmds/statsd/src/statsd_config.proto
+++ b/cmds/statsd/src/statsd_config.proto
@@ -330,6 +330,8 @@
// Class name of the incident report receiver.
optional string receiver_cls = 4;
+
+ optional string alert_description = 5;
}
message PerfettoDetails {
diff --git a/cmds/statsd/src/subscriber/IncidentdReporter.cpp b/cmds/statsd/src/subscriber/IncidentdReporter.cpp
index ff1cb4f..f2c6f1a 100644
--- a/cmds/statsd/src/subscriber/IncidentdReporter.cpp
+++ b/cmds/statsd/src/subscriber/IncidentdReporter.cpp
@@ -36,12 +36,14 @@
using android::util::ProtoOutputStream;
using std::vector;
-using util::FIELD_TYPE_MESSAGE;
using util::FIELD_TYPE_INT32;
using util::FIELD_TYPE_INT64;
+using util::FIELD_TYPE_MESSAGE;
+using util::FIELD_TYPE_STRING;
// field ids in IncidentHeaderProto
const int FIELD_ID_ALERT_ID = 1;
+const int FIELD_ID_REASON = 2;
const int FIELD_ID_CONFIG_KEY = 3;
const int FIELD_ID_CONFIG_KEY_UID = 1;
const int FIELD_ID_CONFIG_KEY_ID = 2;
@@ -57,9 +59,11 @@
namespace {
void getProtoData(const int64_t& rule_id, int64_t metricId, const MetricDimensionKey& dimensionKey,
- int64_t metricValue, const ConfigKey& configKey, vector<uint8_t>* protoData) {
+ int64_t metricValue, const ConfigKey& configKey, const string& reason,
+ vector<uint8_t>* protoData) {
ProtoOutputStream headerProto;
headerProto.write(FIELD_TYPE_INT64 | FIELD_ID_ALERT_ID, (long long)rule_id);
+ headerProto.write(FIELD_TYPE_STRING | FIELD_ID_REASON, reason);
uint64_t token =
headerProto.start(FIELD_TYPE_MESSAGE | FIELD_ID_CONFIG_KEY);
headerProto.write(FIELD_TYPE_INT32 | FIELD_ID_CONFIG_KEY_UID, configKey.GetUid());
@@ -142,7 +146,8 @@
IncidentReportArgs incidentReport;
vector<uint8_t> protoData;
- getProtoData(rule_id, metricId, dimensionKey, metricValue, configKey, &protoData);
+ getProtoData(rule_id, metricId, dimensionKey, metricValue, configKey,
+ config.alert_description(), &protoData);
incidentReport.addHeader(protoData);
for (int i = 0; i < config.section_size(); i++) {
diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java
index b0142ea..f662b61 100644
--- a/core/java/android/hardware/camera2/CameraManager.java
+++ b/core/java/android/hardware/camera2/CameraManager.java
@@ -98,7 +98,7 @@
* identifiers, while removable cameras have a unique identifier for each
* individual device, even if they are the same model.</p>
*
- * <p>This list doesn't contain physical cameras that can only used as part of a logical
+ * <p>This list doesn't contain physical cameras that can only be used as part of a logical
* multi-camera device.</p>
*
* @return The list of currently connected camera devices.
@@ -263,7 +263,7 @@
* immutable for a given camera.</p>
*
* <p>From API level 29, this function can also be used to query the capabilities of physical
- * cameras that can only be used as part of logical multi-camera. These cameras cannot not be
+ * cameras that can only be used as part of logical multi-camera. These cameras cannot be
* opened directly via {@link #openCamera}</p>
*
* @param cameraId The id of the camera device to query. This could be either a standalone
diff --git a/core/java/android/os/IncidentManager.java b/core/java/android/os/IncidentManager.java
index ed8d3f7..a94fd65 100644
--- a/core/java/android/os/IncidentManager.java
+++ b/core/java/android/os/IncidentManager.java
@@ -16,6 +16,7 @@
package android.os;
+import android.annotation.CallbackExecutor;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -34,6 +35,7 @@
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.List;
+import java.util.concurrent.Executor;
/**
* Class to take an incident report.
@@ -248,6 +250,24 @@
public @NonNull String toString() {
return "PendingReport(" + getUri().toString() + ")";
}
+
+ /**
+ * @inheritDoc
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof PendingReport)) {
+ return false;
+ }
+ final PendingReport that = (PendingReport) obj;
+ return this.mUri.equals(that.mUri)
+ && this.mFlags == that.mFlags
+ && this.mRequestingPackage.equals(that.mRequestingPackage)
+ && this.mTimestamp == that.mTimestamp;
+ }
}
/**
@@ -355,21 +375,35 @@
}
/**
- * Listener for the status of an incident report being authroized or denied.
+ * Listener for the status of an incident report being authorized or denied.
*
* @see #requestAuthorization
* @see #cancelAuthorization
*/
public static class AuthListener {
+ Executor mExecutor;
+
IIncidentAuthListener.Stub mBinder = new IIncidentAuthListener.Stub() {
@Override
public void onReportApproved() {
- AuthListener.this.onReportApproved();
+ if (mExecutor != null) {
+ mExecutor.execute(() -> {
+ AuthListener.this.onReportApproved();
+ });
+ } else {
+ AuthListener.this.onReportApproved();
+ }
}
@Override
public void onReportDenied() {
- AuthListener.this.onReportDenied();
+ if (mExecutor != null) {
+ mExecutor.execute(() -> {
+ AuthListener.this.onReportDenied();
+ });
+ } else {
+ AuthListener.this.onReportDenied();
+ }
}
};
@@ -410,7 +444,23 @@
@RequiresPermission(android.Manifest.permission.REQUEST_INCIDENT_REPORT_APPROVAL)
public void requestAuthorization(int callingUid, String callingPackage, int flags,
AuthListener listener) {
+ requestAuthorization(callingUid, callingPackage, flags,
+ mContext.getMainExecutor(), listener);
+ }
+
+ /**
+ * Request authorization of an incident report.
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.REQUEST_INCIDENT_REPORT_APPROVAL)
+ public void requestAuthorization(int callingUid, @NonNull String callingPackage, int flags,
+ @NonNull @CallbackExecutor Executor executor, @NonNull AuthListener listener) {
try {
+ if (listener.mExecutor != null) {
+ throw new RuntimeException("Do not reuse AuthListener objects when calling"
+ + " requestAuthorization");
+ }
+ listener.mExecutor = executor;
getCompanionServiceLocked().authorizeReport(callingUid, callingPackage, null, null,
flags, listener.mBinder);
} catch (RemoteException ex) {
diff --git a/core/java/android/service/contentcapture/ContentCaptureService.java b/core/java/android/service/contentcapture/ContentCaptureService.java
index 02ce873..08d9733 100644
--- a/core/java/android/service/contentcapture/ContentCaptureService.java
+++ b/core/java/android/service/contentcapture/ContentCaptureService.java
@@ -29,6 +29,7 @@
import android.annotation.TestApi;
import android.app.Service;
import android.content.ComponentName;
+import android.content.ContentCaptureOptions;
import android.content.Intent;
import android.content.pm.ParceledListSlice;
import android.os.Binder;
@@ -40,6 +41,7 @@
import android.util.Log;
import android.util.Slog;
import android.util.SparseIntArray;
+import android.util.StatsLog;
import android.view.contentcapture.ContentCaptureCondition;
import android.view.contentcapture.ContentCaptureContext;
import android.view.contentcapture.ContentCaptureEvent;
@@ -114,6 +116,9 @@
private Handler mHandler;
private IContentCaptureServiceCallback mCallback;
+ private long mCallerMismatchTimeout = 1000;
+ private long mLastCallerMismatchLog;
+
/**
* Binder that receives calls from the system server.
*/
@@ -176,9 +181,10 @@
new IContentCaptureDirectManager.Stub() {
@Override
- public void sendEvents(@SuppressWarnings("rawtypes") ParceledListSlice events) {
+ public void sendEvents(@SuppressWarnings("rawtypes") ParceledListSlice events, int reason,
+ ContentCaptureOptions options) {
mHandler.sendMessage(obtainMessage(ContentCaptureService::handleSendEvents,
- ContentCaptureService.this, Binder.getCallingUid(), events));
+ ContentCaptureService.this, Binder.getCallingUid(), events, reason, options));
}
};
@@ -424,14 +430,23 @@
}
private void handleSendEvents(int uid,
- @NonNull ParceledListSlice<ContentCaptureEvent> parceledEvents) {
+ @NonNull ParceledListSlice<ContentCaptureEvent> parceledEvents, int reason,
+ @Nullable ContentCaptureOptions options) {
+ final List<ContentCaptureEvent> events = parceledEvents.getList();
+ if (events.isEmpty()) {
+ Log.w(TAG, "handleSendEvents() received empty list of events");
+ return;
+ }
+
+ // Metrics.
+ final FlushMetrics metrics = new FlushMetrics();
+ ComponentName activityComponent = null;
// Most events belong to the same session, so we can keep a reference to the last one
// to avoid creating too many ContentCaptureSessionId objects
int lastSessionId = NO_SESSION_ID;
ContentCaptureSessionId sessionId = null;
- final List<ContentCaptureEvent> events = parceledEvents.getList();
for (int i = 0; i < events.size(); i++) {
final ContentCaptureEvent event = events.get(i);
if (!handleIsRightCallerFor(event, uid)) continue;
@@ -439,22 +454,44 @@
if (sessionIdInt != lastSessionId) {
sessionId = new ContentCaptureSessionId(sessionIdInt);
lastSessionId = sessionIdInt;
+ if (i != 0) {
+ writeFlushMetrics(lastSessionId, activityComponent, metrics, options, reason);
+ metrics.reset();
+ }
+ }
+ final ContentCaptureContext clientContext = event.getContentCaptureContext();
+ if (activityComponent == null && clientContext != null) {
+ activityComponent = clientContext.getActivityComponent();
}
switch (event.getType()) {
case ContentCaptureEvent.TYPE_SESSION_STARTED:
- final ContentCaptureContext clientContext = event.getContentCaptureContext();
clientContext.setParentSessionId(event.getParentSessionId());
mSessionUids.put(sessionIdInt, uid);
onCreateContentCaptureSession(clientContext, sessionId);
+ metrics.sessionStarted++;
break;
case ContentCaptureEvent.TYPE_SESSION_FINISHED:
mSessionUids.delete(sessionIdInt);
onDestroyContentCaptureSession(sessionId);
+ metrics.sessionFinished++;
+ break;
+ case ContentCaptureEvent.TYPE_VIEW_APPEARED:
+ onContentCaptureEvent(sessionId, event);
+ metrics.viewAppearedCount++;
+ break;
+ case ContentCaptureEvent.TYPE_VIEW_DISAPPEARED:
+ onContentCaptureEvent(sessionId, event);
+ metrics.viewDisappearedCount++;
+ break;
+ case ContentCaptureEvent.TYPE_VIEW_TEXT_CHANGED:
+ onContentCaptureEvent(sessionId, event);
+ metrics.viewTextChangedCount++;
break;
default:
onContentCaptureEvent(sessionId, event);
}
}
+ writeFlushMetrics(lastSessionId, activityComponent, metrics, options, reason);
}
private void handleOnActivitySnapshot(int sessionId, @NonNull SnapshotData snapshotData) {
@@ -499,7 +536,13 @@
if (rightUid != uid) {
Log.e(TAG, "invalid call from UID " + uid + ": session " + sessionId + " belongs to "
+ rightUid);
- //TODO(b/111276913): log metrics as this could be a malicious app forging a sessionId
+ long now = System.currentTimeMillis();
+ if (now - mLastCallerMismatchLog > mCallerMismatchTimeout) {
+ StatsLog.write(StatsLog.CONTENT_CAPTURE_CALLER_MISMATCH_REPORTED,
+ getPackageManager().getNameForUid(rightUid),
+ getPackageManager().getNameForUid(uid));
+ mLastCallerMismatchLog = now;
+ }
return false;
}
return true;
@@ -530,4 +573,22 @@
Slog.w(TAG, "Error async reporting result to client: " + e);
}
}
+
+ /**
+ * Logs the metrics for content capture events flushing.
+ */
+ private void writeFlushMetrics(int sessionId, @Nullable ComponentName app,
+ @NonNull FlushMetrics flushMetrics, @Nullable ContentCaptureOptions options,
+ int flushReason) {
+ if (mCallback == null) {
+ Log.w(TAG, "writeSessionFlush(): no server callback");
+ return;
+ }
+
+ try {
+ mCallback.writeSessionFlush(sessionId, app, flushMetrics, options, flushReason);
+ } catch (RemoteException e) {
+ Log.e(TAG, "failed to write flush metrics: " + e);
+ }
+ }
}
diff --git a/core/java/android/service/contentcapture/FlushMetrics.aidl b/core/java/android/service/contentcapture/FlushMetrics.aidl
new file mode 100644
index 0000000..d0b935f
--- /dev/null
+++ b/core/java/android/service/contentcapture/FlushMetrics.aidl
@@ -0,0 +1,20 @@
+/**
+ * Copyright (c) 2019, 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.service.contentcapture;
+
+/* @hide */
+parcelable FlushMetrics;
diff --git a/core/java/android/service/contentcapture/FlushMetrics.java b/core/java/android/service/contentcapture/FlushMetrics.java
new file mode 100644
index 0000000..01f3a12
--- /dev/null
+++ b/core/java/android/service/contentcapture/FlushMetrics.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2019 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.service.contentcapture;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Holds metrics for content capture events flushing.
+ *
+ * @hide
+ */
+public final class FlushMetrics implements Parcelable {
+ public int viewAppearedCount;
+ public int viewDisappearedCount;
+ public int viewTextChangedCount;
+ public int sessionStarted;
+ public int sessionFinished;
+
+ /**
+ * Resets all flush metrics.
+ */
+ public void reset() {
+ viewAppearedCount = 0;
+ viewDisappearedCount = 0;
+ viewTextChangedCount = 0;
+ sessionStarted = 0;
+ sessionFinished = 0;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeInt(sessionStarted);
+ out.writeInt(sessionFinished);
+ out.writeInt(viewAppearedCount);
+ out.writeInt(viewDisappearedCount);
+ out.writeInt(viewTextChangedCount);
+ }
+
+ @NonNull
+ public static final Creator<FlushMetrics> CREATOR = new Creator<FlushMetrics>() {
+ @NonNull
+ @Override
+ public FlushMetrics createFromParcel(Parcel in) {
+ final FlushMetrics flushMetrics = new FlushMetrics();
+ flushMetrics.sessionStarted = in.readInt();
+ flushMetrics.sessionFinished = in.readInt();
+ flushMetrics.viewAppearedCount = in.readInt();
+ flushMetrics.viewDisappearedCount = in.readInt();
+ flushMetrics.viewTextChangedCount = in.readInt();
+ return flushMetrics;
+ }
+
+ @Override
+ public FlushMetrics[] newArray(int size) {
+ return new FlushMetrics[size];
+ }
+ };
+}
diff --git a/core/java/android/service/contentcapture/IContentCaptureServiceCallback.aidl b/core/java/android/service/contentcapture/IContentCaptureServiceCallback.aidl
index 0550ad3..ea6e76b 100644
--- a/core/java/android/service/contentcapture/IContentCaptureServiceCallback.aidl
+++ b/core/java/android/service/contentcapture/IContentCaptureServiceCallback.aidl
@@ -18,6 +18,8 @@
import android.content.ComponentName;
import android.view.contentcapture.ContentCaptureCondition;
+import android.service.contentcapture.FlushMetrics;
+import android.content.ContentCaptureOptions;
import java.util.List;
@@ -30,4 +32,8 @@
void setContentCaptureWhitelist(in List<String> packages, in List<ComponentName> activities);
void setContentCaptureConditions(String packageName, in List<ContentCaptureCondition> conditions);
void disableSelf();
- }
+
+ // Logs aggregated content capture flush metrics to Statsd
+ void writeSessionFlush(int sessionId, in ComponentName app, in FlushMetrics flushMetrics,
+ in ContentCaptureOptions options, int flushReason);
+}
diff --git a/core/java/android/view/contentcapture/IContentCaptureDirectManager.aidl b/core/java/android/view/contentcapture/IContentCaptureDirectManager.aidl
index 8d8117b..959bf13 100644
--- a/core/java/android/view/contentcapture/IContentCaptureDirectManager.aidl
+++ b/core/java/android/view/contentcapture/IContentCaptureDirectManager.aidl
@@ -18,6 +18,7 @@
import android.content.pm.ParceledListSlice;
import android.view.contentcapture.ContentCaptureEvent;
+import android.content.ContentCaptureOptions;
/**
* Interface between an app (ContentCaptureManager / ContentCaptureSession) and the app providing
@@ -26,5 +27,6 @@
* @hide
*/
oneway interface IContentCaptureDirectManager {
- void sendEvents(in ParceledListSlice events);
+ // reason and options are used only for metrics logging.
+ void sendEvents(in ParceledListSlice events, int reason, in ContentCaptureOptions options);
}
diff --git a/core/java/android/view/contentcapture/MainContentCaptureSession.java b/core/java/android/view/contentcapture/MainContentCaptureSession.java
index 7241664..c5a5f73 100644
--- a/core/java/android/view/contentcapture/MainContentCaptureSession.java
+++ b/core/java/android/view/contentcapture/MainContentCaptureSession.java
@@ -498,7 +498,7 @@
}
final ParceledListSlice<ContentCaptureEvent> events = clearEvents();
- mDirectServiceInterface.sendEvents(events);
+ mDirectServiceInterface.sendEvents(events, reason, mManager.mOptions);
} catch (RemoteException e) {
Log.w(TAG, "Error sending " + numberEvents + " for " + getDebugState()
+ ": " + e);
diff --git a/core/java/com/android/internal/os/ZygoteConfig.java b/core/java/com/android/internal/os/ZygoteConfig.java
index c6af8c2..6ebcae1 100644
--- a/core/java/com/android/internal/os/ZygoteConfig.java
+++ b/core/java/com/android/internal/os/ZygoteConfig.java
@@ -34,4 +34,7 @@
/** The minimum number of processes to keep in the USAP pool */
public static final String USAP_POOL_SIZE_MIN = "usap_pool_size_min";
+
+ /** The number of milliseconds to delay before refilling the USAP pool */
+ public static final String USAP_POOL_REFILL_DELAY_MS = "usap_pool_refill_delay_ms";
}
diff --git a/core/java/com/android/internal/os/ZygoteServer.java b/core/java/com/android/internal/os/ZygoteServer.java
index 7eed9b1..492bc94 100644
--- a/core/java/com/android/internal/os/ZygoteServer.java
+++ b/core/java/com/android/internal/os/ZygoteServer.java
@@ -66,11 +66,8 @@
/** The default value used for the USAP_POOL_SIZE_MIN device property */
private static final String USAP_POOL_SIZE_MIN_DEFAULT = "1";
- /**
- * Number of milliseconds to delay before refilling the pool if it hasn't reached its
- * minimum value.
- */
- private static final int USAP_REFILL_DELAY_MS = 3000;
+ /** The default value used for the USAP_REFILL_DELAY_MS device property */
+ private static final String USAP_POOL_REFILL_DELAY_MS_DEFAULT = "3000";
/** The "not a timestamp" value for the refill delay timestamp mechanism. */
private static final int INVALID_TIMESTAMP = -1;
@@ -140,6 +137,12 @@
*/
private int mUsapPoolRefillThreshold = 0;
+ /**
+ * Number of milliseconds to delay before refilling the pool if it hasn't reached its
+ * minimum value.
+ */
+ private int mUsapPoolRefillDelayMs = -1;
+
private enum UsapPoolRefillAction {
DELAYED,
IMMEDIATE,
@@ -282,6 +285,13 @@
mUsapPoolSizeMax);
}
+ final String usapPoolRefillDelayMsPropString = Zygote.getConfigurationProperty(
+ ZygoteConfig.USAP_POOL_REFILL_DELAY_MS, USAP_POOL_REFILL_DELAY_MS_DEFAULT);
+
+ if (!usapPoolRefillDelayMsPropString.isEmpty()) {
+ mUsapPoolRefillDelayMs = Integer.parseInt(usapPoolRefillDelayMsPropString);
+ }
+
// Sanity check
if (mUsapPoolSizeMin >= mUsapPoolSizeMax) {
Log.w(TAG, "The max size of the USAP pool must be greater than the minimum size."
@@ -467,13 +477,13 @@
int elapsedTimeMs =
(int) (System.currentTimeMillis() - usapPoolRefillTriggerTimestamp);
- if (elapsedTimeMs >= USAP_REFILL_DELAY_MS) {
+ if (elapsedTimeMs >= mUsapPoolRefillDelayMs) {
// Normalize the poll timeout value when the time between one poll event and the
// next pushes us over the delay value. This prevents poll receiving a 0
// timeout value, which would result in it returning immediately.
pollTimeoutMs = -1;
} else {
- pollTimeoutMs = USAP_REFILL_DELAY_MS - elapsedTimeMs;
+ pollTimeoutMs = mUsapPoolRefillDelayMs - elapsedTimeMs;
}
}
diff --git a/libs/protoutil/tests/EncodedBuffer_test.cpp b/libs/protoutil/tests/EncodedBuffer_test.cpp
index 398af60..f895154 100644
--- a/libs/protoutil/tests/EncodedBuffer_test.cpp
+++ b/libs/protoutil/tests/EncodedBuffer_test.cpp
@@ -29,101 +29,101 @@
}
TEST(EncodedBufferTest, WriteSimple) {
- EncodedBuffer buffer(TEST_CHUNK_SIZE);
- EXPECT_EQ(buffer.size(), 0UL);
- expectPointer(buffer.wp(), 0);
- EXPECT_EQ(buffer.currentToWrite(), TEST_CHUNK_SIZE);
+ sp<EncodedBuffer> buffer = new EncodedBuffer(TEST_CHUNK_SIZE);
+ EXPECT_EQ(buffer->size(), 0UL);
+ expectPointer(buffer->wp(), 0);
+ EXPECT_EQ(buffer->currentToWrite(), TEST_CHUNK_SIZE);
for (size_t i = 0; i < TEST_CHUNK_HALF_SIZE; i++) {
- buffer.writeRawByte(50 + i);
+ buffer->writeRawByte(50 + i);
}
- EXPECT_EQ(buffer.size(), TEST_CHUNK_HALF_SIZE);
- expectPointer(buffer.wp(), TEST_CHUNK_HALF_SIZE);
- EXPECT_EQ(buffer.currentToWrite(), TEST_CHUNK_HALF_SIZE);
+ EXPECT_EQ(buffer->size(), TEST_CHUNK_HALF_SIZE);
+ expectPointer(buffer->wp(), TEST_CHUNK_HALF_SIZE);
+ EXPECT_EQ(buffer->currentToWrite(), TEST_CHUNK_HALF_SIZE);
for (size_t i = 0; i < TEST_CHUNK_SIZE; i++) {
- buffer.writeRawByte(80 + i);
+ buffer->writeRawByte(80 + i);
}
- EXPECT_EQ(buffer.size(), TEST_CHUNK_SIZE + TEST_CHUNK_HALF_SIZE);
- expectPointer(buffer.wp(), TEST_CHUNK_SIZE + TEST_CHUNK_HALF_SIZE);
- EXPECT_EQ(buffer.currentToWrite(), TEST_CHUNK_HALF_SIZE);
+ EXPECT_EQ(buffer->size(), TEST_CHUNK_SIZE + TEST_CHUNK_HALF_SIZE);
+ expectPointer(buffer->wp(), TEST_CHUNK_SIZE + TEST_CHUNK_HALF_SIZE);
+ EXPECT_EQ(buffer->currentToWrite(), TEST_CHUNK_HALF_SIZE);
// verifies the buffer's data
- expectPointer(buffer.ep(), 0);
+ expectPointer(buffer->ep(), 0);
for (size_t i = 0; i < TEST_CHUNK_HALF_SIZE; i++) {
- EXPECT_EQ(buffer.readRawByte(), 50 + i);
+ EXPECT_EQ(buffer->readRawByte(), 50 + i);
}
for (size_t i = 0; i < TEST_CHUNK_SIZE; i++) {
- EXPECT_EQ(buffer.readRawByte(), 80 + i);
+ EXPECT_EQ(buffer->readRawByte(), 80 + i);
}
// clears the buffer
- buffer.clear();
- EXPECT_EQ(buffer.size(), 0UL);
- expectPointer(buffer.wp(), 0);
+ buffer->clear();
+ EXPECT_EQ(buffer->size(), 0UL);
+ expectPointer(buffer->wp(), 0);
}
TEST(EncodedBufferTest, WriteVarint) {
- EncodedBuffer buffer(TEST_CHUNK_SIZE);
+ sp<EncodedBuffer> buffer = new EncodedBuffer(TEST_CHUNK_SIZE);
size_t expected_buffer_size = 0;
- EXPECT_EQ(buffer.writeRawVarint32(13), 1);
+ EXPECT_EQ(buffer->writeRawVarint32(13), 1);
expected_buffer_size += 1;
- EXPECT_EQ(buffer.size(), expected_buffer_size);
- EXPECT_EQ(buffer.writeRawVarint32(UINT32_C(-1)), 5);
+ EXPECT_EQ(buffer->size(), expected_buffer_size);
+ EXPECT_EQ(buffer->writeRawVarint32(UINT32_C(-1)), 5);
expected_buffer_size += 5;
- EXPECT_EQ(buffer.size(), expected_buffer_size);
+ EXPECT_EQ(buffer->size(), expected_buffer_size);
- EXPECT_EQ(buffer.writeRawVarint64(200), 2);
+ EXPECT_EQ(buffer->writeRawVarint64(200), 2);
expected_buffer_size += 2;
- EXPECT_EQ(buffer.size(), expected_buffer_size);
- EXPECT_EQ(buffer.writeRawVarint64(UINT64_C(-1)), 10);
+ EXPECT_EQ(buffer->size(), expected_buffer_size);
+ EXPECT_EQ(buffer->writeRawVarint64(UINT64_C(-1)), 10);
expected_buffer_size += 10;
- EXPECT_EQ(buffer.size(), expected_buffer_size);
+ EXPECT_EQ(buffer->size(), expected_buffer_size);
- buffer.writeRawFixed32(UINT32_C(-1));
+ buffer->writeRawFixed32(UINT32_C(-1));
expected_buffer_size += 4;
- EXPECT_EQ(buffer.size(), expected_buffer_size);
- buffer.writeRawFixed64(UINT64_C(-1));
+ EXPECT_EQ(buffer->size(), expected_buffer_size);
+ buffer->writeRawFixed64(UINT64_C(-1));
expected_buffer_size += 8;
- EXPECT_EQ(buffer.size(), expected_buffer_size);
+ EXPECT_EQ(buffer->size(), expected_buffer_size);
- EXPECT_EQ(buffer.writeHeader(32, 2), 2);
+ EXPECT_EQ(buffer->writeHeader(32, 2), 2);
expected_buffer_size += 2;
- EXPECT_EQ(buffer.size(), expected_buffer_size);
+ EXPECT_EQ(buffer->size(), expected_buffer_size);
// verify data are correctly written to the buffer.
- expectPointer(buffer.ep(), 0);
- EXPECT_EQ(buffer.readRawVarint(), UINT32_C(13));
- EXPECT_EQ(buffer.readRawVarint(), UINT32_C(-1));
- EXPECT_EQ(buffer.readRawVarint(), UINT64_C(200));
- EXPECT_EQ(buffer.readRawVarint(), UINT64_C(-1));
- EXPECT_EQ(buffer.readRawFixed32(), UINT32_C(-1));
- EXPECT_EQ(buffer.readRawFixed64(), UINT64_C(-1));
- EXPECT_EQ(buffer.readRawVarint(), UINT64_C((32 << 3) + 2));
- expectPointer(buffer.ep(), expected_buffer_size);
+ expectPointer(buffer->ep(), 0);
+ EXPECT_EQ(buffer->readRawVarint(), UINT32_C(13));
+ EXPECT_EQ(buffer->readRawVarint(), UINT32_C(-1));
+ EXPECT_EQ(buffer->readRawVarint(), UINT64_C(200));
+ EXPECT_EQ(buffer->readRawVarint(), UINT64_C(-1));
+ EXPECT_EQ(buffer->readRawFixed32(), UINT32_C(-1));
+ EXPECT_EQ(buffer->readRawFixed64(), UINT64_C(-1));
+ EXPECT_EQ(buffer->readRawVarint(), UINT64_C((32 << 3) + 2));
+ expectPointer(buffer->ep(), expected_buffer_size);
}
TEST(EncodedBufferTest, Edit) {
- EncodedBuffer buffer(TEST_CHUNK_SIZE);
- buffer.writeRawFixed64(0xdeadbeefdeadbeef);
- EXPECT_EQ(buffer.readRawFixed64(), UINT64_C(0xdeadbeefdeadbeef));
+ sp<EncodedBuffer> buffer = new EncodedBuffer(TEST_CHUNK_SIZE);
+ buffer->writeRawFixed64(0xdeadbeefdeadbeef);
+ EXPECT_EQ(buffer->readRawFixed64(), UINT64_C(0xdeadbeefdeadbeef));
- buffer.editRawFixed32(4, 0x12345678);
+ buffer->editRawFixed32(4, 0x12345678);
// fixed 64 is little endian order.
- buffer.ep()->rewind(); // rewind ep for readRawFixed64 from 0
- EXPECT_EQ(buffer.readRawFixed64(), UINT64_C(0x12345678deadbeef));
+ buffer->ep()->rewind(); // rewind ep for readRawFixed64 from 0
+ EXPECT_EQ(buffer->readRawFixed64(), UINT64_C(0x12345678deadbeef));
- buffer.wp()->rewind();
- expectPointer(buffer.wp(), 0);
- buffer.copy(4, 3);
- buffer.ep()->rewind(); // rewind ep for readRawFixed64 from 0
- EXPECT_EQ(buffer.readRawFixed64(), UINT64_C(0x12345678de345678));
+ buffer->wp()->rewind();
+ expectPointer(buffer->wp(), 0);
+ buffer->copy(4, 3);
+ buffer->ep()->rewind(); // rewind ep for readRawFixed64 from 0
+ EXPECT_EQ(buffer->readRawFixed64(), UINT64_C(0x12345678de345678));
}
TEST(EncodedBufferTest, ReadSimple) {
- EncodedBuffer buffer(TEST_CHUNK_SIZE);
+ sp<EncodedBuffer> buffer = new EncodedBuffer(TEST_CHUNK_SIZE);
for (size_t i = 0; i < TEST_CHUNK_3X_SIZE; i++) {
- buffer.writeRawByte(i);
+ buffer->writeRawByte(i);
}
- sp<ProtoReader> reader1 = buffer.read();
+ sp<ProtoReader> reader1 = buffer->read();
EXPECT_EQ(reader1->size(), TEST_CHUNK_3X_SIZE);
EXPECT_EQ(reader1->bytesRead(), 0);
@@ -132,7 +132,7 @@
}
EXPECT_EQ(reader1->bytesRead(), TEST_CHUNK_3X_SIZE);
- sp<ProtoReader> reader2 = buffer.read();
+ sp<ProtoReader> reader2 = buffer->read();
uint8_t val = 0;
while (reader2->hasNext()) {
EXPECT_EQ(reader2->next(), val);
@@ -143,10 +143,10 @@
}
TEST(EncodedBufferTest, ReadVarint) {
- EncodedBuffer buffer;
+ sp<EncodedBuffer> buffer = new EncodedBuffer();
uint64_t val = UINT64_C(1522865904593);
- size_t len = buffer.writeRawVarint64(val);
- sp<ProtoReader> reader = buffer.read();
+ size_t len = buffer->writeRawVarint64(val);
+ sp<ProtoReader> reader = buffer->read();
EXPECT_EQ(reader->size(), len);
EXPECT_EQ(reader->readRawVarint(), val);
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
index 1d351a5..9a95288 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
@@ -826,11 +826,22 @@
}
/**
- * @return resource for string that discribes the connection state of this device.
- * case 1: idle or playing media, show "Active" on the only one A2DP active device.
- * case 2: in phone call, show "Active" on the only one HFP active device
+ * Return full summary that describes connection state of this device
+ *
+ * @see #getConnectionSummary(boolean shortSummary)
*/
public String getConnectionSummary() {
+ return getConnectionSummary(false /* shortSummary */);
+ }
+
+ /**
+ * Return summary that describes connection state of this device. Summary depends on:
+ * 1. Whether device has battery info
+ * 2. Whether device is in active usage(or in phone call)
+ *
+ * @param shortSummary {@code true} if need to return short version summary
+ */
+ public String getConnectionSummary(boolean shortSummary) {
boolean profileConnected = false; // Updated as long as BluetoothProfile is connected
boolean a2dpConnected = true; // A2DP is connected
boolean hfpConnected = true; // HFP is connected
@@ -909,9 +920,9 @@
if ((mIsActiveDeviceHearingAid)
|| (mIsActiveDeviceHeadset && isOnCall)
|| (mIsActiveDeviceA2dp && !isOnCall)) {
- if (isTwsBatteryAvailable(leftBattery, rightBattery)) {
+ if (isTwsBatteryAvailable(leftBattery, rightBattery) && !shortSummary) {
stringRes = R.string.bluetooth_active_battery_level_untethered;
- } else if (batteryLevelPercentageString != null) {
+ } else if (batteryLevelPercentageString != null && !shortSummary) {
stringRes = R.string.bluetooth_active_battery_level;
} else {
stringRes = R.string.bluetooth_active_no_battery_level;
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java
index c0a1f11..93dcbfe 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java
@@ -192,6 +192,27 @@
}
@Test
+ public void getConnectionSummary_shortSummary_returnShortSummary() {
+ // Test without battery level
+ // Set A2DP profile to be connected and test connection state summary
+ updateProfileStatus(mA2dpProfile, BluetoothProfile.STATE_CONNECTED);
+ assertThat(mCachedDevice.getConnectionSummary(true /* shortSummary */)).isNull();
+
+ // Set device as Active for A2DP and test connection state summary
+ mCachedDevice.onActiveDeviceChanged(true, BluetoothProfile.A2DP);
+ assertThat(mCachedDevice.getConnectionSummary(true /* shortSummary */)).isEqualTo("Active");
+
+ // Test with battery level
+ mBatteryLevel = 10;
+ assertThat(mCachedDevice.getConnectionSummary(true /* shortSummary */)).isEqualTo(
+ "Active");
+
+ // Set A2DP profile to be disconnected and test connection state summary
+ updateProfileStatus(mA2dpProfile, BluetoothProfile.STATE_DISCONNECTED);
+ assertThat(mCachedDevice.getConnectionSummary()).isNull();
+ }
+
+ @Test
public void getConnectionSummary_testA2dpBatteryInactive_returnBattery() {
// Arrange:
// 1. Profile: {A2DP, CONNECTED, Inactive}
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index aecae63..e00e5f2 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -41,10 +41,10 @@
<!-- Size of the nav bar edge panels, should be greater to the
edge sensitivity + the drag threshold -->
- <dimen name="navigation_edge_panel_width">76dp</dimen>
+ <dimen name="navigation_edge_panel_width">70dp</dimen>
<!-- Padding at the end of the navigation panel to allow the arrow not to be clipped off -->
- <dimen name="navigation_edge_panel_padding">24dp</dimen>
- <dimen name="navigation_edge_panel_height">84dp</dimen>
+ <dimen name="navigation_edge_panel_padding">8dp</dimen>
+ <dimen name="navigation_edge_panel_height">96dp</dimen>
<!-- The threshold to drag to trigger the edge action -->
<dimen name="navigation_edge_action_drag_threshold">16dp</dimen>
<!-- The minimum display position of the arrow on the screen -->
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarEdgePanel.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarEdgePanel.java
index 4603ba6..4f223c3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarEdgePanel.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarEdgePanel.java
@@ -25,6 +25,7 @@
import android.graphics.Rect;
import android.os.SystemClock;
import android.os.VibrationEffect;
+import android.util.DisplayMetrics;
import android.util.MathUtils;
import android.view.ContextThemeWrapper;
import android.view.MotionEvent;
@@ -47,14 +48,14 @@
public class NavigationBarEdgePanel extends View {
- private static final long COLOR_ANIMATION_DURATION_MS = 100;
- private static final long DISAPPEAR_FADE_ANIMATION_DURATION_MS = 140;
+ private static final long COLOR_ANIMATION_DURATION_MS = 120;
+ private static final long DISAPPEAR_FADE_ANIMATION_DURATION_MS = 80;
private static final long DISAPPEAR_ARROW_ANIMATION_DURATION_MS = 100;
/**
- * The minimum time required since the first vibration effect to receive a second one
+ * The time required since the first vibration effect to automatically trigger a click
*/
- private static final int MIN_TIME_BETWEEN_EFFECTS_MS = 120;
+ private static final int GESTURE_DURATION_FOR_CLICK_MS = 400;
/**
* The size of the protection of the arrow in px. Only used if this is not background protected
@@ -79,7 +80,7 @@
/**
* The angle that is added per 1000 px speed to the angle of the leg
*/
- private static final int ARROW_ANGLE_ADDED_PER_1000_SPEED = 8;
+ private static final int ARROW_ANGLE_ADDED_PER_1000_SPEED = 4;
/**
* The maximum angle offset allowed due to speed
@@ -92,15 +93,15 @@
private static final float ARROW_THICKNESS_DP = 2.5f;
/**
- * The amount of rubber banding we do for the horizontal translation beyond the base translation
+ * The amount of rubber banding we do for the vertical translation
*/
- private static final int RUBBER_BAND_AMOUNT = 10;
+ private static final int RUBBER_BAND_AMOUNT = 15;
/**
* The interpolator used to rubberband
*/
private static final Interpolator RUBBER_BAND_INTERPOLATOR
- = new PathInterpolator(1.0f / RUBBER_BAND_AMOUNT, 1.0f, 1.0f, 1.0f);
+ = new PathInterpolator(1.0f / 5.0f, 1.0f, 1.0f, 1.0f);
/**
* The amount of rubber banding we do for the translation before base translation
@@ -189,6 +190,7 @@
private int mCurrentArrowColor;
private float mDisappearAmount;
private long mVibrationTime;
+ private int mScreenSize;
private DynamicAnimation.OnAnimationEndListener mSetGoneEndListener
= new DynamicAnimation.OnAnimationEndListener() {
@@ -281,9 +283,8 @@
mAngleAnimation =
new SpringAnimation(this, CURRENT_ANGLE);
mAngleAppearForce = new SpringForce()
- .setStiffness(SpringForce.STIFFNESS_LOW)
- .setDampingRatio(0.4f)
- .setFinalPosition(ARROW_ANGLE_WHEN_EXTENDED_DEGREES);
+ .setStiffness(500)
+ .setDampingRatio(0.5f);
mAngleDisappearForce = new SpringForce()
.setStiffness(SpringForce.STIFFNESS_MEDIUM)
.setDampingRatio(SpringForce.DAMPING_RATIO_MEDIUM_BOUNCY)
@@ -447,13 +448,14 @@
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
- // TODO: read the gesture length from the nav controller.
mMaxTranslation = getWidth() - mArrowPaddingEnd;
}
private void loadDimens() {
mArrowPaddingEnd = getContext().getResources().getDimensionPixelSize(
R.dimen.navigation_edge_panel_padding);
+ DisplayMetrics metrics = getResources().getDisplayMetrics();
+ mScreenSize = Math.min(metrics.widthPixels, metrics.heightPixels);
}
private void updateArrowDirection() {
@@ -510,7 +512,7 @@
if (!mArrowsPointLeft) {
x = -x;
}
- float extent = 1.0f - mDisappearAmount;
+ float extent = MathUtils.lerp(1.0f, 0.75f, mDisappearAmount);
x = x * extent;
y = y * extent;
mArrowPath.reset();
@@ -529,27 +531,29 @@
}
private void triggerBack() {
- if (SystemClock.uptimeMillis() - mVibrationTime >= MIN_TIME_BETWEEN_EFFECTS_MS) {
- mVibratorHelper.vibrate(VibrationEffect.EFFECT_CLICK);
- }
mVelocityTracker.computeCurrentVelocity(1000);
// Only do the extra translation if we're not already flinging
- boolean doExtraTranslation = Math.abs(mVelocityTracker.getXVelocity()) < 1000;
- if (doExtraTranslation) {
- setDesiredTranslation(mDesiredTranslation + dp(16), true /* animate */);
+ boolean isSlow = Math.abs(mVelocityTracker.getXVelocity()) < 500;
+ if (isSlow
+ || SystemClock.uptimeMillis() - mVibrationTime >= GESTURE_DURATION_FOR_CLICK_MS) {
+ mVibratorHelper.vibrate(VibrationEffect.EFFECT_CLICK);
}
// Let's also snap the angle a bit
- if (mAngleOffset < -4) {
- mAngleOffset = Math.max(-16, mAngleOffset - 16);
+ if (mAngleOffset > -4) {
+ mAngleOffset = Math.max(-8, mAngleOffset - 8);
updateAngle(true /* animated */);
}
// Finally, after the translation, animate back and disappear the arrow
Runnable translationEnd = () -> {
- setTriggerBack(false /* false */, true /* animate */);
+ // let's snap it back
+ mAngleOffset = Math.max(0, mAngleOffset + 8);
+ updateAngle(true /* animated */);
+
mTranslationAnimation.setSpring(mTriggerBackSpring);
- setDesiredTranslation(0, true /* animated */);
+ // Translate the arrow back a bit to make for a nice transition
+ setDesiredTranslation(mDesiredTranslation - dp(32), true /* animated */);
animate().alpha(0f).setDuration(DISAPPEAR_FADE_ANIMATION_DURATION_MS)
.withEndAction(() -> setVisibility(GONE));
mArrowDisappearAnimation.start();
@@ -584,6 +588,7 @@
setTriggerBack(false /* triggerBack */, false /* animated */);
setDesiredTranslation(0, false /* animated */);
setCurrentTranslation(0);
+ updateAngle(false /* animate */);
mPreviousTouchTranslation = 0;
mTotalTouchDelta = 0;
mVibrationTime = 0;
@@ -621,7 +626,7 @@
// Let's make sure we only go to the baseextend and apply rubberbanding afterwards
if (touchTranslation > mBaseTranslation) {
float diff = touchTranslation - mBaseTranslation;
- float progress = MathUtils.saturate(diff / (mBaseTranslation * RUBBER_BAND_AMOUNT));
+ float progress = MathUtils.saturate(diff / (mScreenSize - mBaseTranslation));
progress = RUBBER_BAND_INTERPOLATOR.getInterpolation(progress)
* (mMaxTranslation - mBaseTranslation);
touchTranslation = mBaseTranslation + progress;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/RegionSamplingHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/RegionSamplingHelper.java
index d59319e..2b0bb21 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/RegionSamplingHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/RegionSamplingHelper.java
@@ -27,6 +27,7 @@
import android.view.CompositionSamplingListener;
import android.view.SurfaceControl;
import android.view.View;
+import android.view.ViewRootImpl;
import android.view.ViewTreeObserver;
import com.android.systemui.R;
@@ -153,8 +154,12 @@
boolean isSamplingEnabled = mSamplingEnabled && !mSamplingRequestBounds.isEmpty()
&& (mSampledView.isAttachedToWindow() || mFirstSamplingAfterStart);
if (isSamplingEnabled) {
- SurfaceControl stopLayerControl = mSampledView.getViewRootImpl().getSurfaceControl();
- if (!stopLayerControl.isValid()) {
+ ViewRootImpl viewRootImpl = mSampledView.getViewRootImpl();
+ SurfaceControl stopLayerControl = null;
+ if (viewRootImpl != null) {
+ stopLayerControl = viewRootImpl.getSurfaceControl();
+ }
+ if (stopLayerControl == null || !stopLayerControl.isValid()) {
if (!mWaitingOnDraw) {
mWaitingOnDraw = true;
// The view might be attached but we haven't drawn yet, so wait until the
diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureMetricsLogger.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureMetricsLogger.java
new file mode 100644
index 0000000..dd1b84b
--- /dev/null
+++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureMetricsLogger.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2019 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.server.contentcapture;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.content.ContentCaptureOptions;
+import android.service.contentcapture.FlushMetrics;
+import android.util.StatsLog;
+
+import java.util.List;
+
+/** @hide */
+public final class ContentCaptureMetricsLogger {
+ /**
+ * Class only contains static utility functions, and should not be instantiated
+ */
+ private ContentCaptureMetricsLogger() {
+ }
+
+ /** @hide */
+ public static void writeServiceEvent(int eventType, @NonNull String serviceName,
+ @Nullable String targetPackage) {
+ StatsLog.write(StatsLog.CONTENT_CAPTURE_SERVICE_EVENTS, eventType, serviceName,
+ targetPackage);
+ }
+
+ /** @hide */
+ public static void writeServiceEvent(int eventType, @NonNull ComponentName service,
+ @Nullable ComponentName target) {
+ writeServiceEvent(eventType, ComponentName.flattenToShortString(service),
+ ComponentName.flattenToShortString(target));
+ }
+
+ /** @hide */
+ public static void writeServiceEvent(int eventType, @NonNull ComponentName service,
+ @Nullable String targetPackage) {
+ writeServiceEvent(eventType, ComponentName.flattenToShortString(service), targetPackage);
+ }
+
+ /** @hide */
+ public static void writeServiceEvent(int eventType, @NonNull ComponentName service) {
+ writeServiceEvent(eventType, ComponentName.flattenToShortString(service), null);
+ }
+
+ /** @hide */
+ public static void writeSetWhitelistEvent(@Nullable ComponentName service,
+ @Nullable List<String> packages, @Nullable List<ComponentName> activities) {
+ final String serviceName = ComponentName.flattenToShortString(service);
+ StringBuilder stringBuilder = new StringBuilder();
+ if (packages != null && packages.size() > 0) {
+ final int size = packages.size();
+ stringBuilder.append(packages.get(0));
+ for (int i = 1; i < size; i++) {
+ stringBuilder.append(" ");
+ stringBuilder.append(packages.get(i));
+ }
+ }
+ if (activities != null && activities.size() > 0) {
+ stringBuilder.append(" ");
+ stringBuilder.append(activities.get(0).flattenToShortString());
+ final int size = activities.size();
+ for (int i = 1; i < size; i++) {
+ stringBuilder.append(" ");
+ stringBuilder.append(activities.get(i).flattenToShortString());
+ }
+ }
+ StatsLog.write(StatsLog.CONTENT_CAPTURE_SERVICE_EVENTS,
+ StatsLog.CONTENT_CAPTURE_SERVICE_EVENTS__EVENT__SET_WHITELIST,
+ serviceName, stringBuilder.toString());
+ }
+
+ /** @hide */
+ public static void writeSessionEvent(int sessionId, int event, int flags,
+ @NonNull ComponentName service, @Nullable ComponentName app, boolean isChildSession) {
+ StatsLog.write(StatsLog.CONTENT_CAPTURE_SESSION_EVENTS, sessionId, event, flags,
+ ComponentName.flattenToShortString(service),
+ ComponentName.flattenToShortString(app), isChildSession);
+ }
+
+ /** @hide */
+ public static void writeSessionFlush(int sessionId, @NonNull ComponentName service,
+ @Nullable ComponentName app, @NonNull FlushMetrics fm,
+ @NonNull ContentCaptureOptions options, int flushReason) {
+ StatsLog.write(StatsLog.CONTENT_CAPTURE_FLUSHED, sessionId,
+ ComponentName.flattenToShortString(service),
+ ComponentName.flattenToShortString(app), fm.sessionStarted, fm.sessionFinished,
+ fm.viewAppearedCount, fm.viewDisappearedCount, fm.viewTextChangedCount,
+ options.maxBufferSize, options.idleFlushingFrequencyMs,
+ options.textChangeFlushingFrequencyMs, flushReason);
+ }
+}
diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java
index 67c3d01..a186d4e 100644
--- a/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java
+++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java
@@ -24,6 +24,9 @@
import static android.view.contentcapture.ContentCaptureSession.STATE_NOT_WHITELISTED;
import static android.view.contentcapture.ContentCaptureSession.STATE_NO_SERVICE;
+import static com.android.server.contentcapture.ContentCaptureMetricsLogger.writeServiceEvent;
+import static com.android.server.contentcapture.ContentCaptureMetricsLogger.writeSessionEvent;
+import static com.android.server.contentcapture.ContentCaptureMetricsLogger.writeSetWhitelistEvent;
import static com.android.server.wm.ActivityTaskManagerInternal.ASSIST_KEY_CONTENT;
import static com.android.server.wm.ActivityTaskManagerInternal.ASSIST_KEY_DATA;
import static com.android.server.wm.ActivityTaskManagerInternal.ASSIST_KEY_STRUCTURE;
@@ -35,6 +38,7 @@
import android.app.assist.AssistContent;
import android.app.assist.AssistStructure;
import android.content.ComponentName;
+import android.content.ContentCaptureOptions;
import android.content.pm.ActivityPresentationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
@@ -48,6 +52,7 @@
import android.service.contentcapture.ActivityEvent.ActivityEventType;
import android.service.contentcapture.ContentCaptureService;
import android.service.contentcapture.ContentCaptureServiceInfo;
+import android.service.contentcapture.FlushMetrics;
import android.service.contentcapture.IContentCaptureServiceCallback;
import android.service.contentcapture.SnapshotData;
import android.util.ArrayMap;
@@ -55,6 +60,7 @@
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
+import android.util.StatsLog;
import android.view.contentcapture.ContentCaptureCondition;
import android.view.contentcapture.DataRemovalRequest;
@@ -231,7 +237,6 @@
resurrectSessionsLocked();
}
- // TODO(b/119613670): log metrics
@GuardedBy("mLock")
public void startSessionLocked(@NonNull IBinder activityToken,
@NonNull ActivityPresentationInfo activityPresentationInfo, int sessionId, int uid,
@@ -263,9 +268,14 @@
if (!enabled) {
// TODO: it would be better to split in differet reasons, like
- // STATE_DISABLED_NO_SERVICE and STATE_DISABLED_BY_DEVICE_POLICY
+ // STATE_DISABLED_NO and STATE_DISABLED_BY_DEVICE_POLICY
setClientState(clientReceiver, STATE_DISABLED | STATE_NO_SERVICE,
/* binder= */ null);
+ // Log metrics.
+ writeSessionEvent(sessionId,
+ StatsLog.CONTENT_CAPTURE_SESSION_EVENTS__EVENT__SESSION_NOT_CREATED,
+ STATE_DISABLED | STATE_NO_SERVICE, serviceComponentName,
+ componentName, /* isChildSession= */ false);
return;
}
if (serviceComponentName == null) {
@@ -285,6 +295,11 @@
}
setClientState(clientReceiver, STATE_DISABLED | STATE_NOT_WHITELISTED,
/* binder= */ null);
+ // Log metrics.
+ writeSessionEvent(sessionId,
+ StatsLog.CONTENT_CAPTURE_SESSION_EVENTS__EVENT__SESSION_NOT_CREATED,
+ STATE_DISABLED | STATE_NOT_WHITELISTED, serviceComponentName,
+ componentName, /* isChildSession= */ false);
return;
}
@@ -294,6 +309,11 @@
+ ": ignoring because it already exists for " + existingSession.mActivityToken);
setClientState(clientReceiver, STATE_DISABLED | STATE_DUPLICATED_ID,
/* binder=*/ null);
+ // Log metrics.
+ writeSessionEvent(sessionId,
+ StatsLog.CONTENT_CAPTURE_SESSION_EVENTS__EVENT__SESSION_NOT_CREATED,
+ STATE_DISABLED | STATE_DUPLICATED_ID,
+ serviceComponentName, componentName, /* isChildSession= */ false);
return;
}
@@ -302,11 +322,15 @@
}
if (mRemoteService == null) {
- // TODO(b/119613670): log metrics
Slog.w(TAG, "startSession(id=" + existingSession + ", token=" + activityToken
+ ": ignoring because service is not set");
setClientState(clientReceiver, STATE_DISABLED | STATE_NO_SERVICE,
/* binder= */ null);
+ // Log metrics.
+ writeSessionEvent(sessionId,
+ StatsLog.CONTENT_CAPTURE_SESSION_EVENTS__EVENT__SESSION_NOT_CREATED,
+ STATE_DISABLED | STATE_NO_SERVICE, serviceComponentName,
+ componentName, /* isChildSession= */ false);
return;
}
@@ -324,7 +348,6 @@
newSession.notifySessionStartedLocked(clientReceiver);
}
- // TODO(b/119613670): log metrics
@GuardedBy("mLock")
public void finishSessionLocked(int sessionId) {
if (!isEnabledLocked()) {
@@ -553,6 +576,7 @@
+ " for user " + mUserId);
}
mMaster.mGlobalContentCaptureOptions.setWhitelist(mUserId, packages, activities);
+ writeSetWhitelistEvent(getServiceComponentName(), packages, activities);
// Must disable session that are not the whitelist anymore...
final int numSessions = mSessions.size();
@@ -602,7 +626,6 @@
mConditionsByPkg.put(packageName, new ArraySet<>(conditions));
}
}
- // TODO(b/119613670): log metrics
}
@Override
@@ -616,6 +639,15 @@
} finally {
Binder.restoreCallingIdentity(token);
}
+ writeServiceEvent(StatsLog.CONTENT_CAPTURE_SERVICE_EVENTS__EVENT__SET_DISABLED,
+ getServiceComponentName());
+ }
+
+ @Override
+ public void writeSessionFlush(int sessionId, ComponentName app, FlushMetrics flushMetrics,
+ ContentCaptureOptions options, int flushReason) {
+ ContentCaptureMetricsLogger.writeSessionFlush(sessionId, getServiceComponentName(), app,
+ flushMetrics, options, flushReason);
}
}
}
diff --git a/services/contentcapture/java/com/android/server/contentcapture/RemoteContentCaptureService.java b/services/contentcapture/java/com/android/server/contentcapture/RemoteContentCaptureService.java
index 2171033..18daf32 100644
--- a/services/contentcapture/java/com/android/server/contentcapture/RemoteContentCaptureService.java
+++ b/services/contentcapture/java/com/android/server/contentcapture/RemoteContentCaptureService.java
@@ -18,6 +18,9 @@
import static android.view.contentcapture.ContentCaptureHelper.sDebug;
import static android.view.contentcapture.ContentCaptureHelper.sVerbose;
+import static com.android.server.contentcapture.ContentCaptureMetricsLogger.writeServiceEvent;
+import static com.android.server.contentcapture.ContentCaptureMetricsLogger.writeSessionEvent;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.ComponentName;
@@ -28,6 +31,7 @@
import android.service.contentcapture.IContentCaptureServiceCallback;
import android.service.contentcapture.SnapshotData;
import android.util.Slog;
+import android.util.StatsLog;
import android.view.contentcapture.ContentCaptureContext;
import android.view.contentcapture.DataRemovalRequest;
@@ -77,6 +81,8 @@
if (connected) {
try {
mService.onConnected(mServerCallback, sVerbose, sDebug);
+ writeServiceEvent(StatsLog.CONTENT_CAPTURE_SERVICE_EVENTS__EVENT__ON_CONNECTED,
+ mComponentName);
} finally {
// Update the system-service state, in case the service reconnected after
// dying
@@ -84,6 +90,8 @@
}
} else {
mService.onDisconnected();
+ writeServiceEvent(StatsLog.CONTENT_CAPTURE_SERVICE_EVENTS__EVENT__ON_DISCONNECTED,
+ mComponentName);
}
} catch (Exception e) {
Slog.w(mTag, "Exception calling onConnectedStateChanged(" + connected + "): " + e);
@@ -102,6 +110,10 @@
@NonNull IResultReceiver clientReceiver, int initialState) {
scheduleAsyncRequest(
(s) -> s.onSessionStarted(context, sessionId, uid, clientReceiver, initialState));
+ // Metrics logging.
+ writeSessionEvent(sessionId,
+ StatsLog.CONTENT_CAPTURE_SESSION_EVENTS__EVENT__ON_SESSION_STARTED, initialState,
+ getComponentName(), context.getActivityComponent(), /* is_child_session= */ false);
}
/**
@@ -110,6 +122,11 @@
*/
public void onSessionFinished(int sessionId) {
scheduleAsyncRequest((s) -> s.onSessionFinished(sessionId));
+ // Metrics logging.
+ writeSessionEvent(sessionId,
+ StatsLog.CONTENT_CAPTURE_SESSION_EVENTS__EVENT__ON_SESSION_FINISHED,
+ /* flags= */ 0, getComponentName(), /* app= */ null,
+ /* is_child_session= */ false);
}
/**
@@ -124,6 +141,8 @@
*/
public void onDataRemovalRequest(@NonNull DataRemovalRequest request) {
scheduleAsyncRequest((s) -> s.onDataRemovalRequest(request));
+ writeServiceEvent(StatsLog.CONTENT_CAPTURE_SERVICE_EVENTS__EVENT__ON_USER_DATA_REMOVED,
+ mComponentName);
}
/**
diff --git a/services/core/java/com/android/server/AlarmManagerService.java b/services/core/java/com/android/server/AlarmManagerService.java
index 72899f6..cb61259 100644
--- a/services/core/java/com/android/server/AlarmManagerService.java
+++ b/services/core/java/com/android/server/AlarmManagerService.java
@@ -1138,8 +1138,8 @@
? clampPositive(whenElapsed + a.windowLength)
: maxTriggerTime(nowElapsed, whenElapsed, a.repeatInterval);
}
- a.whenElapsed = whenElapsed;
- a.maxWhenElapsed = maxElapsed;
+ a.expectedWhenElapsed = a.whenElapsed = whenElapsed;
+ a.expectedMaxWhenElapsed = a.maxWhenElapsed = maxElapsed;
setImplLocked(a, true, doValidate);
}
@@ -1243,7 +1243,7 @@
alarm.count += (nowELAPSED - alarm.expectedWhenElapsed) / alarm.repeatInterval;
// Also schedule its next recurrence
final long delta = alarm.count * alarm.repeatInterval;
- final long nextElapsed = alarm.whenElapsed + delta;
+ final long nextElapsed = alarm.expectedWhenElapsed + delta;
setImplLocked(alarm.type, alarm.when + delta, nextElapsed, alarm.windowLength,
maxTriggerTime(nowELAPSED, nextElapsed, alarm.repeatInterval),
alarm.repeatInterval, alarm.operation, null, null, alarm.flags, true,
@@ -3596,10 +3596,9 @@
// this adjustment will be zero if we're late by
// less than one full repeat interval
alarm.count += (nowELAPSED - alarm.expectedWhenElapsed) / alarm.repeatInterval;
-
// Also schedule its next recurrence
final long delta = alarm.count * alarm.repeatInterval;
- final long nextElapsed = alarm.whenElapsed + delta;
+ final long nextElapsed = alarm.expectedWhenElapsed + delta;
setImplLocked(alarm.type, alarm.when + delta, nextElapsed, alarm.windowLength,
maxTriggerTime(nowELAPSED, nextElapsed, alarm.repeatInterval),
alarm.repeatInterval, alarm.operation, null, null, alarm.flags, true,
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index d8f5937..ea71a3b 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -735,11 +735,11 @@
* <p>NOTE: Callers should avoid acquiring the mPidsSelfLocked lock before calling this
* method.
*/
- void put(int key, ProcessRecord value) {
+ void put(ProcessRecord app) {
synchronized (this) {
- mPidMap.put(key, value);
+ mPidMap.put(app.pid, app);
}
- mAtmInternal.onProcessMapped(key, value.getWindowProcessController());
+ mAtmInternal.onProcessMapped(app.pid, app.getWindowProcessController());
}
/**
@@ -747,11 +747,18 @@
* <p>NOTE: Callers should avoid acquiring the mPidsSelfLocked lock before calling this
* method.
*/
- void remove(int pid) {
+ void remove(ProcessRecord app) {
+ boolean removed = false;
synchronized (this) {
- mPidMap.remove(pid);
+ final ProcessRecord existingApp = mPidMap.get(app.pid);
+ if (existingApp != null && existingApp.startSeq == app.startSeq) {
+ mPidMap.remove(app.pid);
+ removed = true;
+ }
}
- mAtmInternal.onProcessUnMapped(pid);
+ if (removed) {
+ mAtmInternal.onProcessUnMapped(app.pid);
+ }
}
/**
@@ -759,17 +766,18 @@
* <p>NOTE: Callers should avoid acquiring the mPidsSelfLocked lock before calling this
* method.
*/
- boolean removeIfNoThread(int pid) {
+ boolean removeIfNoThread(ProcessRecord app) {
boolean removed = false;
synchronized (this) {
- final ProcessRecord app = get(pid);
- if (app != null && app.thread == null) {
- mPidMap.remove(pid);
+ final ProcessRecord existingApp = get(app.pid);
+ if (existingApp != null && existingApp.startSeq == app.startSeq
+ && app.thread == null) {
+ mPidMap.remove(app.pid);
removed = true;
}
}
if (removed) {
- mAtmInternal.onProcessUnMapped(pid);
+ mAtmInternal.onProcessUnMapped(app.pid);
}
return removed;
}
@@ -1970,7 +1978,7 @@
app.getWindowProcessController().setPid(MY_PID);
app.maxAdj = ProcessList.SYSTEM_ADJ;
app.makeActive(mSystemThread.getApplicationThread(), mProcessStats);
- mPidsSelfLocked.put(app.pid, app);
+ mPidsSelfLocked.put(app);
mProcessList.updateLruProcessLocked(app, false, null);
updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
}
@@ -4601,7 +4609,7 @@
@GuardedBy("this")
private final void processStartTimedOutLocked(ProcessRecord app) {
final int pid = app.pid;
- boolean gone = mPidsSelfLocked.removeIfNoThread(pid);
+ boolean gone = mPidsSelfLocked.removeIfNoThread(app);
if (gone) {
Slog.w(TAG, "Process " + app + " failed to attach");
@@ -4658,6 +4666,26 @@
synchronized (mPidsSelfLocked) {
app = mPidsSelfLocked.get(pid);
}
+ if (app != null && (app.startUid != callingUid || app.startSeq != startSeq)) {
+ String processName = null;
+ final ProcessRecord pending = mProcessList.mPendingStarts.get(startSeq);
+ if (pending != null) {
+ processName = pending.processName;
+ }
+ final String msg = "attachApplicationLocked process:" + processName
+ + " startSeq:" + startSeq
+ + " pid:" + pid
+ + " belongs to another existing app:" + app.processName
+ + " startSeq:" + app.startSeq;
+ Slog.wtf(TAG, msg);
+ // SafetyNet logging for b/131105245.
+ EventLog.writeEvent(0x534e4554, "131105245", app.startUid, msg);
+ // If there is already an app occupying that pid that hasn't been cleaned up
+ cleanUpApplicationRecordLocked(app, false, false, -1,
+ true /*replacingPid*/);
+ mPidsSelfLocked.remove(app);
+ app = null;
+ }
} else {
app = null;
}
@@ -4666,7 +4694,7 @@
// update the internal state.
if (app == null && startSeq > 0) {
final ProcessRecord pending = mProcessList.mPendingStarts.get(startSeq);
- if (pending != null && pending.startUid == callingUid
+ if (pending != null && pending.startUid == callingUid && pending.startSeq == startSeq
&& mProcessList.handleProcessStartedLocked(pending, pid, pending
.isUsingWrapper(),
startSeq, true)) {
@@ -13642,7 +13670,7 @@
return true;
} else if (app.pid > 0 && app.pid != MY_PID) {
// Goodbye!
- mPidsSelfLocked.remove(app.pid);
+ mPidsSelfLocked.remove(app);
mHandler.removeMessages(PROC_START_TIMEOUT_MSG, app);
mBatteryStatsService.noteProcessFinish(app.processName, app.info.uid);
if (app.isolated) {
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index b394eea..dc94c1e 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -1445,10 +1445,11 @@
long startTime = SystemClock.elapsedRealtime();
if (app.pid > 0 && app.pid != ActivityManagerService.MY_PID) {
checkSlow(startTime, "startProcess: removing from pids map");
- mService.mPidsSelfLocked.remove(app.pid);
+ mService.mPidsSelfLocked.remove(app);
mService.mHandler.removeMessages(PROC_START_TIMEOUT_MSG, app);
checkSlow(startTime, "startProcess: done removing from pids map");
app.setPid(0);
+ app.startSeq = 0;
}
if (DEBUG_PROCESSES && mService.mProcessesOnHold.contains(app)) Slog.v(
@@ -1656,6 +1657,14 @@
app.killedByAm = false;
app.removed = false;
app.killed = false;
+ if (app.startSeq != 0) {
+ Slog.wtf(TAG, "startProcessLocked processName:" + app.processName
+ + " with non-zero startSeq:" + app.startSeq);
+ }
+ if (app.pid != 0) {
+ Slog.wtf(TAG, "startProcessLocked processName:" + app.processName
+ + " with non-zero pid:" + app.pid);
+ }
final long startSeq = app.startSeq = ++mProcStartSeqCounter;
app.setStartParams(uid, hostingRecord, seInfo, startTime);
app.setUsingWrapper(invokeWith != null
@@ -2063,12 +2072,15 @@
// If there is already an app occupying that pid that hasn't been cleaned up
if (oldApp != null && !app.isolated) {
// Clean up anything relating to this pid first
- Slog.w(TAG, "Reusing pid " + pid
- + " while app is still mapped to it");
+ Slog.wtf(TAG, "handleProcessStartedLocked process:" + app.processName
+ + " startSeq:" + app.startSeq
+ + " pid:" + pid
+ + " belongs to another existing app:" + oldApp.processName
+ + " startSeq:" + oldApp.startSeq);
mService.cleanUpApplicationRecordLocked(oldApp, false, false, -1,
true /*replacingPid*/);
}
- mService.mPidsSelfLocked.put(pid, app);
+ mService.mPidsSelfLocked.put(app);
synchronized (mService.mPidsSelfLocked) {
if (!procAttached) {
Message msg = mService.mHandler.obtainMessage(PROC_START_TIMEOUT_MSG);
@@ -2241,7 +2253,7 @@
.pendingStart)) {
int pid = app.pid;
if (pid > 0) {
- mService.mPidsSelfLocked.remove(pid);
+ mService.mPidsSelfLocked.remove(app);
mService.mHandler.removeMessages(PROC_START_TIMEOUT_MSG, app);
mService.mBatteryStatsService.noteProcessFinish(app.processName, app.info.uid);
if (app.isolated) {
diff --git a/services/core/java/com/android/server/om/OverlayManagerShellCommand.java b/services/core/java/com/android/server/om/OverlayManagerShellCommand.java
index 7ee167a..99f5839 100644
--- a/services/core/java/com/android/server/om/OverlayManagerShellCommand.java
+++ b/services/core/java/com/android/server/om/OverlayManagerShellCommand.java
@@ -120,42 +120,65 @@
return 1;
}
}
- final String packageName = getNextArg();
+ final String packageName = getNextArg();
+ if (packageName != null) {
+ List<OverlayInfo> overlaysForTarget = mInterface.getOverlayInfosForTarget(
+ packageName, userId);
+
+ // If the package is not targeted by any overlays, check if the package is an overlay.
+ if (overlaysForTarget.isEmpty()) {
+ final OverlayInfo info = mInterface.getOverlayInfo(packageName, userId);
+ if (info != null) {
+ printListOverlay(out, info);
+ }
+ return 0;
+ }
+
+ out.println(packageName);
+
+ // Print the overlays for the target.
+ final int n = overlaysForTarget.size();
+ for (int i = 0; i < n; i++) {
+ printListOverlay(out, overlaysForTarget.get(i));
+ }
+
+ return 0;
+ }
+
+ // Print all overlays grouped by target package name.
final Map<String, List<OverlayInfo>> allOverlays = mInterface.getAllOverlays(userId);
for (final String targetPackageName : allOverlays.keySet()) {
- if (targetPackageName.equals(packageName)) {
- out.println(targetPackageName);
- }
+ out.println(targetPackageName);
+
List<OverlayInfo> overlaysForTarget = allOverlays.get(targetPackageName);
final int n = overlaysForTarget.size();
for (int i = 0; i < n; i++) {
- final OverlayInfo oi = overlaysForTarget.get(i);
- if (!targetPackageName.equals(packageName) && !oi.packageName.equals(packageName)) {
- continue;
- }
- String status;
- switch (oi.state) {
- case OverlayInfo.STATE_ENABLED_STATIC:
- case OverlayInfo.STATE_ENABLED:
- status = "[x]";
- break;
- case OverlayInfo.STATE_DISABLED:
- status = "[ ]";
- break;
- default:
- status = "---";
- break;
- }
- out.println(String.format("%s %s", status, oi.packageName));
+ printListOverlay(out, overlaysForTarget.get(i));
}
- if (targetPackageName.equals(packageName)) {
- out.println();
- }
+ out.println();
}
+
return 0;
}
+ private void printListOverlay(PrintWriter out, OverlayInfo oi) {
+ String status;
+ switch (oi.state) {
+ case OverlayInfo.STATE_ENABLED_STATIC:
+ case OverlayInfo.STATE_ENABLED:
+ status = "[x]";
+ break;
+ case OverlayInfo.STATE_DISABLED:
+ status = "[ ]";
+ break;
+ default:
+ status = "---";
+ break;
+ }
+ out.println(String.format("%s %s", status, oi.packageName));
+ }
+
private int runEnableDisable(final boolean enable) throws RemoteException {
final PrintWriter err = getErrPrintWriter();
diff --git a/services/core/java/com/android/server/power/batterysaver/BatterySaverStateMachine.java b/services/core/java/com/android/server/power/batterysaver/BatterySaverStateMachine.java
index fe0b9a6..b7e18c3 100644
--- a/services/core/java/com/android/server/power/batterysaver/BatterySaverStateMachine.java
+++ b/services/core/java/com/android/server/power/batterysaver/BatterySaverStateMachine.java
@@ -537,6 +537,7 @@
Slog.d(TAG, "doAutoBatterySaverLocked: mBootCompleted=" + mBootCompleted
+ " mSettingsLoaded=" + mSettingsLoaded
+ " mBatteryStatusSet=" + mBatteryStatusSet
+ + " mState=" + mState
+ " mIsBatteryLevelLow=" + mIsBatteryLevelLow
+ " mIsPowered=" + mIsPowered
+ " mSettingAutomaticBatterySaver=" + mSettingAutomaticBatterySaver
@@ -689,9 +690,9 @@
final boolean isStickyDisabled =
mBatterySaverStickyBehaviourDisabled || !mSettingBatterySaverEnabledSticky;
if (isStickyDisabled || shouldTurnOffSticky) {
+ mState = STATE_OFF;
setStickyActive(false);
triggerStickyDisabledNotification();
- mState = STATE_OFF;
} else if (!mIsPowered) {
// Re-enable BS.
enableBatterySaverLocked(/*enable*/ true, /*manual*/ true,
@@ -797,7 +798,8 @@
Intent.ACTION_POWER_USAGE_SUMMARY));
}
- private void triggerStickyDisabledNotification() {
+ @VisibleForTesting
+ void triggerStickyDisabledNotification() {
NotificationManager manager = mContext.getSystemService(NotificationManager.class);
ensureNotificationChannelExists(manager, BATTERY_SAVER_NOTIF_CHANNEL_ID,
R.string.battery_saver_notification_channel_name);
diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java
index ab5e071..d838691 100644
--- a/services/core/java/com/android/server/wm/TaskStack.java
+++ b/services/core/java/com/android/server/wm/TaskStack.java
@@ -319,7 +319,8 @@
* Sets the bounds animation target bounds ahead of an animation. This can't currently be done
* in onAnimationStart() since that is started on the UiThread.
*/
- void setAnimationFinalBounds(Rect sourceHintBounds, Rect destBounds, boolean toFullscreen) {
+ private void setAnimationFinalBounds(Rect sourceHintBounds, Rect destBounds,
+ boolean toFullscreen) {
mBoundsAnimatingRequested = true;
mBoundsAnimatingToFullscreen = toFullscreen;
if (destBounds != null) {
@@ -329,7 +330,11 @@
}
if (sourceHintBounds != null) {
mBoundsAnimationSourceHintBounds.set(sourceHintBounds);
- } else {
+ } else if (!mBoundsAnimating) {
+ // If the bounds are already animating, we don't want to reset the source hint. This is
+ // because the source hint is sent when starting the animation from the client that
+ // requested to enter pip. Other requests can adjust the pip bounds during an animation,
+ // but could accidentally reset the source hint bounds.
mBoundsAnimationSourceHintBounds.setEmpty();
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/AlarmManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/AlarmManagerServiceTest.java
index 6517303..77e2517 100644
--- a/services/tests/mockingservicestests/src/com/android/server/AlarmManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/AlarmManagerServiceTest.java
@@ -43,6 +43,8 @@
import static com.android.server.AlarmManagerService.Constants.KEY_MAX_INTERVAL;
import static com.android.server.AlarmManagerService.Constants.KEY_MIN_FUTURITY;
import static com.android.server.AlarmManagerService.Constants.KEY_MIN_INTERVAL;
+import static com.android.server.AlarmManagerService.IS_WAKEUP_MASK;
+import static com.android.server.AlarmManagerService.TIME_CHANGED_MASK;
import static com.android.server.AlarmManagerService.WORKING_INDEX;
import static org.junit.Assert.assertEquals;
@@ -126,13 +128,15 @@
private MockitoSession mMockingSession;
private Injector mInjector;
private volatile long mNowElapsedTest;
+ private volatile long mNowRtcTest;
@GuardedBy("mTestTimer")
private TestTimer mTestTimer = new TestTimer();
static class TestTimer {
private long mElapsed;
boolean mExpired;
- int mType;
+ private int mType;
+ private int mFlags; // Flags used to decide what needs to be evaluated.
synchronized long getElapsed() {
return mElapsed;
@@ -147,7 +151,16 @@
return mType;
}
+ synchronized int getFlags() {
+ return mFlags;
+ }
+
synchronized void expire() throws InterruptedException {
+ expire(IS_WAKEUP_MASK); // Default: evaluate eligibility of all alarms
+ }
+
+ synchronized void expire(int flags) throws InterruptedException {
+ mFlags = flags;
mExpired = true;
notifyAll();
// Now wait for the alarm thread to finish execution.
@@ -181,7 +194,7 @@
}
mTestTimer.mExpired = false;
}
- return AlarmManagerService.IS_WAKEUP_MASK; // Doesn't matter, just evaluate.
+ return mTestTimer.getFlags();
}
@Override
@@ -215,6 +228,11 @@
}
@Override
+ long getCurrentTimeMillis() {
+ return mNowRtcTest;
+ }
+
+ @Override
AlarmManagerService.ClockReceiver getClockReceiver(AlarmManagerService service) {
return mClockReceiver;
}
@@ -340,7 +358,7 @@
}
@Test
- public void testSingleAlarmSet() {
+ public void singleElapsedAlarmSet() {
final long triggerTime = mNowElapsedTest + 5000;
final PendingIntent alarmPi = getNewMockPendingIntent();
setTestAlarm(ELAPSED_REALTIME_WAKEUP, triggerTime, alarmPi);
@@ -348,6 +366,33 @@
}
@Test
+ public void singleRtcAlarmSet() {
+ mNowElapsedTest = 54;
+ mNowRtcTest = 1243; // arbitrary values of time
+ final long triggerRtc = mNowRtcTest + 5000;
+ final PendingIntent alarmPi = getNewMockPendingIntent();
+ setTestAlarm(RTC_WAKEUP, triggerRtc, alarmPi);
+ final long triggerElapsed = triggerRtc - (mNowRtcTest - mNowElapsedTest);
+ assertEquals(triggerElapsed, mTestTimer.getElapsed());
+ }
+
+ @Test
+ public void timeChangeMovesRtcAlarm() throws Exception {
+ mNowElapsedTest = 42;
+ mNowRtcTest = 4123; // arbitrary values of time
+ final long triggerRtc = mNowRtcTest + 5000;
+ final PendingIntent alarmPi = getNewMockPendingIntent();
+ setTestAlarm(RTC_WAKEUP, triggerRtc, alarmPi);
+ final long triggerElapsed1 = mTestTimer.getElapsed();
+ final long timeDelta = -123;
+ mNowRtcTest += timeDelta;
+ mTestTimer.expire(TIME_CHANGED_MASK);
+ final long triggerElapsed2 = mTestTimer.getElapsed();
+ assertEquals("Invalid movement of triggerElapsed following time change", triggerElapsed2,
+ triggerElapsed1 - timeDelta);
+ }
+
+ @Test
public void testSingleAlarmExpiration() throws Exception {
final long triggerTime = mNowElapsedTest + 5000;
final PendingIntent alarmPi = getNewMockPendingIntent();
diff --git a/services/tests/mockingservicestests/src/com/android/server/power/batterysaver/BatterySaverStateMachineTest.java b/services/tests/mockingservicestests/src/com/android/server/power/batterysaver/BatterySaverStateMachineTest.java
index 212d2a8..a8faa54 100644
--- a/services/tests/mockingservicestests/src/com/android/server/power/batterysaver/BatterySaverStateMachineTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/power/batterysaver/BatterySaverStateMachineTest.java
@@ -15,6 +15,10 @@
*/
package com.android.server.power.batterysaver;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.inOrder;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+
import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -22,6 +26,8 @@
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.NotificationManager;
@@ -37,6 +43,7 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.InOrder;
import java.util.HashMap;
import java.util.Objects;
@@ -201,6 +208,8 @@
mDevice = new Device();
mTarget = new TestableBatterySaverStateMachine();
+ spyOn(mTarget);
+ doNothing().when(mTarget).triggerStickyDisabledNotification();
mDevice.pushBatteryStatus();
mTarget.onBootCompleted();
@@ -423,7 +432,7 @@
assertEquals(70, mPersistedState.batteryLevel);
assertEquals(false, mPersistedState.batteryLow);
- // Bump ump the threshold.
+ // Bump up the threshold.
mDevice.putGlobalSetting(Global.LOW_POWER_MODE_TRIGGER_LEVEL, 70);
mDevice.setBatteryLevel(mPersistedState.batteryLevel);
@@ -545,6 +554,8 @@
@Test
public void testAutoBatterySaver_withSticky_withAutoOffEnabled() {
+ InOrder inOrder = inOrder(mTarget);
+
mDevice.putGlobalSetting(Global.LOW_POWER_MODE_TRIGGER_LEVEL, 50);
mDevice.putGlobalSetting(Global.LOW_POWER_MODE_STICKY_AUTO_DISABLE_ENABLED, 1);
mDevice.putGlobalSetting(Global.LOW_POWER_MODE_STICKY_AUTO_DISABLE_LEVEL, 90);
@@ -569,6 +580,7 @@
assertEquals(true, mDevice.batterySaverEnabled); // Stays on.
assertEquals(95, mPersistedState.batteryLevel);
assertEquals(false, mPersistedState.batteryLow);
+ inOrder.verify(mTarget, never()).triggerStickyDisabledNotification();
// Scenario 2: User turns BS on manually above the threshold then charges device. BS
// shouldn't turn back on.
@@ -584,6 +596,7 @@
assertEquals(false, mDevice.batterySaverEnabled); // Sticky BS no longer enabled.
assertEquals(97, mPersistedState.batteryLevel);
assertEquals(false, mPersistedState.batteryLow);
+ inOrder.verify(mTarget).triggerStickyDisabledNotification();
// Scenario 3: User turns BS on manually above the threshold. Device drains below
// threshold and then charged to below threshold. Sticky BS should activate.
@@ -612,6 +625,7 @@
assertEquals(true, mDevice.batterySaverEnabled);
assertEquals(30, mPersistedState.batteryLevel);
assertEquals(true, mPersistedState.batteryLow);
+ inOrder.verify(mTarget, never()).triggerStickyDisabledNotification();
// Scenario 4: User turns BS on manually above the threshold. Device drains below
// threshold and is eventually charged to above threshold. Sticky BS should turn off.
@@ -627,6 +641,7 @@
assertEquals(false, mDevice.batterySaverEnabled); // Sticky BS no longer enabled.
assertEquals(90, mPersistedState.batteryLevel);
assertEquals(false, mPersistedState.batteryLow);
+ inOrder.verify(mTarget).triggerStickyDisabledNotification();
// Scenario 5: User turns BS on manually below threshold and charges to below threshold.
// Sticky BS should activate.
@@ -654,6 +669,7 @@
assertEquals(true, mDevice.batterySaverEnabled); // Sticky BS still on.
assertEquals(80, mPersistedState.batteryLevel);
assertEquals(false, mPersistedState.batteryLow);
+ inOrder.verify(mTarget, never()).triggerStickyDisabledNotification();
// Scenario 6: User turns BS on manually below threshold and eventually charges to above
// threshold. Sticky BS should turn off.
@@ -665,6 +681,7 @@
assertEquals(false, mDevice.batterySaverEnabled);
assertEquals(95, mPersistedState.batteryLevel);
assertEquals(false, mPersistedState.batteryLow);
+ inOrder.verify(mTarget).triggerStickyDisabledNotification();
// Scenario 7: User turns BS on above threshold and then reboots device. Sticky BS
// shouldn't activate.
@@ -676,6 +693,8 @@
assertEquals(false, mDevice.batterySaverEnabled);
assertEquals(93, mPersistedState.batteryLevel);
assertEquals(false, mPersistedState.batteryLow);
+ // initDevice() changes the mTarget reference, so inOrder is invalid here.
+ verify(mTarget).triggerStickyDisabledNotification();
// Scenario 8: User turns BS on below threshold and then reboots device without charging.
// Sticky BS should activate.
@@ -690,6 +709,8 @@
assertEquals(true, mDevice.batterySaverEnabled);
assertEquals(75, mPersistedState.batteryLevel);
assertEquals(false, mPersistedState.batteryLow);
+ // initDevice() changes the mTarget reference, so inOrder is invalid here.
+ verify(mTarget, never()).triggerStickyDisabledNotification();
// Scenario 9: User turns BS on below threshold and then reboots device after charging
// above threshold. Sticky BS shouldn't activate.
@@ -702,6 +723,8 @@
assertEquals(false, mDevice.batterySaverEnabled);
assertEquals(100, mPersistedState.batteryLevel);
assertEquals(false, mPersistedState.batteryLow);
+ // initDevice() changes the mTarget reference, so inOrder is invalid here.
+ verify(mTarget).triggerStickyDisabledNotification();
// Scenario 10: Somehow autoDisableLevel is set to a value below lowPowerModeTriggerLevel
// and then user enables manually above both thresholds, discharges below
@@ -738,6 +761,8 @@
assertEquals(true, mDevice.batterySaverEnabled);
assertEquals(65, mPersistedState.batteryLevel);
assertEquals(true, mPersistedState.batteryLow);
+ // initDevice() changes the mTarget reference, so inOrder is invalid here.
+ verify(mTarget, never()).triggerStickyDisabledNotification();
}
@Test
@@ -780,6 +805,7 @@
assertEquals(false, mDevice.batterySaverEnabled); // Sticky BS no longer enabled.
assertEquals(95, mPersistedState.batteryLevel);
assertEquals(false, mPersistedState.batteryLow);
+ verify(mTarget).triggerStickyDisabledNotification();
}
@Test