Merge "Dump cancelled jobs for testing."
diff --git a/Android.bp b/Android.bp
index 30b38d3..ef25ec2 100644
--- a/Android.bp
+++ b/Android.bp
@@ -326,6 +326,7 @@
"packages/modules/Connectivity/framework/aidl-export",
"packages/modules/Media/apex/aidl/stable",
"hardware/interfaces/graphics/common/aidl",
+ "frameworks/native/libs/permission/aidl",
],
},
dxflags: [
@@ -595,6 +596,7 @@
"packages/modules/Connectivity/framework/aidl-export",
"packages/modules/Media/apex/aidl/stable",
"hardware/interfaces/graphics/common/aidl",
+ "frameworks/native/libs/permission/aidl",
],
},
// These are libs from framework-internal-utils that are required (i.e. being referenced)
diff --git a/core/api/current.txt b/core/api/current.txt
index 32bdec0..9ebd118 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -9126,6 +9126,7 @@
method @Nullable public String getAttributionTag();
method @Nullable public android.content.AttributionSource getNext();
method @Nullable public String getPackageName();
+ method public int getPid();
method public int getUid();
method public boolean isTrusted(@NonNull android.content.Context);
method @NonNull public static android.content.AttributionSource myAttributionSource();
@@ -9140,6 +9141,7 @@
method @NonNull public android.content.AttributionSource.Builder setAttributionTag(@Nullable String);
method @NonNull public android.content.AttributionSource.Builder setNext(@Nullable android.content.AttributionSource);
method @NonNull public android.content.AttributionSource.Builder setPackageName(@Nullable String);
+ method @NonNull public android.content.AttributionSource.Builder setPid(int);
}
public abstract class BroadcastReceiver {
@@ -12448,6 +12450,16 @@
field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.SigningInfo> CREATOR;
}
+ public final class UserProperties implements android.os.Parcelable {
+ method public int describeContents();
+ method public int getShowInLauncher();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.UserProperties> CREATOR;
+ field public static final int SHOW_IN_LAUNCHER_NO = 2; // 0x2
+ field public static final int SHOW_IN_LAUNCHER_SEPARATE = 1; // 0x1
+ field public static final int SHOW_IN_LAUNCHER_WITH_PARENT = 0; // 0x0
+ }
+
public final class VersionedPackage implements android.os.Parcelable {
ctor public VersionedPackage(@NonNull String, int);
ctor public VersionedPackage(@NonNull String, long);
@@ -16903,6 +16915,7 @@
method public int describeContents();
method public int getFormat();
method public int getHeight();
+ method public long getId();
method public int getLayers();
method public long getUsage();
method public int getWidth();
@@ -31994,6 +32007,7 @@
method @Deprecated public static final boolean supportsProcesses();
field public static final int BLUETOOTH_UID = 1002; // 0x3ea
field public static final int FIRST_APPLICATION_UID = 10000; // 0x2710
+ field public static final int INVALID_PID = -1; // 0xffffffff
field public static final int INVALID_UID = -1; // 0xffffffff
field public static final int LAST_APPLICATION_UID = 19999; // 0x4e1f
field public static final int PHONE_UID = 1001; // 0x3e9
@@ -32250,6 +32264,7 @@
method public android.os.UserHandle getUserForSerialNumber(long);
method @NonNull @RequiresPermission(anyOf={"android.permission.MANAGE_USERS", "android.permission.CREATE_USERS", "android.permission.QUERY_USERS", android.Manifest.permission.GET_ACCOUNTS_PRIVILEGED}) public String getUserName();
method public java.util.List<android.os.UserHandle> getUserProfiles();
+ method @NonNull @RequiresPermission(anyOf={"android.permission.MANAGE_USERS", "android.permission.QUERY_USERS", "android.permission.INTERACT_ACROSS_USERS"}, conditional=true) public android.content.pm.UserProperties getUserProperties(@NonNull android.os.UserHandle);
method public android.os.Bundle getUserRestrictions();
method @RequiresPermission(anyOf={"android.permission.MANAGE_USERS", "android.permission.INTERACT_ACROSS_USERS"}, conditional=true) public android.os.Bundle getUserRestrictions(android.os.UserHandle);
method public boolean hasUserRestriction(String);
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 6b3dc82..b383d7d 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -107,7 +107,6 @@
import android.net.Proxy;
import android.net.TrafficStats;
import android.net.Uri;
-import android.net.wifi.WifiFrameworkInitializer;
import android.os.AsyncTask;
import android.os.Binder;
import android.os.BluetoothServiceManager;
@@ -7901,8 +7900,6 @@
BluetoothFrameworkInitializer.setBluetoothServiceManager(new BluetoothServiceManager());
BluetoothFrameworkInitializer.setBinderCallsStatsInitializer(context -> {
BinderCallsStats.startForBluetooth(context); });
- WifiFrameworkInitializer.setBinderCallsStatsInitializer(context -> {
- BinderCallsStats.startForWifi(context); });
}
private void purgePendingResources() {
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 9072b50..2f282f7 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -8578,9 +8578,9 @@
public int noteProxyOp(int op, @Nullable String proxiedPackageName, int proxiedUid,
@Nullable String proxiedAttributionTag, @Nullable String message) {
return noteProxyOp(op, new AttributionSource(mContext.getAttributionSource(),
- new AttributionSource(proxiedUid, proxiedPackageName, proxiedAttributionTag,
- mContext.getAttributionSource().getToken())), message,
- /*skipProxyOperation*/ false);
+ new AttributionSource(proxiedUid, /*pid*/ -1, proxiedPackageName,
+ proxiedAttributionTag, mContext.getAttributionSource().getToken())),
+ message, /*skipProxyOperation*/ false);
}
/**
@@ -8664,7 +8664,7 @@
public int noteProxyOpNoThrow(@NonNull String op, @Nullable String proxiedPackageName,
int proxiedUid, @Nullable String proxiedAttributionTag, @Nullable String message) {
return noteProxyOpNoThrow(strOpToOp(op), new AttributionSource(
- mContext.getAttributionSource(), new AttributionSource(proxiedUid,
+ mContext.getAttributionSource(), new AttributionSource(proxiedUid, /*pid*/ -1,
proxiedPackageName, proxiedAttributionTag, mContext.getAttributionSource()
.getToken())), message,/*skipProxyOperation*/ false);
}
@@ -9076,9 +9076,9 @@
public int startProxyOp(@NonNull String op, int proxiedUid, @NonNull String proxiedPackageName,
@Nullable String proxiedAttributionTag, @Nullable String message) {
return startProxyOp(op, new AttributionSource(mContext.getAttributionSource(),
- new AttributionSource(proxiedUid, proxiedPackageName, proxiedAttributionTag,
- mContext.getAttributionSource().getToken())), message,
- /*skipProxyOperation*/ false);
+ new AttributionSource(proxiedUid, /*pid*/ -1, proxiedPackageName,
+ proxiedAttributionTag, mContext.getAttributionSource().getToken())),
+ message, /*skipProxyOperation*/ false);
}
/**
@@ -9124,7 +9124,7 @@
@Nullable String message) {
return startProxyOpNoThrow(AppOpsManager.strOpToOp(op), new AttributionSource(
mContext.getAttributionSource(), new AttributionSource(proxiedUid,
- proxiedPackageName, proxiedAttributionTag,
+ /*pid*/ -1, proxiedPackageName, proxiedAttributionTag,
mContext.getAttributionSource().getToken())), message,
/*skipProxyOperation*/ false);
}
@@ -9270,8 +9270,9 @@
public void finishProxyOp(@NonNull String op, int proxiedUid,
@NonNull String proxiedPackageName, @Nullable String proxiedAttributionTag) {
finishProxyOp(op, new AttributionSource(mContext.getAttributionSource(),
- new AttributionSource(proxiedUid, proxiedPackageName, proxiedAttributionTag,
- mContext.getAttributionSource().getToken())), /*skipProxyOperation*/ false);
+ new AttributionSource(proxiedUid, /*pid*/ -1, proxiedPackageName,
+ proxiedAttributionTag, mContext.getAttributionSource().getToken())),
+ /*skipProxyOperation*/ false);
}
/**
diff --git a/core/java/android/app/BroadcastOptions.java b/core/java/android/app/BroadcastOptions.java
index f0e1448..aa5fa5b 100644
--- a/core/java/android/app/BroadcastOptions.java
+++ b/core/java/android/app/BroadcastOptions.java
@@ -528,6 +528,28 @@
return mIsAlarmBroadcast;
}
+ /**
+ * Did this broadcast originate from a push message from the server?
+ *
+ * @return true if this broadcast is a push message, false otherwise.
+ * @hide
+ */
+ public boolean isPushMessagingBroadcast() {
+ return mTemporaryAppAllowlistReasonCode == PowerExemptionManager.REASON_PUSH_MESSAGING;
+ }
+
+ /**
+ * Did this broadcast originate from a push message from the server which was over the allowed
+ * quota?
+ *
+ * @return true if this broadcast is a push message over quota, false otherwise.
+ * @hide
+ */
+ public boolean isPushMessagingOverQuotaBroadcast() {
+ return mTemporaryAppAllowlistReasonCode
+ == PowerExemptionManager.REASON_PUSH_MESSAGING_OVER_QUOTA;
+ }
+
/** {@hide} */
public long getRequireCompatChangeId() {
return mRequireCompatChangeId;
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 4c7bc6d..c1a2183 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -3227,7 +3227,9 @@
@Nullable AttributionSource nextAttributionSource,
@Nullable Set<String> renouncedPermissions) {
AttributionSource attributionSource = new AttributionSource(Process.myUid(),
- mOpPackageName, attributionTag, renouncedPermissions, nextAttributionSource);
+ Process.myPid(), mOpPackageName, attributionTag,
+ (renouncedPermissions != null) ? renouncedPermissions.toArray(new String[0]) : null,
+ nextAttributionSource);
// If we want to access protected data on behalf of another app we need to
// tell the OS that we opt in to participate in the attribution chain.
if (nextAttributionSource != null) {
diff --git a/core/java/android/content/AttributionSource.java b/core/java/android/content/AttributionSource.java
index 3f2fa21..272e235 100644
--- a/core/java/android/content/AttributionSource.java
+++ b/core/java/android/content/AttributionSource.java
@@ -100,22 +100,28 @@
@TestApi
public AttributionSource(int uid, @Nullable String packageName,
@Nullable String attributionTag) {
- this(uid, packageName, attributionTag, sDefaultToken);
+ this(uid, Process.INVALID_PID, packageName, attributionTag, sDefaultToken);
+ }
+
+ /** @hide */
+ public AttributionSource(int uid, int pid, @Nullable String packageName,
+ @Nullable String attributionTag) {
+ this(uid, pid, packageName, attributionTag, sDefaultToken);
}
/** @hide */
@TestApi
public AttributionSource(int uid, @Nullable String packageName,
@Nullable String attributionTag, @NonNull IBinder token) {
- this(uid, packageName, attributionTag, token, /*renouncedPermissions*/ null,
- /*next*/ null);
+ this(uid, Process.INVALID_PID, packageName, attributionTag, token,
+ /*renouncedPermissions*/ null, /*next*/ null);
}
/** @hide */
- public AttributionSource(int uid, @Nullable String packageName,
- @Nullable String attributionTag, @NonNull IBinder token,
- @Nullable AttributionSource next) {
- this(uid, packageName, attributionTag, token, /*renouncedPermissions*/ null, next);
+ public AttributionSource(int uid, int pid, @Nullable String packageName,
+ @Nullable String attributionTag, @NonNull IBinder token) {
+ this(uid, pid, packageName, attributionTag, token, /*renouncedPermissions*/ null,
+ /*next*/ null);
}
/** @hide */
@@ -123,26 +129,33 @@
public AttributionSource(int uid, @Nullable String packageName,
@Nullable String attributionTag, @Nullable Set<String> renouncedPermissions,
@Nullable AttributionSource next) {
- this(uid, packageName, attributionTag, (renouncedPermissions != null)
- ? renouncedPermissions.toArray(new String[0]) : null, next);
+ this(uid, Process.INVALID_PID, packageName, attributionTag, sDefaultToken,
+ (renouncedPermissions != null)
+ ? renouncedPermissions.toArray(new String[0]) : null, /*next*/ next);
}
/** @hide */
public AttributionSource(@NonNull AttributionSource current, @Nullable AttributionSource next) {
- this(current.getUid(), current.getPackageName(), current.getAttributionTag(),
- current.getToken(), current.mAttributionSourceState.renouncedPermissions, next);
+ this(current.getUid(), current.getPid(), current.getPackageName(),
+ current.getAttributionTag(), current.getToken(),
+ current.mAttributionSourceState.renouncedPermissions, next);
}
- AttributionSource(int uid, @Nullable String packageName, @Nullable String attributionTag,
- @Nullable String[] renouncedPermissions, @Nullable AttributionSource next) {
- this(uid, packageName, attributionTag, sDefaultToken, renouncedPermissions, next);
+ /** @hide */
+ public AttributionSource(int uid, int pid, @Nullable String packageName,
+ @Nullable String attributionTag, @Nullable String[] renouncedPermissions,
+ @Nullable AttributionSource next) {
+ this(uid, pid, packageName, attributionTag, sDefaultToken, renouncedPermissions, next);
}
- AttributionSource(int uid, @Nullable String packageName, @Nullable String attributionTag,
- @NonNull IBinder token, @Nullable String[] renouncedPermissions,
+ /** @hide */
+ public AttributionSource(int uid, int pid, @Nullable String packageName,
+ @Nullable String attributionTag, @NonNull IBinder token,
+ @Nullable String[] renouncedPermissions,
@Nullable AttributionSource next) {
mAttributionSourceState = new AttributionSourceState();
mAttributionSourceState.uid = uid;
+ mAttributionSourceState.pid = pid;
mAttributionSourceState.token = token;
mAttributionSourceState.packageName = packageName;
mAttributionSourceState.attributionTag = attributionTag;
@@ -156,7 +169,17 @@
// Since we just unpacked this object as part of it transiting a Binder
// call, this is the perfect time to enforce that its UID and PID can be trusted
- enforceCallingUidAndPid();
+ enforceCallingUid();
+
+ // If this object is being constructed as part of a oneway Binder call, getCallingPid will
+ // return 0 instead of the true PID. In that case, invalidate the PID by setting it to
+ // INVALID_PID (-1).
+ final int callingPid = Binder.getCallingPid();
+ if (callingPid == 0) {
+ mAttributionSourceState.pid = Process.INVALID_PID;
+ }
+
+ enforceCallingPid();
}
/** @hide */
@@ -166,19 +189,19 @@
/** @hide */
public AttributionSource withNextAttributionSource(@Nullable AttributionSource next) {
- return new AttributionSource(getUid(), getPackageName(), getAttributionTag(),
- mAttributionSourceState.renouncedPermissions, next);
+ return new AttributionSource(getUid(), getPid(), getPackageName(), getAttributionTag(),
+ getToken(), mAttributionSourceState.renouncedPermissions, next);
}
/** @hide */
public AttributionSource withPackageName(@Nullable String packageName) {
- return new AttributionSource(getUid(), packageName, getAttributionTag(),
- mAttributionSourceState.renouncedPermissions, getNext());
+ return new AttributionSource(getUid(), getPid(), packageName, getAttributionTag(),
+ getToken(), mAttributionSourceState.renouncedPermissions, getNext());
}
/** @hide */
public AttributionSource withToken(@NonNull Binder token) {
- return new AttributionSource(getUid(), getPackageName(), getAttributionTag(),
+ return new AttributionSource(getUid(), getPid(), getPackageName(), getAttributionTag(),
token, mAttributionSourceState.renouncedPermissions, getNext());
}
@@ -222,6 +245,7 @@
}
try {
return new AttributionSource.Builder(uid)
+ .setPid(Process.myPid())
.setPackageName(AppGlobals.getPackageManager().getPackagesForUid(uid)[0])
.build();
} catch (Exception ignored) {
@@ -259,18 +283,6 @@
}
/**
- * If you are handling an IPC and you don't trust the caller you need to validate whether the
- * attribution source is one for the calling app to prevent the caller to pass you a source from
- * another app without including themselves in the attribution chain.
- *
- * @throws SecurityException if the attribution source cannot be trusted to be from the caller.
- */
- private void enforceCallingUidAndPid() {
- enforceCallingUid();
- enforceCallingPid();
- }
-
- /**
* If you are handling an IPC and you don't trust the caller you need to validate
* whether the attribution source is one for the calling app to prevent the caller
* to pass you a source from another app without including themselves in the
@@ -306,7 +318,10 @@
}
/**
- * Validate that the pid being claimed for the calling app is not spoofed
+ * Validate that the pid being claimed for the calling app is not spoofed.
+ *
+ * Note that the PID may be unavailable, for example if we're in a oneway Binder call. In this
+ * case, calling enforceCallingPid is guaranteed to fail. The caller should anticipate this.
*
* @throws SecurityException if the attribution source cannot be trusted to be from the caller.
* @hide
@@ -314,8 +329,12 @@
@TestApi
public void enforceCallingPid() {
if (!checkCallingPid()) {
- throw new SecurityException("Calling pid: " + Binder.getCallingPid()
- + " doesn't match source pid: " + mAttributionSourceState.pid);
+ if (Binder.getCallingPid() == 0) {
+ throw new SecurityException("Calling pid unavailable due to oneway Binder call.");
+ } else {
+ throw new SecurityException("Calling pid: " + Binder.getCallingPid()
+ + " doesn't match source pid: " + mAttributionSourceState.pid);
+ }
}
}
@@ -326,7 +345,8 @@
*/
private boolean checkCallingPid() {
final int callingPid = Binder.getCallingPid();
- if (mAttributionSourceState.pid != -1 && callingPid != mAttributionSourceState.pid) {
+ if (mAttributionSourceState.pid != Process.INVALID_PID
+ && callingPid != mAttributionSourceState.pid) {
return false;
}
return true;
@@ -443,6 +463,13 @@
}
/**
+ * The PID that is accessing the permission protected data.
+ */
+ public int getPid() {
+ return mAttributionSourceState.pid;
+ }
+
+ /**
* The package that is accessing the permission protected data.
*/
public @Nullable String getPackageName() {
@@ -550,6 +577,7 @@
throw new IllegalArgumentException("current AttributionSource can not be null");
}
mAttributionSourceState.uid = current.getUid();
+ mAttributionSourceState.pid = current.getPid();
mAttributionSourceState.packageName = current.getPackageName();
mAttributionSourceState.attributionTag = current.getAttributionTag();
mAttributionSourceState.token = current.getToken();
@@ -558,11 +586,24 @@
}
/**
+ * The PID of the process that is accessing the permission protected data.
+ *
+ * If not called, pid will default to Process.INVALID_PID (-1). This indicates that the PID
+ * data is missing. Supplying a PID is not required, but recommended when accessible.
+ */
+ public @NonNull Builder setPid(int value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x2;
+ mAttributionSourceState.pid = value;
+ return this;
+ }
+
+ /**
* The package that is accessing the permission protected data.
*/
public @NonNull Builder setPackageName(@Nullable String value) {
checkNotUsed();
- mBuilderFieldsSet |= 0x2;
+ mBuilderFieldsSet |= 0x4;
mAttributionSourceState.packageName = value;
return this;
}
@@ -572,7 +613,7 @@
*/
public @NonNull Builder setAttributionTag(@Nullable String value) {
checkNotUsed();
- mBuilderFieldsSet |= 0x4;
+ mBuilderFieldsSet |= 0x8;
mAttributionSourceState.attributionTag = value;
return this;
}
@@ -605,7 +646,7 @@
@RequiresPermission(android.Manifest.permission.RENOUNCE_PERMISSIONS)
public @NonNull Builder setRenouncedPermissions(@Nullable Set<String> value) {
checkNotUsed();
- mBuilderFieldsSet |= 0x8;
+ mBuilderFieldsSet |= 0x10;
mAttributionSourceState.renouncedPermissions = (value != null)
? value.toArray(new String[0]) : null;
return this;
@@ -616,7 +657,7 @@
*/
public @NonNull Builder setNext(@Nullable AttributionSource value) {
checkNotUsed();
- mBuilderFieldsSet |= 0x10;
+ mBuilderFieldsSet |= 0x20;
mAttributionSourceState.next = (value != null) ? new AttributionSourceState[]
{value.mAttributionSourceState} : mAttributionSourceState.next;
return this;
@@ -628,15 +669,18 @@
mBuilderFieldsSet |= 0x40; // Mark builder used
if ((mBuilderFieldsSet & 0x2) == 0) {
- mAttributionSourceState.packageName = null;
+ mAttributionSourceState.pid = Process.INVALID_PID;
}
if ((mBuilderFieldsSet & 0x4) == 0) {
- mAttributionSourceState.attributionTag = null;
+ mAttributionSourceState.packageName = null;
}
if ((mBuilderFieldsSet & 0x8) == 0) {
- mAttributionSourceState.renouncedPermissions = null;
+ mAttributionSourceState.attributionTag = null;
}
if ((mBuilderFieldsSet & 0x10) == 0) {
+ mAttributionSourceState.renouncedPermissions = null;
+ }
+ if ((mBuilderFieldsSet & 0x20) == 0) {
mAttributionSourceState.next = null;
}
diff --git a/core/java/android/content/PermissionChecker.java b/core/java/android/content/PermissionChecker.java
index 8d3452e..0e3217d 100644
--- a/core/java/android/content/PermissionChecker.java
+++ b/core/java/android/content/PermissionChecker.java
@@ -152,7 +152,7 @@
@NonNull String permission, int pid, int uid, @Nullable String packageName,
@Nullable String attributionTag, @Nullable String message, boolean startDataDelivery) {
return checkPermissionForDataDelivery(context, permission, pid, new AttributionSource(uid,
- packageName, attributionTag), message, startDataDelivery);
+ pid, packageName, attributionTag), message, startDataDelivery);
}
/**
diff --git a/core/java/android/content/pm/OWNERS b/core/java/android/content/pm/OWNERS
index 087e61d..f9d3222 100644
--- a/core/java/android/content/pm/OWNERS
+++ b/core/java/android/content/pm/OWNERS
@@ -11,3 +11,4 @@
per-file AppSearchPerson.java = file:/core/java/android/content/pm/SHORTCUT_OWNERS
per-file *Launcher* = file:/core/java/android/content/pm/LAUNCHER_OWNERS
per-file UserInfo* = file:/MULTIUSER_OWNERS
+per-file *UserProperties* = file:/MULTIUSER_OWNERS
diff --git a/core/java/android/content/pm/UserProperties.aidl b/core/java/android/content/pm/UserProperties.aidl
new file mode 100644
index 0000000..4d37067
--- /dev/null
+++ b/core/java/android/content/pm/UserProperties.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2022 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.content.pm;
+
+parcelable UserProperties;
diff --git a/core/java/android/content/pm/UserProperties.java b/core/java/android/content/pm/UserProperties.java
new file mode 100644
index 0000000..1a82e4d
--- /dev/null
+++ b/core/java/android/content/pm/UserProperties.java
@@ -0,0 +1,356 @@
+/*
+ * Copyright (C) 2022 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.content.pm;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Slog;
+import android.util.TypedXmlPullParser;
+import android.util.TypedXmlSerializer;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Class holding the properties of a user that derive mostly from its user type.
+ */
+public final class UserProperties implements Parcelable {
+ private static final String LOG_TAG = UserProperties.class.getSimpleName();
+
+ // Attribute strings for reading/writing properties to/from XML.
+ private static final String ATTR_SHOW_IN_LAUNCHER = "showInLauncher";
+ private static final String ATTR_START_WITH_PARENT = "startWithParent";
+
+ /** Index values of each property (to indicate whether they are present in this object). */
+ @IntDef(prefix = "INDEX_", value = {
+ INDEX_SHOW_IN_LAUNCHER,
+ INDEX_START_WITH_PARENT,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ private @interface PropertyIndex {
+ }
+ private static final int INDEX_SHOW_IN_LAUNCHER = 0;
+ private static final int INDEX_START_WITH_PARENT = 1;
+ /** A bit set, mapping each PropertyIndex to whether it is present (1) or absent (0). */
+ private long mPropertiesPresent = 0;
+
+
+ /**
+ * Possible values for whether or how to show this user in the Launcher.
+ * @hide
+ */
+ @IntDef(prefix = "SHOW_IN_LAUNCHER_", value = {
+ SHOW_IN_LAUNCHER_WITH_PARENT,
+ SHOW_IN_LAUNCHER_SEPARATE,
+ SHOW_IN_LAUNCHER_NO,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ShowInLauncher {
+ }
+ /**
+ * Suggests that the launcher should show this user's apps in the main tab.
+ * That is, either this user is a full user, so its apps should be presented accordingly, or, if
+ * this user is a profile, then its apps should be shown alongside its parent's apps.
+ */
+ public static final int SHOW_IN_LAUNCHER_WITH_PARENT = 0;
+ /**
+ * Suggests that the launcher should show this user's apps, but separately from the apps of this
+ * user's parent.
+ */
+ public static final int SHOW_IN_LAUNCHER_SEPARATE = 1;
+ /**
+ * Suggests that the launcher should not show this user.
+ */
+ public static final int SHOW_IN_LAUNCHER_NO = 2;
+
+ /**
+ * Reference to the default user properties for this user's user type.
+ * <li>If non-null, then any absent property will use the default property from here instead.
+ * <li>If null, then any absent property indicates that the caller lacks permission to see it,
+ * so attempting to get that property will trigger a SecurityException.
+ */
+ private final @Nullable UserProperties mDefaultProperties;
+
+ /**
+ * Creates a UserProperties (intended for the SystemServer) that stores a reference to the given
+ * default properties, which it uses for any property not subsequently set.
+ * @hide
+ */
+ public UserProperties(@NonNull UserProperties defaultProperties) {
+ mDefaultProperties = defaultProperties;
+ mPropertiesPresent = 0;
+ }
+
+ /**
+ * Copies the given UserProperties, excluding any information that doesn't satisfy the specified
+ * permissions.
+ * Can only be used on the original version (one that won't throw on permission errors).
+ * Note that, internally, this does not perform an exact copy.
+ * @hide
+ */
+ public UserProperties(UserProperties orig,
+ boolean exposeAllFields,
+ boolean hasManagePermission,
+ boolean hasQueryPermission) {
+
+ if (orig.mDefaultProperties == null) {
+ throw new IllegalArgumentException("Attempting to copy a non-original UserProperties.");
+ }
+
+ this.mDefaultProperties = null;
+
+ // NOTE: Copy each property using getters to ensure default values are copied if needed.
+ if (exposeAllFields) {
+ setStartWithParent(orig.getStartWithParent());
+ }
+ if (hasManagePermission) {
+ // Add any items that require this permission.
+ }
+ if (hasQueryPermission) {
+ // Add any items that require this permission.
+ }
+ // Add any items that require no permissions at all.
+ setShowInLauncher(orig.getShowInLauncher());
+ }
+
+ /**
+ * Indicates that the given property is being stored explicitly in this object.
+ * If false, it means that either
+ * <li>the default property for the user type should be used instead (for SystemServer callers)
+ * <li>the caller lacks permission to see this property (for all other callers)
+ */
+ private boolean isPresent(@PropertyIndex long index) {
+ return (mPropertiesPresent & (1L << index)) != 0;
+ }
+
+ /** Indicates that the given property is henceforth being explicitly stored in this object. */
+ private void setPresent(@PropertyIndex long index) {
+ mPropertiesPresent |= (1L << index);
+ }
+
+ /** @hide Returns the internal mPropertiesPresent value. Only for testing purposes. */
+ @VisibleForTesting
+ public long getPropertiesPresent() {
+ return mPropertiesPresent;
+ }
+
+ /**
+ * Returns whether, and how, a user should be shown in the Launcher.
+ * This is generally inapplicable for non-profile users.
+ *
+ * Possible return values include
+ * {@link #SHOW_IN_LAUNCHER_WITH_PARENT}},
+ * {@link #SHOW_IN_LAUNCHER_SEPARATE},
+ * and {@link #SHOW_IN_LAUNCHER_NO}.
+ *
+ * @return whether, and how, a profile should be shown in the Launcher.
+ */
+ public @ShowInLauncher int getShowInLauncher() {
+ if (isPresent(INDEX_SHOW_IN_LAUNCHER)) return mShowInLauncher;
+ if (mDefaultProperties != null) return mDefaultProperties.mShowInLauncher;
+ throw new SecurityException("You don't have permission to query showInLauncher");
+ }
+ /** @hide */
+ public void setShowInLauncher(@ShowInLauncher int val) {
+ this.mShowInLauncher = val;
+ setPresent(INDEX_SHOW_IN_LAUNCHER);
+ }
+ private @ShowInLauncher int mShowInLauncher;
+
+ /**
+ * Returns whether a profile should be started when its parent starts (unless in quiet mode).
+ * This only applies for users that have parents (i.e. for profiles).
+ * @hide
+ */
+ public boolean getStartWithParent() {
+ if (isPresent(INDEX_START_WITH_PARENT)) return mStartWithParent;
+ if (mDefaultProperties != null) return mDefaultProperties.mStartWithParent;
+ throw new SecurityException("You don't have permission to query startWithParent");
+ }
+ /** @hide */
+ public void setStartWithParent(boolean val) {
+ this.mStartWithParent = val;
+ setPresent(INDEX_START_WITH_PARENT);
+ }
+ private boolean mStartWithParent;
+
+ @Override
+ public String toString() {
+ // Please print in increasing order of PropertyIndex.
+ return "UserProperties{"
+ + "mPropertiesPresent=" + Long.toBinaryString(mPropertiesPresent)
+ + ", mShowInLauncher=" + getShowInLauncher()
+ + ", mStartWithParent=" + getStartWithParent()
+ + "}";
+ }
+
+ /**
+ * Print the UserProperties to the given PrintWriter.
+ * @hide
+ */
+ public void println(PrintWriter pw, String prefix) {
+ // Please print in increasing order of PropertyIndex.
+ pw.println(prefix + "UserProperties:");
+ pw.println(prefix + " mPropertiesPresent=" + Long.toBinaryString(mPropertiesPresent));
+ pw.println(prefix + " mShowInLauncher=" + getShowInLauncher());
+ pw.println(prefix + " mStartWithParent=" + getStartWithParent());
+ }
+
+ /**
+ * Reads in a UserProperties from an xml file, for use by the SystemServer.
+ *
+ * The serializer should already be inside a tag from which to read the user properties.
+ *
+ * @param defaultUserPropertiesReference the default UserProperties to use for this user type.
+ * @see #writeToXml
+ * @hide
+ */
+ public UserProperties(
+ TypedXmlPullParser parser,
+ @NonNull UserProperties defaultUserPropertiesReference)
+ throws IOException, XmlPullParserException {
+
+ this(defaultUserPropertiesReference);
+ updateFromXml(parser);
+ }
+
+ /**
+ * Parses the given xml file and updates this UserProperties with its data.
+ * I.e., if a piece of data is present in the xml, it will overwrite whatever was
+ * previously stored in this UserProperties.
+ * @hide
+ */
+ public void updateFromXml(TypedXmlPullParser parser)
+ throws IOException, XmlPullParserException {
+
+ final int attributeCount = parser.getAttributeCount();
+ for (int i = 0; i < attributeCount; i++) {
+ final String attributeName = parser.getAttributeName(i);
+ switch(attributeName) {
+ case ATTR_SHOW_IN_LAUNCHER:
+ setShowInLauncher(parser.getAttributeInt(i));
+ break;
+ case ATTR_START_WITH_PARENT:
+ setStartWithParent(parser.getAttributeBoolean(i));
+ break;
+ default:
+ Slog.w(LOG_TAG, "Skipping unknown property " + attributeName);
+ }
+ }
+ }
+
+ /**
+ * Writes the UserProperties, as used by the SystemServer, to the xml file.
+ *
+ * The serializer should already be inside a tag in which to write the user properties.
+ *
+ * @see #UserProperties(TypedXmlPullParser, UserProperties)
+ * @hide
+ */
+ public void writeToXml(TypedXmlSerializer serializer)
+ throws IOException, XmlPullParserException {
+
+ if (isPresent(INDEX_SHOW_IN_LAUNCHER)) {
+ serializer.attributeInt(null, ATTR_SHOW_IN_LAUNCHER, mShowInLauncher);
+ }
+ if (isPresent(INDEX_START_WITH_PARENT)) {
+ serializer.attributeBoolean(null, ATTR_START_WITH_PARENT, mStartWithParent);
+ }
+ }
+
+ // For use only with an object that has already had any permission-lacking fields stripped out.
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int parcelableFlags) {
+ dest.writeLong(mPropertiesPresent);
+ dest.writeInt(mShowInLauncher);
+ dest.writeBoolean(mStartWithParent);
+ }
+
+ /**
+ * Reads a UserProperties object from the parcel.
+ * Not suitable for the canonical SystemServer version since it lacks mDefaultProperties.
+ */
+ private UserProperties(@NonNull Parcel source) {
+ mDefaultProperties = null;
+
+ mPropertiesPresent = source.readLong();
+ mShowInLauncher = source.readInt();
+ mStartWithParent = source.readBoolean();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ public static final @android.annotation.NonNull Parcelable.Creator<UserProperties> CREATOR
+ = new Parcelable.Creator<UserProperties>() {
+ public UserProperties createFromParcel(Parcel source) {
+ return new UserProperties(source);
+ }
+ public UserProperties[] newArray(int size) {
+ return new UserProperties[size];
+ }
+ };
+
+ /**
+ * Builder for the SystemServer's {@link UserProperties}; see that class for documentation.
+ * Intended for building default values (and so all properties are present in the built object).
+ * @hide
+ */
+ public static final class Builder {
+ // UserProperties fields and their default values.
+ private @ShowInLauncher int mShowInLauncher = SHOW_IN_LAUNCHER_WITH_PARENT;
+ private boolean mStartWithParent = false;
+
+ public Builder setShowInLauncher(@ShowInLauncher int showInLauncher) {
+ mShowInLauncher = showInLauncher;
+ return this;
+ }
+
+ public Builder setStartWithParent(boolean startWithParent) {
+ mStartWithParent = startWithParent;
+ return this;
+ }
+
+ /** Builds a UserProperties object with *all* values populated. */
+ public UserProperties build() {
+ return new UserProperties(
+ mShowInLauncher,
+ mStartWithParent);
+ }
+ } // end Builder
+
+ /** Creates a UserProperties with the given properties. Intended for building default values. */
+ private UserProperties(
+ @ShowInLauncher int showInLauncher,
+ boolean startWithParent) {
+
+ mDefaultProperties = null;
+ setShowInLauncher(showInLauncher);
+ setStartWithParent(startWithParent);
+ }
+}
diff --git a/core/java/android/hardware/HardwareBuffer.java b/core/java/android/hardware/HardwareBuffer.java
index 1e4c9501..11892fe 100644
--- a/core/java/android/hardware/HardwareBuffer.java
+++ b/core/java/android/hardware/HardwareBuffer.java
@@ -188,9 +188,6 @@
public static HardwareBuffer create(
@IntRange(from = 1) int width, @IntRange(from = 1) int height,
@Format int format, @IntRange(from = 1) int layers, @Usage long usage) {
- if (!HardwareBuffer.isSupportedFormat(format)) {
- throw new IllegalArgumentException("Invalid pixel format " + format);
- }
if (width <= 0) {
throw new IllegalArgumentException("Invalid width " + width);
}
@@ -226,9 +223,6 @@
*/
public static boolean isSupported(@IntRange(from = 1) int width, @IntRange(from = 1) int height,
@Format int format, @IntRange(from = 1) int layers, @Usage long usage) {
- if (!HardwareBuffer.isSupportedFormat(format)) {
- throw new IllegalArgumentException("Invalid pixel format " + format);
- }
if (width <= 0) {
throw new IllegalArgumentException("Invalid width " + width);
}
@@ -286,10 +280,7 @@
* Returns the width of this buffer in pixels.
*/
public int getWidth() {
- if (isClosed()) {
- throw new IllegalStateException("This HardwareBuffer has been closed and its width "
- + "cannot be obtained.");
- }
+ checkClosed("width");
return nGetWidth(mNativeObject);
}
@@ -297,10 +288,7 @@
* Returns the height of this buffer in pixels.
*/
public int getHeight() {
- if (isClosed()) {
- throw new IllegalStateException("This HardwareBuffer has been closed and its height "
- + "cannot be obtained.");
- }
+ checkClosed("height");
return nGetHeight(mNativeObject);
}
@@ -309,10 +297,7 @@
*/
@Format
public int getFormat() {
- if (isClosed()) {
- throw new IllegalStateException("This HardwareBuffer has been closed and its format "
- + "cannot be obtained.");
- }
+ checkClosed("format");
return nGetFormat(mNativeObject);
}
@@ -320,10 +305,7 @@
* Returns the number of layers in this buffer.
*/
public int getLayers() {
- if (isClosed()) {
- throw new IllegalStateException("This HardwareBuffer has been closed and its layer "
- + "count cannot be obtained.");
- }
+ checkClosed("layer count");
return nGetLayers(mNativeObject);
}
@@ -331,14 +313,27 @@
* Returns the usage flags of the usage hints set on this buffer.
*/
public long getUsage() {
- if (isClosed()) {
- throw new IllegalStateException("This HardwareBuffer has been closed and its usage "
- + "cannot be obtained.");
- }
+ checkClosed("usage");
return nGetUsage(mNativeObject);
}
/**
+ * Returns the system-wide unique id for this buffer
+ *
+ */
+ public long getId() {
+ checkClosed("id");
+ return nGetId(mNativeObject);
+ }
+
+ private void checkClosed(String name) {
+ if (isClosed()) {
+ throw new IllegalStateException("This HardwareBuffer has been closed and its "
+ + name + " cannot be obtained.");
+ }
+ }
+
+ /**
* Destroys this buffer immediately. Calling this method frees up any
* underlying native resources. After calling this method, this buffer
* must not be used in any way.
@@ -407,36 +402,6 @@
}
};
- /**
- * Validates whether a particular format is supported by HardwareBuffer.
- *
- * @param format The format to validate.
- *
- * @return True if <code>format</code> is a supported format. false otherwise.
- * See {@link #create(int, int, int, int, long)}.
- */
- private static boolean isSupportedFormat(@Format int format) {
- switch(format) {
- case RGBA_8888:
- case RGBA_FP16:
- case RGBA_1010102:
- case RGBX_8888:
- case RGB_565:
- case RGB_888:
- case BLOB:
- case YCBCR_420_888:
- case D_16:
- case D_24:
- case DS_24UI8:
- case D_FP32:
- case DS_FP32UI8:
- case S_UI8:
- case YCBCR_P010:
- return true;
- }
- return false;
- }
-
private static native long nCreateHardwareBuffer(int width, int height, int format, int layers,
long usage);
private static native long nCreateFromGraphicBuffer(GraphicBuffer graphicBuffer);
@@ -457,4 +422,6 @@
long usage);
@CriticalNative
private static native long nEstimateSize(long nativeObject);
+ @CriticalNative
+ private static native long nGetId(long nativeObject);
}
diff --git a/core/java/android/hardware/soundtrigger/SoundTrigger.java b/core/java/android/hardware/soundtrigger/SoundTrigger.java
index a9d665c8..621eab5 100644
--- a/core/java/android/hardware/soundtrigger/SoundTrigger.java
+++ b/core/java/android/hardware/soundtrigger/SoundTrigger.java
@@ -1963,7 +1963,6 @@
}
private static Object mServiceLock = new Object();
- private static ISoundTriggerMiddlewareService mService;
/**
* Translate an exception thrown from interaction with the underlying service to an error code.
@@ -2217,20 +2216,12 @@
binder =
ServiceManager.getServiceOrThrow(
Context.SOUND_TRIGGER_MIDDLEWARE_SERVICE);
- binder.linkToDeath(() -> {
- synchronized (mServiceLock) {
- mService = null;
- }
- }, 0);
- mService = ISoundTriggerMiddlewareService.Stub.asInterface(binder);
- break;
+ return ISoundTriggerMiddlewareService.Stub.asInterface(binder);
} catch (Exception e) {
Log.e(TAG, "Failed to bind to soundtrigger service", e);
}
}
- return mService;
}
-
}
/**
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index 9d05cec0..26600e2 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -2317,11 +2317,6 @@
public abstract void finishIteratingHistoryLocked();
/**
- * Return the base time offset for the battery history.
- */
- public abstract long getHistoryBaseTime();
-
- /**
* Returns the number of times the device has been started.
*/
public abstract int getStartCount();
@@ -7606,8 +7601,6 @@
CHECKIN_VERSION, getParcelVersion(), getStartPlatformVersion(),
getEndPlatformVersion());
- long now = getHistoryBaseTime() + SystemClock.elapsedRealtime();
-
if ((flags & (DUMP_INCLUDE_HISTORY | DUMP_HISTORY_ONLY)) != 0) {
if (startIteratingHistoryLocked()) {
try {
diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl
index f69d6b0..62ee408 100644
--- a/core/java/android/os/IUserManager.aidl
+++ b/core/java/android/os/IUserManager.aidl
@@ -23,6 +23,7 @@
import android.os.UserHandle;
import android.os.UserManager;
import android.content.pm.UserInfo;
+import android.content.pm.UserProperties;
import android.content.IntentSender;
import android.content.RestrictionEntry;
import android.graphics.Bitmap;
@@ -71,6 +72,7 @@
boolean isUserOfType(int userId, in String userType);
@UnsupportedAppUsage
UserInfo getUserInfo(int userId);
+ UserProperties getUserPropertiesCopy(int userId);
String getUserAccount(int userId);
void setUserAccount(int userId, String accountName);
long getUserCreationTime(int userId);
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index b16360c..095d53e 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -364,6 +364,11 @@
public static final int LAST_APPLICATION_CACHE_GID = 29999;
/**
+ * An invalid PID value.
+ */
+ public static final int INVALID_PID = -1;
+
+ /**
* Standard priority of application threads.
* Use with {@link #setThreadPriority(int)} and
* {@link #setThreadPriority(int, int)}, <b>not</b> with the normal
@@ -1463,18 +1468,6 @@
public static final native int killProcessGroup(int uid, int pid);
/**
- * Freeze the cgroup for the given UID.
- * This cgroup may contain child cgroups which will also be frozen. If this cgroup or its
- * children contain processes with Binder interfaces, those interfaces should be frozen before
- * the cgroup to avoid blocking synchronous callers indefinitely.
- *
- * @param uid The UID to be frozen
- * @param freeze true = freeze; false = unfreeze
- * @hide
- */
- public static final native void freezeCgroupUid(int uid, boolean freeze);
-
- /**
* Remove all process groups. Expected to be called when ActivityManager
* is restarted.
* @hide
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 99cf737..ef04f64 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -51,6 +51,7 @@
import android.content.IntentSender;
import android.content.pm.UserInfo;
import android.content.pm.UserInfo.UserInfoFlag;
+import android.content.pm.UserProperties;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
@@ -2786,7 +2787,7 @@
return isUserRunning(user.getIdentifier());
}
- /** {@hide} */
+ /** @hide */
@RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS,
Manifest.permission.INTERACT_ACROSS_USERS}, conditional = true)
public boolean isUserRunning(@UserIdInt int userId) {
@@ -2969,7 +2970,7 @@
}
};
- /** {@hide} */
+ /** @hide */
@UnsupportedAppUsage
@RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS,
Manifest.permission.INTERACT_ACROSS_USERS}, conditional = true)
@@ -2977,13 +2978,13 @@
return mIsUserUnlockedCache.query(userId);
}
- /** {@hide} */
+ /** @hide */
public void disableIsUserUnlockedCache() {
mIsUserUnlockedCache.disableLocal();
mIsUserUnlockingOrUnlockedCache.disableLocal();
}
- /** {@hide} */
+ /** @hide */
public static final void invalidateIsUserUnlockedCache() {
PropertyInvalidatedCache.invalidateCache(CACHE_KEY_IS_USER_UNLOCKED_PROPERTY);
}
@@ -3013,7 +3014,7 @@
return isUserUnlockingOrUnlocked(user.getIdentifier());
}
- /** {@hide} */
+ /** @hide */
@RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS,
Manifest.permission.INTERACT_ACROSS_USERS}, conditional = true)
public boolean isUserUnlockingOrUnlocked(@UserIdInt int userId) {
@@ -3084,6 +3085,30 @@
}
/**
+ * Returns a {@link UserProperties} object describing the properties of the given user.
+ *
+ * Note that the caller may not have permission to access all items; requesting any item for
+ * which permission is lacking will throw a {@link SecurityException}.
+ *
+ * <p> Requires
+ * {@code android.Manifest.permission#MANAGE_USERS},
+ * {@code android.Manifest.permission#QUERY_USERS}, or
+ * {@code android.Manifest.permission#INTERACT_ACROSS_USERS}
+ * permission, or else the caller must be in the same profile group as the caller.
+ *
+ * @param userHandle the user handle of the user whose information is being requested.
+ * @return a UserProperties object for a specific user.
+ * @throws IllegalArgumentException if {@code userHandle} doesn't correspond to an existing user
+ */
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.MANAGE_USERS,
+ android.Manifest.permission.QUERY_USERS,
+ android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional = true)
+ public @NonNull UserProperties getUserProperties(@NonNull UserHandle userHandle) {
+ return mUserPropertiesCache.query(userHandle.getIdentifier());
+ }
+
+ /**
* @hide
*
* Returns who set a user restriction on a user.
@@ -5481,11 +5506,37 @@
}
};
- /** {@hide} */
+ /** @hide */
public static final void invalidateStaticUserProperties() {
PropertyInvalidatedCache.invalidateCache(CACHE_KEY_STATIC_USER_PROPERTIES);
}
+ /* Cache key for UserProperties object. */
+ private static final String CACHE_KEY_USER_PROPERTIES = "cache_key.user_properties";
+
+ // TODO: It would be better to somehow have this as static, so that it can work cross-context.
+ private final PropertyInvalidatedCache<Integer, UserProperties> mUserPropertiesCache =
+ new PropertyInvalidatedCache<Integer, UserProperties>(16, CACHE_KEY_USER_PROPERTIES) {
+ @Override
+ public UserProperties recompute(Integer userId) {
+ try {
+ // If the userId doesn't exist, this will throw rather than cache garbage.
+ return mService.getUserPropertiesCopy(userId);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+ @Override
+ public boolean bypass(Integer query) {
+ return query < 0;
+ }
+ };
+
+ /** @hide */
+ public static final void invalidateUserPropertiesCache() {
+ PropertyInvalidatedCache.invalidateCache(CACHE_KEY_USER_PROPERTIES);
+ }
+
/**
* @hide
* User that enforces a restriction.
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index baf3eeca..e67b67d 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -10841,6 +10841,23 @@
"accessibility_software_cursor_enabled";
/**
+ * Software Cursor settings that specifies whether trigger hints are enabled.
+ *
+ * @hide
+ */
+ public static final String ACCESSIBILITY_SOFTWARE_CURSOR_TRIGGER_HINTS_ENABLED =
+ "accessibility_software_cursor_trigger_hints_enabled";
+
+ /**
+ * Software Cursor settings that specifies whether triggers are shifted when the keyboard
+ * is shown.
+ *
+ * @hide
+ */
+ public static final String ACCESSIBILITY_SOFTWARE_CURSOR_KEYBOARD_SHIFT_ENABLED =
+ "accessibility_software_cursor_keyboard_shift_enabled";
+
+ /**
* Whether the Adaptive connectivity option is enabled.
*
* @hide
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index a2cb1d5..0e3bcd1 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -314,6 +314,9 @@
(int) (startValue.right + fraction * (endValue.right - startValue.right)),
(int) (startValue.bottom + fraction * (endValue.bottom - startValue.bottom)));
+ /** Logging listener. */
+ private WindowInsetsAnimationControlListener mLoggingListener;
+
/**
* The default implementation of listener, to be used by InsetsController and InsetsPolicy to
* animate insets.
@@ -330,6 +333,7 @@
private final long mDurationMs;
private final boolean mDisable;
private final int mFloatingImeBottomInset;
+ private final WindowInsetsAnimationControlListener mLoggingListener;
private final ThreadLocal<AnimationHandler> mSfAnimationHandlerThreadLocal =
new ThreadLocal<AnimationHandler>() {
@@ -343,7 +347,7 @@
public InternalAnimationControlListener(boolean show, boolean hasAnimationCallbacks,
@InsetsType int requestedTypes, @Behavior int behavior, boolean disable,
- int floatingImeBottomInset) {
+ int floatingImeBottomInset, WindowInsetsAnimationControlListener loggingListener) {
mShow = show;
mHasAnimationCallbacks = hasAnimationCallbacks;
mRequestedTypes = requestedTypes;
@@ -351,12 +355,16 @@
mDurationMs = calculateDurationMs();
mDisable = disable;
mFloatingImeBottomInset = floatingImeBottomInset;
+ mLoggingListener = loggingListener;
}
@Override
public void onReady(WindowInsetsAnimationController controller, int types) {
mController = controller;
if (DEBUG) Log.d(TAG, "default animation onReady types: " + types);
+ if (mLoggingListener != null) {
+ mLoggingListener.onReady(controller, types);
+ }
if (mDisable) {
onAnimationFinish();
@@ -410,6 +418,9 @@
public void onFinished(WindowInsetsAnimationController controller) {
if (DEBUG) Log.d(TAG, "InternalAnimationControlListener onFinished types:"
+ Type.toString(mRequestedTypes));
+ if (mLoggingListener != null) {
+ mLoggingListener.onFinished(controller);
+ }
}
@Override
@@ -420,6 +431,9 @@
}
if (DEBUG) Log.d(TAG, "InternalAnimationControlListener onCancelled types:"
+ mRequestedTypes);
+ if (mLoggingListener != null) {
+ mLoggingListener.onCancelled(controller);
+ }
}
protected Interpolator getInsetsInterpolator() {
@@ -1147,6 +1161,13 @@
updateRequestedVisibilities();
}
+ // TODO(b/242962223): Make this setter restrictive.
+ @Override
+ public void setSystemDrivenInsetsAnimationLoggingListener(
+ @Nullable WindowInsetsAnimationControlListener listener) {
+ mLoggingListener = listener;
+ }
+
/**
* @return Pair of (types ready to animate, IME ready to animate).
*/
@@ -1460,7 +1481,8 @@
boolean hasAnimationCallbacks = mHost.hasAnimationCallbacks();
final InternalAnimationControlListener listener = new InternalAnimationControlListener(
show, hasAnimationCallbacks, types, mHost.getSystemBarsBehavior(),
- skipAnim || mAnimationsDisabled, mHost.dipToPx(FLOATING_IME_BOTTOM_INSET_DP));
+ skipAnim || mAnimationsDisabled, mHost.dipToPx(FLOATING_IME_BOTTOM_INSET_DP),
+ mLoggingListener);
// We are about to playing the default animation (show/hide). Passing a null frame indicates
// the controlled types should be animated regardless of the frame.
diff --git a/core/java/android/view/PendingInsetsController.java b/core/java/android/view/PendingInsetsController.java
index c61baf6..3fe9110 100644
--- a/core/java/android/view/PendingInsetsController.java
+++ b/core/java/android/view/PendingInsetsController.java
@@ -44,6 +44,7 @@
private ArrayList<OnControllableInsetsChangedListener> mControllableInsetsChangedListeners
= new ArrayList<>();
private int mCaptionInsetsHeight = 0;
+ private WindowInsetsAnimationControlListener mLoggingListener;
@Override
public void show(int types) {
@@ -176,6 +177,9 @@
controller.addOnControllableInsetsChangedListener(
mControllableInsetsChangedListeners.get(i));
}
+ if (mLoggingListener != null) {
+ controller.setSystemDrivenInsetsAnimationLoggingListener(mLoggingListener);
+ }
// Reset all state so it doesn't get applied twice just in case
mRequests.clear();
@@ -184,7 +188,7 @@
mAppearance = 0;
mAppearanceMask = 0;
mAnimationsDisabled = false;
-
+ mLoggingListener = null;
// After replaying, we forward everything directly to the replayed instance.
mReplayedInsetsController = controller;
}
@@ -198,6 +202,16 @@
}
@Override
+ public void setSystemDrivenInsetsAnimationLoggingListener(
+ @Nullable WindowInsetsAnimationControlListener listener) {
+ if (mReplayedInsetsController != null) {
+ mReplayedInsetsController.setSystemDrivenInsetsAnimationLoggingListener(listener);
+ } else {
+ mLoggingListener = listener;
+ }
+ }
+
+ @Override
public void controlWindowInsetsAnimation(@InsetsType int types, long durationMillis,
@Nullable Interpolator interpolator,
CancellationSignal cancellationSignal,
diff --git a/core/java/android/view/SurfaceControlViewHost.java b/core/java/android/view/SurfaceControlViewHost.java
index 5721fa6..3acb053 100644
--- a/core/java/android/view/SurfaceControlViewHost.java
+++ b/core/java/android/view/SurfaceControlViewHost.java
@@ -28,8 +28,8 @@
import android.os.Parcelable;
import android.os.RemoteException;
import android.util.Log;
-import android.window.WindowTokenClient;
import android.view.accessibility.IAccessibilityEmbeddedConnection;
+import android.window.WindowTokenClient;
import java.util.Objects;
@@ -271,14 +271,8 @@
/** @hide */
public SurfaceControlViewHost(@NonNull Context c, @NonNull Display d,
@NonNull WindowlessWindowManager wwm) {
- this(c, d, wwm, false /* useSfChoreographer */);
- }
-
- /** @hide */
- public SurfaceControlViewHost(@NonNull Context c, @NonNull Display d,
- @NonNull WindowlessWindowManager wwm, boolean useSfChoreographer) {
mWm = wwm;
- mViewRoot = new ViewRootImpl(c, d, mWm, new WindowlessWindowLayout(), useSfChoreographer);
+ mViewRoot = new ViewRootImpl(c, d, mWm, new WindowlessWindowLayout());
addConfigCallback(c, d);
WindowManagerGlobal.getInstance().addWindowlessRoot(mViewRoot);
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 674f0a2..9091b79 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -910,17 +910,11 @@
private String mTag = TAG;
public ViewRootImpl(Context context, Display display) {
- this(context, display, WindowManagerGlobal.getWindowSession(), new WindowLayout(),
- false /* useSfChoreographer */);
+ this(context, display, WindowManagerGlobal.getWindowSession(), new WindowLayout());
}
public ViewRootImpl(@UiContext Context context, Display display, IWindowSession session,
WindowLayout windowLayout) {
- this(context, display, session, windowLayout, false /* useSfChoreographer */);
- }
-
- public ViewRootImpl(@UiContext Context context, Display display, IWindowSession session,
- WindowLayout windowLayout, boolean useSfChoreographer) {
mContext = context;
mWindowSession = session;
mWindowLayout = windowLayout;
@@ -952,8 +946,7 @@
mNoncompatDensity = context.getResources().getDisplayMetrics().noncompatDensityDpi;
mFallbackEventHandler = new PhoneFallbackEventHandler(context);
// TODO(b/222696368): remove getSfInstance usage and use vsyncId for transactions
- mChoreographer = useSfChoreographer
- ? Choreographer.getSfInstance() : Choreographer.getInstance();
+ mChoreographer = Choreographer.getInstance();
mDisplayManager = (DisplayManager)context.getSystemService(Context.DISPLAY_SERVICE);
mInsetsController = new InsetsController(new ViewRootInsetsControllerHost(this));
mHandwritingInitiator = new HandwritingInitiator(
diff --git a/core/java/android/view/WindowInsetsController.java b/core/java/android/view/WindowInsetsController.java
index 227b9f4..63f9e13 100644
--- a/core/java/android/view/WindowInsetsController.java
+++ b/core/java/android/view/WindowInsetsController.java
@@ -201,6 +201,21 @@
@NonNull WindowInsetsAnimationControlListener listener);
/**
+ * Lets the application add non-controllable listener object that can be called back
+ * when animation is invoked by the system by host calling methods such as {@link #show} or
+ * {@link #hide}.
+ *
+ * The listener is supposed to be used for logging only, using the control or
+ * relying on the timing of the callback in any other way is not supported.
+ *
+ * @param listener The {@link WindowInsetsAnimationControlListener} that gets called when
+ * the animation is driven by the system and not the host
+ * @hide
+ */
+ void setSystemDrivenInsetsAnimationLoggingListener(
+ @Nullable WindowInsetsAnimationControlListener listener);
+
+ /**
* Controls the appearance of system bars.
* <p>
* For example, the following statement adds {@link #APPEARANCE_LIGHT_STATUS_BARS}:
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index fe8d64f..a49caaf 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -1054,6 +1054,14 @@
}
}
+ /**
+ * Ensure scales are between 0 and 20.
+ * @hide
+ */
+ static float fixScale(float scale) {
+ return Math.max(Math.min(scale, 20), 0);
+ }
+
public static class LayoutParams extends ViewGroup.LayoutParams implements Parcelable {
/**
* X position for this window. With the default gravity it is ignored.
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 8d3cf6d..6049613 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -91,6 +91,7 @@
import android.view.KeyEvent;
import android.view.View;
import android.view.ViewRootImpl;
+import android.view.WindowInsets;
import android.view.WindowManager.LayoutParams.SoftInputModeFlags;
import android.view.autofill.AutofillId;
import android.view.autofill.AutofillManager;
@@ -2150,8 +2151,9 @@
null /* icProto */);
synchronized (mH) {
final View view = getServedViewLocked();
- if (mImeInsetsConsumer != null && view != null) {
- if (mImeInsetsConsumer.isRequestedVisible()) {
+ if (view != null) {
+ final WindowInsets rootInsets = view.getRootWindowInsets();
+ if (rootInsets != null && rootInsets.isVisible(WindowInsets.Type.ime())) {
hideSoftInputFromWindow(view.getWindowToken(), hideFlags, null,
SoftInputShowHideReason.HIDE_TOGGLE_SOFT_INPUT);
} else {
diff --git a/core/java/android/window/TaskFragmentOrganizer.java b/core/java/android/window/TaskFragmentOrganizer.java
index e567ced..49849a4 100644
--- a/core/java/android/window/TaskFragmentOrganizer.java
+++ b/core/java/android/window/TaskFragmentOrganizer.java
@@ -16,7 +16,7 @@
package android.window;
-import static android.window.TaskFragmentTransaction.TYPE_ACTIVITY_REPARENT_TO_TASK;
+import static android.window.TaskFragmentTransaction.TYPE_ACTIVITY_REPARENTED_TO_TASK;
import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_APPEARED;
import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_ERROR;
import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_INFO_CHANGED;
@@ -147,13 +147,25 @@
}
}
- /** Called when a TaskFragment is created and organized by this organizer. */
+ /**
+ * Called when a TaskFragment is created and organized by this organizer.
+ *
+ * @param taskFragmentInfo Info of the TaskFragment that is created.
+ */
public void onTaskFragmentAppeared(@NonNull TaskFragmentInfo taskFragmentInfo) {}
- /** Called when the status of an organized TaskFragment is changed. */
+ /**
+ * Called when the status of an organized TaskFragment is changed.
+ *
+ * @param taskFragmentInfo Info of the TaskFragment that is changed.
+ */
public void onTaskFragmentInfoChanged(@NonNull TaskFragmentInfo taskFragmentInfo) {}
- /** Called when an organized TaskFragment is removed. */
+ /**
+ * Called when an organized TaskFragment is removed.
+ *
+ * @param taskFragmentInfo Info of the TaskFragment that is removed.
+ */
public void onTaskFragmentVanished(@NonNull TaskFragmentInfo taskFragmentInfo) {}
/**
@@ -176,6 +188,9 @@
* For case like screen size change, it will trigger onTaskFragmentParentInfoChanged with new
* Task bounds, but may not trigger onTaskFragmentInfoChanged because there can be an override
* bounds.
+ *
+ * @param taskId Id of the parent Task that is changed.
+ * @param parentConfig Config of the parent Task.
* @hide
*/
public void onTaskFragmentParentInfoChanged(int taskId, @NonNull Configuration parentConfig) {
@@ -208,7 +223,7 @@
/**
* Called when an Activity is reparented to the Task with organized TaskFragment. For example,
* when an Activity enters and then exits Picture-in-picture, it will be reparented back to its
- * orginial Task. In this case, we need to notify the organizer so that it can check if the
+ * original Task. In this case, we need to notify the organizer so that it can check if the
* Activity matches any split rule.
*
* @param taskId The Task that the activity is reparented to.
@@ -220,7 +235,7 @@
* {@link WindowContainerTransaction} if needed.
* @hide
*/
- public void onActivityReparentToTask(int taskId, @NonNull Intent activityIntent,
+ public void onActivityReparentedToTask(int taskId, @NonNull Intent activityIntent,
@NonNull IBinder activityToken) {}
/**
@@ -280,8 +295,8 @@
errorBundle.getSerializable(KEY_ERROR_CALLBACK_EXCEPTION,
java.lang.Throwable.class));
break;
- case TYPE_ACTIVITY_REPARENT_TO_TASK:
- onActivityReparentToTask(
+ case TYPE_ACTIVITY_REPARENTED_TO_TASK:
+ onActivityReparentedToTask(
change.getTaskId(),
change.getActivityIntent(),
change.getActivityToken());
diff --git a/core/java/android/window/TaskFragmentTransaction.java b/core/java/android/window/TaskFragmentTransaction.java
index 755864f..07e8e8c 100644
--- a/core/java/android/window/TaskFragmentTransaction.java
+++ b/core/java/android/window/TaskFragmentTransaction.java
@@ -122,7 +122,7 @@
* then exits Picture-in-picture, it will be reparented back to its original Task. In this case,
* we need to notify the organizer so that it can check if the Activity matches any split rule.
*/
- public static final int TYPE_ACTIVITY_REPARENT_TO_TASK = 6;
+ public static final int TYPE_ACTIVITY_REPARENTED_TO_TASK = 6;
@IntDef(prefix = { "TYPE_" }, value = {
TYPE_TASK_FRAGMENT_APPEARED,
@@ -130,7 +130,7 @@
TYPE_TASK_FRAGMENT_VANISHED,
TYPE_TASK_FRAGMENT_PARENT_INFO_CHANGED,
TYPE_TASK_FRAGMENT_ERROR,
- TYPE_ACTIVITY_REPARENT_TO_TASK
+ TYPE_ACTIVITY_REPARENTED_TO_TASK
})
@Retention(RetentionPolicy.SOURCE)
@interface ChangeType {}
@@ -247,7 +247,7 @@
/**
* Intent of the activity that is reparented to the Task for
- * {@link #TYPE_ACTIVITY_REPARENT_TO_TASK}.
+ * {@link #TYPE_ACTIVITY_REPARENTED_TO_TASK}.
*/
public Change setActivityIntent(@NonNull Intent intent) {
mActivityIntent = requireNonNull(intent);
@@ -255,7 +255,7 @@
}
/**
- * Token of the reparent activity for {@link #TYPE_ACTIVITY_REPARENT_TO_TASK}.
+ * Token of the reparent activity for {@link #TYPE_ACTIVITY_REPARENTED_TO_TASK}.
* If the activity belongs to the same process as the organizer, this will be the actual
* activity token; if the activity belongs to a different process, the server will generate
* a temporary token that the organizer can use to reparent the activity through
diff --git a/core/java/com/android/internal/os/BatteryStatsHistory.java b/core/java/com/android/internal/os/BatteryStatsHistory.java
index 962870e..6909965 100644
--- a/core/java/com/android/internal/os/BatteryStatsHistory.java
+++ b/core/java/com/android/internal/os/BatteryStatsHistory.java
@@ -17,25 +17,35 @@
package com.android.internal.os;
import android.annotation.Nullable;
-import android.os.BatteryStats;
+import android.os.BatteryManager;
+import android.os.BatteryStats.HistoryItem;
+import android.os.BatteryStats.HistoryStepDetails;
+import android.os.BatteryStats.HistoryTag;
import android.os.Parcel;
+import android.os.ParcelFormatException;
+import android.os.Process;
import android.os.StatFs;
import android.os.SystemClock;
import android.util.ArraySet;
import android.util.AtomicFile;
import android.util.Slog;
+import android.util.SparseArray;
+import android.util.TimeUtils;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ParseUtils;
import java.io.File;
+import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
import java.util.Set;
-import java.util.function.Supplier;
+import java.util.concurrent.locks.ReentrantLock;
/**
* BatteryStatsHistory encapsulates battery history files.
@@ -56,57 +66,62 @@
* All interfaces in BatteryStatsHistory should only be called by BatteryStatsImpl and protected by
* locks on BatteryStatsImpl object.
*/
-@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
public class BatteryStatsHistory {
private static final boolean DEBUG = false;
private static final String TAG = "BatteryStatsHistory";
// Current on-disk Parcel version. Must be updated when the format of the parcelable changes
- public static final int VERSION = 208;
+ private static final int VERSION = 208;
- public static final String HISTORY_DIR = "battery-history";
- public static final String FILE_SUFFIX = ".bin";
+ private static final String HISTORY_DIR = "battery-history";
+ private static final String FILE_SUFFIX = ".bin";
private static final int MIN_FREE_SPACE = 100 * 1024 * 1024;
+
// Part of initial delta int that specifies the time delta.
- public static final int DELTA_TIME_MASK = 0x7ffff;
- public static final int DELTA_TIME_LONG = 0x7ffff; // The delta is a following long
- public static final int DELTA_TIME_INT = 0x7fffe; // The delta is a following int
- public static final int DELTA_TIME_ABS = 0x7fffd; // Following is an entire abs update.
+ static final int DELTA_TIME_MASK = 0x7ffff;
+ static final int DELTA_TIME_LONG = 0x7ffff; // The delta is a following long
+ static final int DELTA_TIME_INT = 0x7fffe; // The delta is a following int
+ static final int DELTA_TIME_ABS = 0x7fffd; // Following is an entire abs update.
// Flag in delta int: a new battery level int follows.
- public static final int DELTA_BATTERY_LEVEL_FLAG = 0x00080000;
+ static final int DELTA_BATTERY_LEVEL_FLAG = 0x00080000;
// Flag in delta int: a new full state and battery status int follows.
- public static final int DELTA_STATE_FLAG = 0x00100000;
+ static final int DELTA_STATE_FLAG = 0x00100000;
// Flag in delta int: a new full state2 int follows.
- public static final int DELTA_STATE2_FLAG = 0x00200000;
+ static final int DELTA_STATE2_FLAG = 0x00200000;
// Flag in delta int: contains a wakelock or wakeReason tag.
- public static final int DELTA_WAKELOCK_FLAG = 0x00400000;
+ static final int DELTA_WAKELOCK_FLAG = 0x00400000;
// Flag in delta int: contains an event description.
- public static final int DELTA_EVENT_FLAG = 0x00800000;
+ static final int DELTA_EVENT_FLAG = 0x00800000;
// Flag in delta int: contains the battery charge count in uAh.
- public static final int DELTA_BATTERY_CHARGE_FLAG = 0x01000000;
+ static final int DELTA_BATTERY_CHARGE_FLAG = 0x01000000;
// These upper bits are the frequently changing state bits.
- public static final int DELTA_STATE_MASK = 0xfe000000;
+ static final int DELTA_STATE_MASK = 0xfe000000;
// These are the pieces of battery state that are packed in to the upper bits of
// the state int that have been packed in to the first delta int. They must fit
// in STATE_BATTERY_MASK.
- public static final int STATE_BATTERY_MASK = 0xff000000;
- public static final int STATE_BATTERY_STATUS_MASK = 0x00000007;
- public static final int STATE_BATTERY_STATUS_SHIFT = 29;
- public static final int STATE_BATTERY_HEALTH_MASK = 0x00000007;
- public static final int STATE_BATTERY_HEALTH_SHIFT = 26;
- public static final int STATE_BATTERY_PLUG_MASK = 0x00000003;
- public static final int STATE_BATTERY_PLUG_SHIFT = 24;
+ static final int STATE_BATTERY_MASK = 0xff000000;
+ static final int STATE_BATTERY_STATUS_MASK = 0x00000007;
+ static final int STATE_BATTERY_STATUS_SHIFT = 29;
+ static final int STATE_BATTERY_HEALTH_MASK = 0x00000007;
+ static final int STATE_BATTERY_HEALTH_SHIFT = 26;
+ static final int STATE_BATTERY_PLUG_MASK = 0x00000003;
+ static final int STATE_BATTERY_PLUG_SHIFT = 24;
// We use the low bit of the battery state int to indicate that we have full details
// from a battery level change.
- public static final int BATTERY_DELTA_LEVEL_FLAG = 0x00000001;
+ static final int BATTERY_DELTA_LEVEL_FLAG = 0x00000001;
// Flag in history tag index: indicates that this is the first occurrence of this tag,
// therefore the tag value is written in the parcel
- public static final int TAG_FIRST_OCCURRENCE_FLAG = 0x8000;
+ static final int TAG_FIRST_OCCURRENCE_FLAG = 0x8000;
- @Nullable
- private final Supplier<Integer> mMaxHistoryFiles;
private final Parcel mHistoryBuffer;
+ private final File mSystemDir;
+ private final HistoryStepDetailsCalculator mStepDetailsCalculator;
private final File mHistoryDir;
+ private final Clock mClock;
+
+ private int mMaxHistoryFiles;
+ private int mMaxHistoryBufferSize;
+
/**
* The active history file that the history buffer is backed up into.
*/
@@ -144,19 +159,77 @@
*/
private int mParcelIndex = 0;
+ private final ReentrantLock mWriteLock = new ReentrantLock();
+
+ private final HistoryItem mHistoryCur = new HistoryItem();
+
+ private boolean mHaveBatteryLevel;
+ private boolean mRecordingHistory;
+
+ private static final int HISTORY_TAG_INDEX_LIMIT = 0x7ffe;
+ private static final int MAX_HISTORY_TAG_STRING_LENGTH = 1024;
+
+ private final HashMap<HistoryTag, Integer> mHistoryTagPool = new HashMap<>();
+ private SparseArray<HistoryTag> mHistoryTags;
+ private final HistoryItem mHistoryLastWritten = new HistoryItem();
+ private final HistoryItem mHistoryLastLastWritten = new HistoryItem();
+ private final HistoryItem mHistoryAddTmp = new HistoryItem();
+ private int mNextHistoryTagIdx = 0;
+ private int mNumHistoryTagChars = 0;
+ private int mHistoryBufferLastPos = -1;
+ private int mActiveHistoryStates = 0xffffffff;
+ private int mActiveHistoryStates2 = 0xffffffff;
+ private long mLastHistoryElapsedRealtimeMs = 0;
+ private long mTrackRunningHistoryElapsedRealtimeMs = 0;
+ private long mTrackRunningHistoryUptimeMs = 0;
+ private long mHistoryBaseTimeMs;
+
+ private byte mLastHistoryStepLevel = 0;
+
+ private BatteryStatsHistoryIterator mBatteryStatsHistoryIterator;
+
+ /**
+ * A delegate responsible for computing additional details for a step in battery history.
+ */
+ public interface HistoryStepDetailsCalculator {
+ /**
+ * Returns additional details for the current history step or null.
+ */
+ @Nullable
+ HistoryStepDetails getHistoryStepDetails();
+
+ /**
+ * Resets the calculator to get ready for a new battery session
+ */
+ void clear();
+ }
+
/**
* Constructor
*
- * @param historyBuffer The in-memory history buffer.
- * @param systemDir typically /data/system
- * @param maxHistoryFiles the largest number of history buffer files to keep
+ * @param systemDir typically /data/system
+ * @param maxHistoryFiles the largest number of history buffer files to keep
+ * @param maxHistoryBufferSize the most amount of RAM to used for buffering of history steps
*/
- public BatteryStatsHistory(Parcel historyBuffer, File systemDir,
- Supplier<Integer> maxHistoryFiles) {
- mHistoryBuffer = historyBuffer;
- mHistoryDir = new File(systemDir, HISTORY_DIR);
- mMaxHistoryFiles = maxHistoryFiles;
+ public BatteryStatsHistory(File systemDir, int maxHistoryFiles, int maxHistoryBufferSize,
+ HistoryStepDetailsCalculator stepDetailsCalculator, Clock clock) {
+ this(Parcel.obtain(), systemDir, maxHistoryFiles, maxHistoryBufferSize,
+ stepDetailsCalculator, clock);
+ initHistoryBuffer();
+ }
+ @VisibleForTesting
+ public BatteryStatsHistory(Parcel historyBuffer, File systemDir,
+ int maxHistoryFiles, int maxHistoryBufferSize,
+ HistoryStepDetailsCalculator stepDetailsCalculator, Clock clock) {
+ mHistoryBuffer = historyBuffer;
+ mSystemDir = systemDir;
+ mMaxHistoryFiles = maxHistoryFiles;
+ mMaxHistoryBufferSize = maxHistoryBufferSize;
+ mStepDetailsCalculator = stepDetailsCalculator;
+ mClock = clock;
+
+ mHistoryDir = new File(systemDir, HISTORY_DIR);
mHistoryDir.mkdirs();
if (!mHistoryDir.exists()) {
Slog.wtf(TAG, "HistoryDir does not exist:" + mHistoryDir.getPath());
@@ -192,19 +265,81 @@
}
}
- /**
- * Used when BatteryStatsImpl object is created from deserialization of a parcel,
- * such as Settings app or checkin file.
- * @param historyBuffer the history buffer
- */
- public BatteryStatsHistory(Parcel historyBuffer) {
+ public BatteryStatsHistory(HistoryStepDetailsCalculator stepDetailsCalculator, Clock clock) {
+ mStepDetailsCalculator = stepDetailsCalculator;
+ mClock = clock;
+
+ mHistoryBuffer = Parcel.obtain();
+ mSystemDir = null;
mHistoryDir = null;
- mHistoryBuffer = historyBuffer;
- mMaxHistoryFiles = null;
+ initHistoryBuffer();
}
- public File getHistoryDirectory() {
- return mHistoryDir;
+ /**
+ * Used when BatteryStatsImpl object is created from deserialization of a parcel,
+ * such as a checkin file.
+ */
+ private BatteryStatsHistory(Parcel historyBuffer,
+ HistoryStepDetailsCalculator stepDetailsCalculator, Clock clock) {
+ mHistoryBuffer = historyBuffer;
+ mClock = clock;
+ mSystemDir = null;
+ mHistoryDir = null;
+ mStepDetailsCalculator = stepDetailsCalculator;
+ }
+
+ private void initHistoryBuffer() {
+ mHistoryBaseTimeMs = 0;
+ mLastHistoryElapsedRealtimeMs = 0;
+ mTrackRunningHistoryElapsedRealtimeMs = 0;
+ mTrackRunningHistoryUptimeMs = 0;
+
+ mHistoryBuffer.setDataSize(0);
+ mHistoryBuffer.setDataPosition(0);
+ mHistoryBuffer.setDataCapacity(mMaxHistoryBufferSize / 2);
+ mHistoryLastLastWritten.clear();
+ mHistoryLastWritten.clear();
+ mHistoryTagPool.clear();
+ mNextHistoryTagIdx = 0;
+ mNumHistoryTagChars = 0;
+ mHistoryBufferLastPos = -1;
+ mActiveHistoryStates = 0xffffffff;
+ mActiveHistoryStates2 = 0xffffffff;
+ if (mStepDetailsCalculator != null) {
+ mStepDetailsCalculator.clear();
+ }
+ }
+
+ /**
+ * Changes the maximum number of history files to be kept.
+ */
+ public void setMaxHistoryFiles(int maxHistoryFiles) {
+ mMaxHistoryFiles = maxHistoryFiles;
+ }
+
+ /**
+ * Changes the maximum size of the history buffer, in bytes.
+ */
+ public void setMaxHistoryBufferSize(int maxHistoryBufferSize) {
+ mMaxHistoryBufferSize = maxHistoryBufferSize;
+ }
+
+ /**
+ * Creates a read-only copy of the battery history. Does not copy the files stored
+ * in the system directory, so it is not safe while actively writing history.
+ */
+ public BatteryStatsHistory copy() {
+ // Make a copy of battery history to avoid concurrent modification.
+ Parcel historyBuffer = Parcel.obtain();
+ historyBuffer.appendFrom(mHistoryBuffer, 0, mHistoryBuffer.dataSize());
+ return new BatteryStatsHistory(historyBuffer, mSystemDir, 0, 0, null, null);
+ }
+
+ /**
+ * Returns true if this instance only supports reading history.
+ */
+ public boolean isReadOnly() {
+ return mActiveFile == null;
}
/**
@@ -221,12 +356,13 @@
/**
* Create history AtomicFile from file number.
+ *
* @param num file number.
* @return AtomicFile object.
*/
private AtomicFile getFile(int num) {
return new AtomicFile(
- new File(mHistoryDir, num + FILE_SUFFIX));
+ new File(mHistoryDir, num + FILE_SUFFIX));
}
/**
@@ -234,7 +370,7 @@
* create next history file.
*/
public void startNextFile() {
- if (mMaxHistoryFiles == null) {
+ if (mMaxHistoryFiles == 0) {
Slog.wtf(TAG, "mMaxHistoryFiles should not be zero when writing history");
return;
}
@@ -264,7 +400,7 @@
// if there are more history files than allowed, delete oldest history files.
// mMaxHistoryFiles comes from Constants.MAX_HISTORY_FILES and can be updated by GService
// config at run time.
- while (mFileNumbers.size() > mMaxHistoryFiles.get()) {
+ while (mFileNumbers.size() > mMaxHistoryFiles) {
int oldest = mFileNumbers.get(0);
getFile(oldest).delete();
mFileNumbers.remove(0);
@@ -272,36 +408,43 @@
}
/**
- * Delete all existing history files. Active history file start from number 0 again.
+ * Clear history buffer and delete all existing history files. Active history file start from
+ * number 0 again.
*/
- public void resetAllFiles() {
+ public void reset() {
+ if (DEBUG) Slog.i(TAG, "********** CLEARING HISTORY!");
for (Integer i : mFileNumbers) {
getFile(i).delete();
}
mFileNumbers.clear();
mFileNumbers.add(0);
setActiveFile(0);
+
+ initHistoryBuffer();
}
/**
* Start iterating history files and history buffer.
+ *
* @return always return true.
*/
- public boolean startIteratingHistory() {
+ public BatteryStatsHistoryIterator iterate() {
mRecordCount = 0;
mCurrentFileIndex = 0;
mCurrentParcel = null;
mCurrentParcelEnd = 0;
mParcelIndex = 0;
- return true;
+ mBatteryStatsHistoryIterator = new BatteryStatsHistoryIterator(this);
+ return mBatteryStatsHistoryIterator;
}
/**
* Finish iterating history files and history buffer.
*/
- public void finishIteratingHistory() {
+ void finishIteratingHistory() {
// setDataPosition so mHistoryBuffer Parcel can be written.
mHistoryBuffer.setDataPosition(mHistoryBuffer.dataSize());
+ mBatteryStatsHistoryIterator = null;
if (DEBUG) {
Slog.d(TAG, "Battery history records iterated: " + mRecordCount);
}
@@ -311,11 +454,12 @@
* When iterating history files and history buffer, always start from the lowest numbered
* history file, when reached the mActiveFile (highest numbered history file), do not read from
* mActiveFile, read from history buffer instead because the buffer has more updated data.
+ *
* @param out a history item.
* @return The parcel that has next record. null if finished all history files and history
- * buffer
+ * buffer
*/
- public Parcel getNextParcel(BatteryStats.HistoryItem out) {
+ public Parcel getNextParcel(HistoryItem out) {
if (mRecordCount == 0) {
// reset out if it is the first record.
out.clear();
@@ -323,8 +467,7 @@
++mRecordCount;
// First iterate through all records in current parcel.
- if (mCurrentParcel != null)
- {
+ if (mCurrentParcel != null) {
if (mCurrentParcel.dataPosition() < mCurrentParcelEnd) {
// There are more records in current parcel.
return mCurrentParcel;
@@ -389,7 +532,8 @@
/**
* Read history file into a parcel.
- * @param out the Parcel read into.
+ *
+ * @param out the Parcel read into.
* @param file the File to read from.
* @return true if success, false otherwise.
*/
@@ -402,8 +546,8 @@
Slog.d(TAG, "readFileToParcel:" + file.getBaseFile().getPath()
+ " duration ms:" + (SystemClock.uptimeMillis() - start));
}
- } catch(Exception e) {
- Slog.e(TAG, "Error reading file "+ file.getBaseFile().getPath(), e);
+ } catch (Exception e) {
+ Slog.e(TAG, "Error reading file " + file.getBaseFile().getPath(), e);
return false;
}
out.unmarshall(raw, 0, raw.length);
@@ -413,6 +557,7 @@
/**
* Skip the header part of history parcel.
+ *
* @param p history parcel to skip head.
* @return true if version match, false if not.
*/
@@ -428,18 +573,68 @@
}
/**
+ * Writes the battery history contents for persistence.
+ */
+ public void writeSummaryToParcel(Parcel out, boolean inclHistory) {
+ out.writeBoolean(inclHistory);
+ if (inclHistory) {
+ writeToParcel(out);
+ }
+
+ out.writeInt(mHistoryTagPool.size());
+ for (Map.Entry<HistoryTag, Integer> ent : mHistoryTagPool.entrySet()) {
+ HistoryTag tag = ent.getKey();
+ out.writeInt(ent.getValue());
+ out.writeString(tag.string);
+ out.writeInt(tag.uid);
+ }
+ }
+
+ /**
+ * Reads battery history contents from a persisted parcel.
+ */
+ public void readSummaryFromParcel(Parcel in) {
+ boolean inclHistory = in.readBoolean();
+ if (inclHistory) {
+ readFromParcel(in);
+ }
+
+ mHistoryTagPool.clear();
+ mNextHistoryTagIdx = 0;
+ mNumHistoryTagChars = 0;
+
+ int numTags = in.readInt();
+ for (int i = 0; i < numTags; i++) {
+ int idx = in.readInt();
+ String str = in.readString();
+ int uid = in.readInt();
+ HistoryTag tag = new HistoryTag();
+ tag.string = str;
+ tag.uid = uid;
+ tag.poolIdx = idx;
+ mHistoryTagPool.put(tag, idx);
+ if (idx >= mNextHistoryTagIdx) {
+ mNextHistoryTagIdx = idx + 1;
+ }
+ mNumHistoryTagChars += tag.string.length() + 1;
+ }
+ }
+
+ /**
* Read all history files and serialize into a big Parcel.
* Checkin file calls this method.
*
* @param out the output parcel
*/
public void writeToParcel(Parcel out) {
+ writeHistoryBuffer(out);
writeToParcel(out, false /* useBlobs */);
}
/**
* This is for Settings app, when Settings app receives big history parcel, it call
* this method to parse it into list of parcels.
+ *
* @param out the output parcel
*/
public void writeToBatteryUsageStatsParcel(Parcel out) {
@@ -450,13 +645,13 @@
private void writeToParcel(Parcel out, boolean useBlobs) {
final long start = SystemClock.uptimeMillis();
out.writeInt(mFileNumbers.size() - 1);
- for(int i = 0; i < mFileNumbers.size() - 1; i++) {
+ for (int i = 0; i < mFileNumbers.size() - 1; i++) {
AtomicFile file = getFile(mFileNumbers.get(i));
byte[] raw = new byte[0];
try {
raw = file.readFully();
- } catch(Exception e) {
- Slog.e(TAG, "Error reading file "+ file.getBaseFile().getPath(), e);
+ } catch (Exception e) {
+ Slog.e(TAG, "Error reading file " + file.getBaseFile().getPath(), e);
}
if (useBlobs) {
out.writeBlob(raw);
@@ -480,17 +675,55 @@
Parcel historyBuffer = Parcel.obtain();
historyBuffer.unmarshall(historyBlob, 0, historyBlob.length);
- BatteryStatsHistory history = new BatteryStatsHistory(historyBuffer);
+ BatteryStatsHistory history = new BatteryStatsHistory(historyBuffer, null,
+ Clock.SYSTEM_CLOCK);
history.readFromParcel(in, true /* useBlobs */);
return history;
}
/**
+ * Read history from a check-in file.
+ */
+ public boolean readSummary() {
+ if (mActiveFile == null) {
+ Slog.w(TAG, "readSummary: no history file associated with this instance");
+ return false;
+ }
+
+ Parcel parcel = Parcel.obtain();
+ try {
+ final long start = SystemClock.uptimeMillis();
+ if (mActiveFile.exists()) {
+ byte[] raw = mActiveFile.readFully();
+ if (raw.length > 0) {
+ parcel.unmarshall(raw, 0, raw.length);
+ parcel.setDataPosition(0);
+ readHistoryBuffer(parcel);
+ }
+ if (DEBUG) {
+ Slog.d(TAG, "read history file::"
+ + mActiveFile.getBaseFile().getPath()
+ + " bytes:" + raw.length + " took ms:" + (SystemClock.uptimeMillis()
+ - start));
+ }
+ }
+ } catch (Exception e) {
+ Slog.e(TAG, "Error reading battery history", e);
+ reset();
+ return false;
+ } finally {
+ parcel.recycle();
+ }
+ return true;
+ }
+
+ /**
* This is for the check-in file, which has all history files embedded.
*
* @param in the input parcel.
*/
public void readFromParcel(Parcel in) {
+ readHistoryBuffer(in);
readFromParcel(in, false /* useBlobs */);
}
@@ -498,7 +731,7 @@
final long start = SystemClock.uptimeMillis();
mHistoryParcels = new ArrayList<>();
final int count = in.readInt();
- for(int i = 0; i < count; i++) {
+ for (int i = 0; i < count; i++) {
byte[] temp = useBlobs ? in.readBlob() : in.createByteArray();
if (temp == null || temp.length == 0) {
continue;
@@ -521,10 +754,12 @@
return stats.getAvailableBytes() > MIN_FREE_SPACE;
}
+ @VisibleForTesting
public List<Integer> getFilesNumbers() {
return mFileNumbers;
}
+ @VisibleForTesting
public AtomicFile getActiveFile() {
return mActiveFile;
}
@@ -534,15 +769,972 @@
*/
public int getHistoryUsedSize() {
int ret = 0;
- for(int i = 0; i < mFileNumbers.size() - 1; i++) {
+ for (int i = 0; i < mFileNumbers.size() - 1; i++) {
ret += getFile(mFileNumbers.get(i)).getBaseFile().length();
}
ret += mHistoryBuffer.dataSize();
if (mHistoryParcels != null) {
- for(int i = 0; i < mHistoryParcels.size(); i++) {
+ for (int i = 0; i < mHistoryParcels.size(); i++) {
ret += mHistoryParcels.get(i).dataSize();
}
}
return ret;
}
+
+ /**
+ * Enables/disables recording of history. When disabled, all "record*" calls are a no-op.
+ */
+ public void setHistoryRecordingEnabled(boolean enabled) {
+ mRecordingHistory = enabled;
+ }
+
+ /**
+ * Returns true if history recording is enabled.
+ */
+ public boolean isRecordingHistory() {
+ return mRecordingHistory;
+ }
+
+ /**
+ * Forces history recording regardless of charging state.
+ */
+ @VisibleForTesting
+ public void forceRecordAllHistory() {
+ mHaveBatteryLevel = true;
+ mRecordingHistory = true;
+ }
+
+ /**
+ * Starts a history buffer by recording the current wall-clock time.
+ */
+ public void startRecordingHistory(final long elapsedRealtimeMs, final long uptimeMs,
+ boolean reset) {
+ mRecordingHistory = true;
+ mHistoryCur.currentTime = mClock.currentTimeMillis();
+ writeHistoryItem(elapsedRealtimeMs, uptimeMs, mHistoryCur,
+ reset ? HistoryItem.CMD_RESET : HistoryItem.CMD_CURRENT_TIME);
+ mHistoryCur.currentTime = 0;
+ }
+
+ /**
+ * Prepares to continue recording after restoring previous history from persistent storage.
+ */
+ public void continueRecordingHistory() {
+ if (mHistoryBuffer.dataPosition() <= 0 && mFileNumbers.size() <= 1) {
+ return;
+ }
+
+ mRecordingHistory = true;
+ final long elapsedRealtimeMs = mClock.elapsedRealtime();
+ final long uptimeMs = mClock.uptimeMillis();
+ writeHistoryItem(elapsedRealtimeMs, uptimeMs, mHistoryCur, HistoryItem.CMD_START);
+ startRecordingHistory(elapsedRealtimeMs, uptimeMs, false);
+ }
+
+ /**
+ * Notes the current battery state to be reflected in the next written history item.
+ */
+ public void setBatteryState(boolean charging, int status, int level, int chargeUah) {
+ mHaveBatteryLevel = true;
+ setChargingState(charging);
+ mHistoryCur.batteryStatus = (byte) status;
+ mHistoryCur.batteryLevel = (byte) level;
+ mHistoryCur.batteryChargeUah = chargeUah;
+ }
+
+ /**
+ * Notes the current battery state to be reflected in the next written history item.
+ */
+ public void setBatteryState(int status, int level, int health, int plugType, int temperature,
+ int voltageMv, int chargeUah) {
+ mHaveBatteryLevel = true;
+ mHistoryCur.batteryStatus = (byte) status;
+ mHistoryCur.batteryLevel = (byte) level;
+ mHistoryCur.batteryHealth = (byte) health;
+ mHistoryCur.batteryPlugType = (byte) plugType;
+ mHistoryCur.batteryTemperature = (short) temperature;
+ mHistoryCur.batteryVoltage = (char) voltageMv;
+ mHistoryCur.batteryChargeUah = chargeUah;
+ }
+
+ /**
+ * Notes the current power plugged-in state to be reflected in the next written history item.
+ */
+ public void setPluggedInState(boolean pluggedIn) {
+ if (pluggedIn) {
+ mHistoryCur.states |= HistoryItem.STATE_BATTERY_PLUGGED_FLAG;
+ } else {
+ mHistoryCur.states &= ~HistoryItem.STATE_BATTERY_PLUGGED_FLAG;
+ }
+ }
+
+ /**
+ * Notes the current battery charging state to be reflected in the next written history item.
+ */
+ public void setChargingState(boolean charging) {
+ if (charging) {
+ mHistoryCur.states2 |= HistoryItem.STATE2_CHARGING_FLAG;
+ } else {
+ mHistoryCur.states2 &= ~HistoryItem.STATE2_CHARGING_FLAG;
+ }
+ }
+
+ /**
+ * Records a history event with the given code, name and UID.
+ */
+ public void recordEvent(long elapsedRealtimeMs, long uptimeMs, int code, String name,
+ int uid) {
+ mHistoryCur.eventCode = code;
+ mHistoryCur.eventTag = mHistoryCur.localEventTag;
+ mHistoryCur.eventTag.string = name;
+ mHistoryCur.eventTag.uid = uid;
+ writeHistoryItem(elapsedRealtimeMs, uptimeMs);
+ }
+
+ /**
+ * Records a time change event.
+ */
+ public void recordCurrentTimeChange(long elapsedRealtimeMs, long uptimeMs, long currentTimeMs) {
+ if (!mRecordingHistory) {
+ return;
+ }
+
+ mHistoryCur.currentTime = currentTimeMs;
+ writeHistoryItem(elapsedRealtimeMs, uptimeMs, mHistoryCur,
+ HistoryItem.CMD_CURRENT_TIME);
+ mHistoryCur.currentTime = 0;
+ }
+
+ /**
+ * Records a system shutdown event.
+ */
+ public void recordShutdownEvent(long elapsedRealtimeMs, long uptimeMs, long currentTimeMs) {
+ if (!mRecordingHistory) {
+ return;
+ }
+
+ mHistoryCur.currentTime = currentTimeMs;
+ writeHistoryItem(elapsedRealtimeMs, uptimeMs, mHistoryCur, HistoryItem.CMD_SHUTDOWN);
+ mHistoryCur.currentTime = 0;
+ }
+
+ /**
+ * Records a battery state change event.
+ */
+ public void recordBatteryState(long elapsedRealtimeMs, long uptimeMs, int batteryLevel,
+ boolean isPlugged) {
+ mHistoryCur.batteryLevel = (byte) batteryLevel;
+ setPluggedInState(isPlugged);
+ if (DEBUG) {
+ Slog.v(TAG, "Battery unplugged to: "
+ + Integer.toHexString(mHistoryCur.states));
+ }
+ writeHistoryItem(elapsedRealtimeMs, uptimeMs);
+ }
+
+ /**
+ * Records a history item with the amount of charge consumed by WiFi. Used on certain devices
+ * equipped with on-device power metering.
+ */
+ public void recordWifiConsumedCharge(long elapsedRealtimeMs, long uptimeMs,
+ double monitoredRailChargeMah) {
+ mHistoryCur.wifiRailChargeMah += monitoredRailChargeMah;
+ writeHistoryItem(elapsedRealtimeMs, uptimeMs);
+ }
+
+ /**
+ * Records a wakelock start event.
+ */
+ public void recordWakelockStartEvent(long elapsedRealtimeMs, long uptimeMs, String historyName,
+ int uid) {
+ mHistoryCur.wakelockTag = mHistoryCur.localWakelockTag;
+ mHistoryCur.wakelockTag.string = historyName;
+ mHistoryCur.wakelockTag.uid = uid;
+ recordStateStartEvent(elapsedRealtimeMs, uptimeMs, HistoryItem.STATE_WAKE_LOCK_FLAG);
+ }
+
+ /**
+ * Updates the previous history event with a wakelock name and UID.
+ */
+ public boolean maybeUpdateWakelockTag(long elapsedRealtimeMs, long uptimeMs, String historyName,
+ int uid) {
+ if (mHistoryLastWritten.cmd != HistoryItem.CMD_UPDATE) {
+ return false;
+ }
+ if (mHistoryLastWritten.wakelockTag != null) {
+ // We'll try to update the last tag.
+ mHistoryLastWritten.wakelockTag = null;
+ mHistoryCur.wakelockTag = mHistoryCur.localWakelockTag;
+ mHistoryCur.wakelockTag.string = historyName;
+ mHistoryCur.wakelockTag.uid = uid;
+ writeHistoryItem(elapsedRealtimeMs, uptimeMs);
+ }
+ return true;
+ }
+
+ /**
+ * Records an event when some state flag changes to true.
+ */
+ public void recordStateStartEvent(long elapsedRealtimeMs, long uptimeMs, int stateFlags) {
+ mHistoryCur.states |= stateFlags;
+ writeHistoryItem(elapsedRealtimeMs, uptimeMs);
+ }
+
+ /**
+ * Records an event when some state flag changes to false.
+ */
+ public void recordStateStopEvent(long elapsedRealtimeMs, long uptimeMs, int stateFlags) {
+ mHistoryCur.states &= ~stateFlags;
+ writeHistoryItem(elapsedRealtimeMs, uptimeMs);
+ }
+
+ /**
+ * Records an event when some state flags change to true and some to false.
+ */
+ public void recordStateChangeEvent(long elapsedRealtimeMs, long uptimeMs, int stateStartFlags,
+ int stateStopFlags) {
+ mHistoryCur.states = (mHistoryCur.states | stateStartFlags) & ~stateStopFlags;
+ writeHistoryItem(elapsedRealtimeMs, uptimeMs);
+ }
+
+ /**
+ * Records an event when some state2 flag changes to true.
+ */
+ public void recordState2StartEvent(long elapsedRealtimeMs, long uptimeMs, int stateFlags) {
+ mHistoryCur.states2 |= stateFlags;
+ writeHistoryItem(elapsedRealtimeMs, uptimeMs);
+ }
+
+ /**
+ * Records an event when some state2 flag changes to false.
+ */
+ public void recordState2StopEvent(long elapsedRealtimeMs, long uptimeMs, int stateFlags) {
+ mHistoryCur.states2 &= ~stateFlags;
+ writeHistoryItem(elapsedRealtimeMs, uptimeMs);
+ }
+
+ /**
+ * Records an wakeup event.
+ */
+ public void recordWakeupEvent(long elapsedRealtimeMs, long uptimeMs, String reason) {
+ mHistoryCur.wakeReasonTag = mHistoryCur.localWakeReasonTag;
+ mHistoryCur.wakeReasonTag.string = reason;
+ mHistoryCur.wakeReasonTag.uid = 0;
+ writeHistoryItem(elapsedRealtimeMs, uptimeMs);
+ }
+
+ /**
+ * Records a screen brightness change event.
+ */
+ public void recordScreenBrightnessEvent(long elapsedRealtimeMs, long uptimeMs,
+ int brightnessBin) {
+ mHistoryCur.states = (mHistoryCur.states & ~HistoryItem.STATE_BRIGHTNESS_MASK)
+ | (brightnessBin << HistoryItem.STATE_BRIGHTNESS_SHIFT);
+ writeHistoryItem(elapsedRealtimeMs, uptimeMs);
+ }
+
+ /**
+ * Records a GNSS signal level change event.
+ */
+ public void recordGpsSignalQualityEvent(long elapsedRealtimeMs, long uptimeMs,
+ int signalLevel) {
+ mHistoryCur.states2 = (mHistoryCur.states2 & ~HistoryItem.STATE2_GPS_SIGNAL_QUALITY_MASK)
+ | (signalLevel << HistoryItem.STATE2_GPS_SIGNAL_QUALITY_SHIFT);
+ writeHistoryItem(elapsedRealtimeMs, uptimeMs);
+ }
+
+ /**
+ * Records a device idle mode change event.
+ */
+ public void recordDeviceIdleEvent(long elapsedRealtimeMs, long uptimeMs, int mode) {
+ mHistoryCur.states2 = (mHistoryCur.states2 & ~HistoryItem.STATE2_DEVICE_IDLE_MASK)
+ | (mode << HistoryItem.STATE2_DEVICE_IDLE_SHIFT);
+ writeHistoryItem(elapsedRealtimeMs, uptimeMs);
+ }
+
+ /**
+ * Records a telephony state change event.
+ */
+ public void recordPhoneStateChangeEvent(long elapsedRealtimeMs, long uptimeMs, int addStateFlag,
+ int removeStateFlag, int state, int signalStrength) {
+ mHistoryCur.states = (mHistoryCur.states | addStateFlag) & ~removeStateFlag;
+ if (state != -1) {
+ mHistoryCur.states =
+ (mHistoryCur.states & ~HistoryItem.STATE_PHONE_STATE_MASK)
+ | (state << HistoryItem.STATE_PHONE_STATE_SHIFT);
+ }
+ if (signalStrength != -1) {
+ mHistoryCur.states =
+ (mHistoryCur.states & ~HistoryItem.STATE_PHONE_SIGNAL_STRENGTH_MASK)
+ | (signalStrength << HistoryItem.STATE_PHONE_SIGNAL_STRENGTH_SHIFT);
+ }
+ writeHistoryItem(elapsedRealtimeMs, uptimeMs);
+ }
+
+ /**
+ * Records a data connection type change event.
+ */
+ public void recordDataConnectionTypeChangeEvent(long elapsedRealtimeMs, long uptimeMs,
+ int dataConnectionType) {
+ mHistoryCur.states = (mHistoryCur.states & ~HistoryItem.STATE_DATA_CONNECTION_MASK)
+ | (dataConnectionType << HistoryItem.STATE_DATA_CONNECTION_SHIFT);
+ writeHistoryItem(elapsedRealtimeMs, uptimeMs);
+ }
+
+ /**
+ * Records a WiFi supplicant state change event.
+ */
+ public void recordWifiSupplicantStateChangeEvent(long elapsedRealtimeMs, long uptimeMs,
+ int supplState) {
+ mHistoryCur.states2 =
+ (mHistoryCur.states2 & ~HistoryItem.STATE2_WIFI_SUPPL_STATE_MASK)
+ | (supplState << HistoryItem.STATE2_WIFI_SUPPL_STATE_SHIFT);
+ writeHistoryItem(elapsedRealtimeMs, uptimeMs);
+ }
+
+ /**
+ * Records a WiFi signal strength change event.
+ */
+ public void recordWifiSignalStrengthChangeEvent(long elapsedRealtimeMs, long uptimeMs,
+ int strengthBin) {
+ mHistoryCur.states2 =
+ (mHistoryCur.states2 & ~HistoryItem.STATE2_WIFI_SIGNAL_STRENGTH_MASK)
+ | (strengthBin << HistoryItem.STATE2_WIFI_SIGNAL_STRENGTH_SHIFT);
+ writeHistoryItem(elapsedRealtimeMs, uptimeMs);
+ }
+
+ /**
+ * Writes the current history item to history.
+ */
+ public void writeHistoryItem(long elapsedRealtimeMs, long uptimeMs) {
+ if (mTrackRunningHistoryElapsedRealtimeMs != 0) {
+ final long diffElapsedMs = elapsedRealtimeMs - mTrackRunningHistoryElapsedRealtimeMs;
+ final long diffUptimeMs = uptimeMs - mTrackRunningHistoryUptimeMs;
+ if (diffUptimeMs < (diffElapsedMs - 20)) {
+ final long wakeElapsedTimeMs = elapsedRealtimeMs - (diffElapsedMs - diffUptimeMs);
+ mHistoryAddTmp.setTo(mHistoryLastWritten);
+ mHistoryAddTmp.wakelockTag = null;
+ mHistoryAddTmp.wakeReasonTag = null;
+ mHistoryAddTmp.eventCode = HistoryItem.EVENT_NONE;
+ mHistoryAddTmp.states &= ~HistoryItem.STATE_CPU_RUNNING_FLAG;
+ writeHistoryItem(wakeElapsedTimeMs, uptimeMs, mHistoryAddTmp);
+ }
+ }
+ mHistoryCur.states |= HistoryItem.STATE_CPU_RUNNING_FLAG;
+ mTrackRunningHistoryElapsedRealtimeMs = elapsedRealtimeMs;
+ mTrackRunningHistoryUptimeMs = uptimeMs;
+ writeHistoryItem(elapsedRealtimeMs, uptimeMs, mHistoryCur);
+ }
+
+ private void writeHistoryItem(long elapsedRealtimeMs, long uptimeMs, HistoryItem cur) {
+ if (!mHaveBatteryLevel || !mRecordingHistory) {
+ return;
+ }
+
+ final long timeDiffMs = (mHistoryBaseTimeMs + elapsedRealtimeMs) - mHistoryLastWritten.time;
+ final int diffStates = mHistoryLastWritten.states ^ (cur.states & mActiveHistoryStates);
+ final int diffStates2 = mHistoryLastWritten.states2 ^ (cur.states2 & mActiveHistoryStates2);
+ final int lastDiffStates = mHistoryLastWritten.states ^ mHistoryLastLastWritten.states;
+ final int lastDiffStates2 = mHistoryLastWritten.states2 ^ mHistoryLastLastWritten.states2;
+ if (DEBUG) {
+ Slog.i(TAG, "ADD: tdelta=" + timeDiffMs + " diff="
+ + Integer.toHexString(diffStates) + " lastDiff="
+ + Integer.toHexString(lastDiffStates) + " diff2="
+ + Integer.toHexString(diffStates2) + " lastDiff2="
+ + Integer.toHexString(lastDiffStates2));
+ }
+ if (mHistoryBufferLastPos >= 0 && mHistoryLastWritten.cmd == HistoryItem.CMD_UPDATE
+ && timeDiffMs < 1000 && (diffStates & lastDiffStates) == 0
+ && (diffStates2 & lastDiffStates2) == 0
+ && (!mHistoryLastWritten.tagsFirstOccurrence && !cur.tagsFirstOccurrence)
+ && (mHistoryLastWritten.wakelockTag == null || cur.wakelockTag == null)
+ && (mHistoryLastWritten.wakeReasonTag == null || cur.wakeReasonTag == null)
+ && mHistoryLastWritten.stepDetails == null
+ && (mHistoryLastWritten.eventCode == HistoryItem.EVENT_NONE
+ || cur.eventCode == HistoryItem.EVENT_NONE)
+ && mHistoryLastWritten.batteryLevel == cur.batteryLevel
+ && mHistoryLastWritten.batteryStatus == cur.batteryStatus
+ && mHistoryLastWritten.batteryHealth == cur.batteryHealth
+ && mHistoryLastWritten.batteryPlugType == cur.batteryPlugType
+ && mHistoryLastWritten.batteryTemperature == cur.batteryTemperature
+ && mHistoryLastWritten.batteryVoltage == cur.batteryVoltage) {
+ // We can merge this new change in with the last one. Merging is
+ // allowed as long as only the states have changed, and within those states
+ // as long as no bit has changed both between now and the last entry, as
+ // well as the last entry and the one before it (so we capture any toggles).
+ if (DEBUG) Slog.i(TAG, "ADD: rewinding back to " + mHistoryBufferLastPos);
+ mHistoryBuffer.setDataSize(mHistoryBufferLastPos);
+ mHistoryBuffer.setDataPosition(mHistoryBufferLastPos);
+ mHistoryBufferLastPos = -1;
+ elapsedRealtimeMs = mHistoryLastWritten.time - mHistoryBaseTimeMs;
+ // If the last written history had a wakelock tag, we need to retain it.
+ // Note that the condition above made sure that we aren't in a case where
+ // both it and the current history item have a wakelock tag.
+ if (mHistoryLastWritten.wakelockTag != null) {
+ cur.wakelockTag = cur.localWakelockTag;
+ cur.wakelockTag.setTo(mHistoryLastWritten.wakelockTag);
+ }
+ // If the last written history had a wake reason tag, we need to retain it.
+ // Note that the condition above made sure that we aren't in a case where
+ // both it and the current history item have a wakelock tag.
+ if (mHistoryLastWritten.wakeReasonTag != null) {
+ cur.wakeReasonTag = cur.localWakeReasonTag;
+ cur.wakeReasonTag.setTo(mHistoryLastWritten.wakeReasonTag);
+ }
+ // If the last written history had an event, we need to retain it.
+ // Note that the condition above made sure that we aren't in a case where
+ // both it and the current history item have an event.
+ if (mHistoryLastWritten.eventCode != HistoryItem.EVENT_NONE) {
+ cur.eventCode = mHistoryLastWritten.eventCode;
+ cur.eventTag = cur.localEventTag;
+ cur.eventTag.setTo(mHistoryLastWritten.eventTag);
+ }
+ mHistoryLastWritten.setTo(mHistoryLastLastWritten);
+ }
+ final int dataSize = mHistoryBuffer.dataSize();
+
+ if (dataSize >= mMaxHistoryBufferSize) {
+ if (mMaxHistoryBufferSize == 0) {
+ Slog.wtf(TAG, "mMaxHistoryBufferSize should not be zero when writing history");
+ mMaxHistoryBufferSize = 1024;
+ }
+
+ //open a new history file.
+ final long start = SystemClock.uptimeMillis();
+ writeHistory();
+ if (DEBUG) {
+ Slog.d(TAG, "addHistoryBufferLocked writeHistory took ms:"
+ + (SystemClock.uptimeMillis() - start));
+ }
+ startNextFile();
+ mHistoryBuffer.setDataSize(0);
+ mHistoryBuffer.setDataPosition(0);
+ mHistoryBuffer.setDataCapacity(mMaxHistoryBufferSize / 2);
+ mHistoryBufferLastPos = -1;
+ mHistoryLastWritten.clear();
+ mHistoryLastLastWritten.clear();
+
+ // Mark every entry in the pool with a flag indicating that the tag
+ // has not yet been encountered while writing the current history buffer.
+ for (Map.Entry<HistoryTag, Integer> entry : mHistoryTagPool.entrySet()) {
+ entry.setValue(entry.getValue() | BatteryStatsHistory.TAG_FIRST_OCCURRENCE_FLAG);
+ }
+ // Make a copy of mHistoryCur.
+ HistoryItem copy = new HistoryItem();
+ copy.setTo(cur);
+ // startRecordingHistory will reset mHistoryCur.
+ startRecordingHistory(elapsedRealtimeMs, uptimeMs, false);
+ // Add the copy into history buffer.
+ writeHistoryItem(elapsedRealtimeMs, uptimeMs, copy, HistoryItem.CMD_UPDATE);
+ return;
+ }
+
+ if (dataSize == 0) {
+ // The history is currently empty; we need it to start with a time stamp.
+ cur.currentTime = mClock.currentTimeMillis();
+ writeHistoryItem(elapsedRealtimeMs, uptimeMs, cur, HistoryItem.CMD_RESET);
+ }
+ writeHistoryItem(elapsedRealtimeMs, uptimeMs, cur, HistoryItem.CMD_UPDATE);
+ }
+
+ private void writeHistoryItem(long elapsedRealtimeMs,
+ @SuppressWarnings("UnusedVariable") long uptimeMs, HistoryItem cur, byte cmd) {
+ if (mBatteryStatsHistoryIterator != null) {
+ throw new IllegalStateException("Can't do this while iterating history!");
+ }
+ mHistoryBufferLastPos = mHistoryBuffer.dataPosition();
+ mHistoryLastLastWritten.setTo(mHistoryLastWritten);
+ final boolean hasTags = mHistoryLastWritten.tagsFirstOccurrence || cur.tagsFirstOccurrence;
+ mHistoryLastWritten.setTo(mHistoryBaseTimeMs + elapsedRealtimeMs, cmd, cur);
+ mHistoryLastWritten.tagsFirstOccurrence = hasTags;
+ mHistoryLastWritten.states &= mActiveHistoryStates;
+ mHistoryLastWritten.states2 &= mActiveHistoryStates2;
+ writeHistoryDelta(mHistoryBuffer, mHistoryLastWritten, mHistoryLastLastWritten);
+ mLastHistoryElapsedRealtimeMs = elapsedRealtimeMs;
+ cur.wakelockTag = null;
+ cur.wakeReasonTag = null;
+ cur.eventCode = HistoryItem.EVENT_NONE;
+ cur.eventTag = null;
+ cur.tagsFirstOccurrence = false;
+ if (DEBUG) {
+ Slog.i(TAG, "Writing history buffer: was " + mHistoryBufferLastPos
+ + " now " + mHistoryBuffer.dataPosition()
+ + " size is now " + mHistoryBuffer.dataSize());
+ }
+ }
+
+ /*
+ The history delta format uses flags to denote further data in subsequent ints in the parcel.
+
+ There is always the first token, which may contain the delta time, or an indicator of
+ the length of the time (int or long) following this token.
+
+ First token: always present,
+ 31 23 15 7 0
+ â–ˆM|L|K|J|I|H|G|Fâ–ˆE|D|C|B|A|T|T|Tâ–ˆT|T|T|T|T|T|T|Tâ–ˆT|T|T|T|T|T|T|Tâ–ˆ
+
+ T: the delta time if it is <= 0x7fffd. Otherwise 0x7fffe indicates an int immediately
+ follows containing the time, and 0x7ffff indicates a long immediately follows with the
+ delta time.
+ A: battery level changed and an int follows with battery data.
+ B: state changed and an int follows with state change data.
+ C: state2 has changed and an int follows with state2 change data.
+ D: wakelock/wakereason has changed and an wakelock/wakereason struct follows.
+ E: event data has changed and an event struct follows.
+ F: battery charge in coulombs has changed and an int with the charge follows.
+ G: state flag denoting that the mobile radio was active.
+ H: state flag denoting that the wifi radio was active.
+ I: state flag denoting that a wifi scan occurred.
+ J: state flag denoting that a wifi full lock was held.
+ K: state flag denoting that the gps was on.
+ L: state flag denoting that a wakelock was held.
+ M: state flag denoting that the cpu was running.
+
+ Time int/long: if T in the first token is 0x7ffff or 0x7fffe, then an int or long follows
+ with the time delta.
+
+ Battery level int: if A in the first token is set,
+ 31 23 15 7 0
+ â–ˆL|L|L|L|L|L|L|Tâ–ˆT|T|T|T|T|T|T|Tâ–ˆT|V|V|V|V|V|V|Vâ–ˆV|V|V|V|V|V|V|Dâ–ˆ
+
+ D: indicates that extra history details follow.
+ V: the battery voltage.
+ T: the battery temperature.
+ L: the battery level (out of 100).
+
+ State change int: if B in the first token is set,
+ 31 23 15 7 0
+ â–ˆS|S|S|H|H|H|P|Pâ–ˆF|E|D|C|B| | |Aâ–ˆ | | | | | | | â–ˆ | | | | | | | â–ˆ
+
+ A: wifi multicast was on.
+ B: battery was plugged in.
+ C: screen was on.
+ D: phone was scanning for signal.
+ E: audio was on.
+ F: a sensor was active.
+
+ State2 change int: if C in the first token is set,
+ 31 23 15 7 0
+ â–ˆM|L|K|J|I|H|H|Gâ–ˆF|E|D|C| | | | â–ˆ | | | | | | | â–ˆ |B|B|B|A|A|A|Aâ–ˆ
+
+ A: 4 bits indicating the wifi supplicant state: {@link BatteryStats#WIFI_SUPPL_STATE_NAMES}.
+ B: 3 bits indicating the wifi signal strength: 0, 1, 2, 3, 4.
+ C: a bluetooth scan was active.
+ D: the camera was active.
+ E: bluetooth was on.
+ F: a phone call was active.
+ G: the device was charging.
+ H: 2 bits indicating the device-idle (doze) state: off, light, full
+ I: the flashlight was on.
+ J: wifi was on.
+ K: wifi was running.
+ L: video was playing.
+ M: power save mode was on.
+
+ Wakelock/wakereason struct: if D in the first token is set,
+ Event struct: if E in the first token is set,
+ History step details struct: if D in the battery level int is set,
+
+ Battery charge int: if F in the first token is set, an int representing the battery charge
+ in coulombs follows.
+ */
+ /**
+ * Writes the delta between the previous and current history items into history buffer.
+ */
+ public void writeHistoryDelta(Parcel dest, HistoryItem cur, HistoryItem last) {
+ if (last == null || cur.cmd != HistoryItem.CMD_UPDATE) {
+ dest.writeInt(BatteryStatsHistory.DELTA_TIME_ABS);
+ cur.writeToParcel(dest, 0);
+ return;
+ }
+
+ final long deltaTime = cur.time - last.time;
+ final int lastBatteryLevelInt = buildBatteryLevelInt(last);
+ final int lastStateInt = buildStateInt(last);
+
+ int deltaTimeToken;
+ if (deltaTime < 0 || deltaTime > Integer.MAX_VALUE) {
+ deltaTimeToken = BatteryStatsHistory.DELTA_TIME_LONG;
+ } else if (deltaTime >= BatteryStatsHistory.DELTA_TIME_ABS) {
+ deltaTimeToken = BatteryStatsHistory.DELTA_TIME_INT;
+ } else {
+ deltaTimeToken = (int) deltaTime;
+ }
+ int firstToken = deltaTimeToken | (cur.states & BatteryStatsHistory.DELTA_STATE_MASK);
+ final int includeStepDetails = mLastHistoryStepLevel > cur.batteryLevel
+ ? BatteryStatsHistory.BATTERY_DELTA_LEVEL_FLAG : 0;
+ mLastHistoryStepLevel = cur.batteryLevel;
+ final int batteryLevelInt = buildBatteryLevelInt(cur) | includeStepDetails;
+ final boolean batteryLevelIntChanged = batteryLevelInt != lastBatteryLevelInt;
+ if (batteryLevelIntChanged) {
+ firstToken |= BatteryStatsHistory.DELTA_BATTERY_LEVEL_FLAG;
+ }
+ final int stateInt = buildStateInt(cur);
+ final boolean stateIntChanged = stateInt != lastStateInt;
+ if (stateIntChanged) {
+ firstToken |= BatteryStatsHistory.DELTA_STATE_FLAG;
+ }
+ final boolean state2IntChanged = cur.states2 != last.states2;
+ if (state2IntChanged) {
+ firstToken |= BatteryStatsHistory.DELTA_STATE2_FLAG;
+ }
+ if (cur.wakelockTag != null || cur.wakeReasonTag != null) {
+ firstToken |= BatteryStatsHistory.DELTA_WAKELOCK_FLAG;
+ }
+ if (cur.eventCode != HistoryItem.EVENT_NONE) {
+ firstToken |= BatteryStatsHistory.DELTA_EVENT_FLAG;
+ }
+
+ final boolean batteryChargeChanged = cur.batteryChargeUah != last.batteryChargeUah;
+ if (batteryChargeChanged) {
+ firstToken |= BatteryStatsHistory.DELTA_BATTERY_CHARGE_FLAG;
+ }
+ dest.writeInt(firstToken);
+ if (DEBUG) {
+ Slog.i(TAG, "WRITE DELTA: firstToken=0x" + Integer.toHexString(firstToken)
+ + " deltaTime=" + deltaTime);
+ }
+
+ if (deltaTimeToken >= BatteryStatsHistory.DELTA_TIME_INT) {
+ if (deltaTimeToken == BatteryStatsHistory.DELTA_TIME_INT) {
+ if (DEBUG) Slog.i(TAG, "WRITE DELTA: int deltaTime=" + (int) deltaTime);
+ dest.writeInt((int) deltaTime);
+ } else {
+ if (DEBUG) Slog.i(TAG, "WRITE DELTA: long deltaTime=" + deltaTime);
+ dest.writeLong(deltaTime);
+ }
+ }
+ if (batteryLevelIntChanged) {
+ dest.writeInt(batteryLevelInt);
+ if (DEBUG) {
+ Slog.i(TAG, "WRITE DELTA: batteryToken=0x"
+ + Integer.toHexString(batteryLevelInt)
+ + " batteryLevel=" + cur.batteryLevel
+ + " batteryTemp=" + cur.batteryTemperature
+ + " batteryVolt=" + (int) cur.batteryVoltage);
+ }
+ }
+ if (stateIntChanged) {
+ dest.writeInt(stateInt);
+ if (DEBUG) {
+ Slog.i(TAG, "WRITE DELTA: stateToken=0x"
+ + Integer.toHexString(stateInt)
+ + " batteryStatus=" + cur.batteryStatus
+ + " batteryHealth=" + cur.batteryHealth
+ + " batteryPlugType=" + cur.batteryPlugType
+ + " states=0x" + Integer.toHexString(cur.states));
+ }
+ }
+ if (state2IntChanged) {
+ dest.writeInt(cur.states2);
+ if (DEBUG) {
+ Slog.i(TAG, "WRITE DELTA: states2=0x"
+ + Integer.toHexString(cur.states2));
+ }
+ }
+ if (cur.wakelockTag != null || cur.wakeReasonTag != null) {
+ int wakeLockIndex;
+ int wakeReasonIndex;
+ if (cur.wakelockTag != null) {
+ wakeLockIndex = writeHistoryTag(cur.wakelockTag);
+ if (DEBUG) {
+ Slog.i(TAG, "WRITE DELTA: wakelockTag=#" + cur.wakelockTag.poolIdx
+ + " " + cur.wakelockTag.uid + ":" + cur.wakelockTag.string);
+ }
+ } else {
+ wakeLockIndex = 0xffff;
+ }
+ if (cur.wakeReasonTag != null) {
+ wakeReasonIndex = writeHistoryTag(cur.wakeReasonTag);
+ if (DEBUG) {
+ Slog.i(TAG, "WRITE DELTA: wakeReasonTag=#" + cur.wakeReasonTag.poolIdx
+ + " " + cur.wakeReasonTag.uid + ":" + cur.wakeReasonTag.string);
+ }
+ } else {
+ wakeReasonIndex = 0xffff;
+ }
+ dest.writeInt((wakeReasonIndex << 16) | wakeLockIndex);
+ if (cur.wakelockTag != null
+ && (wakeLockIndex & BatteryStatsHistory.TAG_FIRST_OCCURRENCE_FLAG) != 0) {
+ cur.wakelockTag.writeToParcel(dest, 0);
+ cur.tagsFirstOccurrence = true;
+ }
+ if (cur.wakeReasonTag != null
+ && (wakeReasonIndex & BatteryStatsHistory.TAG_FIRST_OCCURRENCE_FLAG) != 0) {
+ cur.wakeReasonTag.writeToParcel(dest, 0);
+ cur.tagsFirstOccurrence = true;
+ }
+ }
+ if (cur.eventCode != HistoryItem.EVENT_NONE) {
+ final int index = writeHistoryTag(cur.eventTag);
+ final int codeAndIndex = (cur.eventCode & 0xffff) | (index << 16);
+ dest.writeInt(codeAndIndex);
+ if ((index & BatteryStatsHistory.TAG_FIRST_OCCURRENCE_FLAG) != 0) {
+ cur.eventTag.writeToParcel(dest, 0);
+ cur.tagsFirstOccurrence = true;
+ }
+ if (DEBUG) {
+ Slog.i(TAG, "WRITE DELTA: event=" + cur.eventCode + " tag=#"
+ + cur.eventTag.poolIdx + " " + cur.eventTag.uid + ":"
+ + cur.eventTag.string);
+ }
+ }
+
+ cur.stepDetails = mStepDetailsCalculator.getHistoryStepDetails();
+ if (includeStepDetails != 0) {
+ cur.stepDetails.writeToParcel(dest);
+ }
+
+ if (batteryChargeChanged) {
+ if (DEBUG) Slog.i(TAG, "WRITE DELTA: batteryChargeUah=" + cur.batteryChargeUah);
+ dest.writeInt(cur.batteryChargeUah);
+ }
+ dest.writeDouble(cur.modemRailChargeMah);
+ dest.writeDouble(cur.wifiRailChargeMah);
+ }
+
+ private int buildBatteryLevelInt(HistoryItem h) {
+ return ((((int) h.batteryLevel) << 25) & 0xfe000000)
+ | ((((int) h.batteryTemperature) << 15) & 0x01ff8000)
+ | ((((int) h.batteryVoltage) << 1) & 0x00007ffe);
+ }
+
+ private int buildStateInt(HistoryItem h) {
+ int plugType = 0;
+ if ((h.batteryPlugType & BatteryManager.BATTERY_PLUGGED_AC) != 0) {
+ plugType = 1;
+ } else if ((h.batteryPlugType & BatteryManager.BATTERY_PLUGGED_USB) != 0) {
+ plugType = 2;
+ } else if ((h.batteryPlugType & BatteryManager.BATTERY_PLUGGED_WIRELESS) != 0) {
+ plugType = 3;
+ }
+ return ((h.batteryStatus & BatteryStatsHistory.STATE_BATTERY_STATUS_MASK)
+ << BatteryStatsHistory.STATE_BATTERY_STATUS_SHIFT)
+ | ((h.batteryHealth & BatteryStatsHistory.STATE_BATTERY_HEALTH_MASK)
+ << BatteryStatsHistory.STATE_BATTERY_HEALTH_SHIFT)
+ | ((plugType & BatteryStatsHistory.STATE_BATTERY_PLUG_MASK)
+ << BatteryStatsHistory.STATE_BATTERY_PLUG_SHIFT)
+ | (h.states & (~BatteryStatsHistory.STATE_BATTERY_MASK));
+ }
+
+ /**
+ * Returns the index for the specified tag. If this is the first time the tag is encountered
+ * while writing the current history buffer, the method returns
+ * <code>(index | TAG_FIRST_OCCURRENCE_FLAG)</code>
+ */
+ private int writeHistoryTag(HistoryTag tag) {
+ if (tag.string == null) {
+ Slog.wtfStack(TAG, "writeHistoryTag called with null name");
+ }
+
+ final int stringLength = tag.string.length();
+ if (stringLength > MAX_HISTORY_TAG_STRING_LENGTH) {
+ Slog.e(TAG, "Long battery history tag: " + tag.string);
+ tag.string = tag.string.substring(0, MAX_HISTORY_TAG_STRING_LENGTH);
+ }
+
+ Integer idxObj = mHistoryTagPool.get(tag);
+ int idx;
+ if (idxObj != null) {
+ idx = idxObj;
+ if ((idx & BatteryStatsHistory.TAG_FIRST_OCCURRENCE_FLAG) != 0) {
+ mHistoryTagPool.put(tag, idx & ~BatteryStatsHistory.TAG_FIRST_OCCURRENCE_FLAG);
+ }
+ return idx;
+ } else if (mNextHistoryTagIdx < HISTORY_TAG_INDEX_LIMIT) {
+ idx = mNextHistoryTagIdx;
+ HistoryTag key = new HistoryTag();
+ key.setTo(tag);
+ tag.poolIdx = idx;
+ mHistoryTagPool.put(key, idx);
+ mNextHistoryTagIdx++;
+
+ mNumHistoryTagChars += stringLength + 1;
+ if (mHistoryTags != null) {
+ mHistoryTags.put(idx, key);
+ }
+ return idx | BatteryStatsHistory.TAG_FIRST_OCCURRENCE_FLAG;
+ } else {
+ // Tag pool overflow: include the tag itself in the parcel
+ return HISTORY_TAG_INDEX_LIMIT | BatteryStatsHistory.TAG_FIRST_OCCURRENCE_FLAG;
+ }
+ }
+
+ /**
+ * Don't allow any more batching in to the current history event.
+ */
+ public void commitCurrentHistoryBatchLocked() {
+ mHistoryLastWritten.cmd = HistoryItem.CMD_NULL;
+ }
+
+ /**
+ * Saves the accumulated history buffer in the active file, see {@link #getActiveFile()} .
+ */
+ public void writeHistory() {
+ if (mActiveFile == null) {
+ Slog.w(TAG, "writeHistory: no history file associated with this instance");
+ return;
+ }
+
+ Parcel p = Parcel.obtain();
+ try {
+ final long start = SystemClock.uptimeMillis();
+ writeHistoryBuffer(p);
+ if (DEBUG) {
+ Slog.d(TAG, "writeHistoryBuffer duration ms:"
+ + (SystemClock.uptimeMillis() - start) + " bytes:" + p.dataSize());
+ }
+ writeParcelToFileLocked(p, mActiveFile);
+ } finally {
+ p.recycle();
+ }
+ }
+
+ /**
+ * Reads history buffer from a persisted Parcel.
+ */
+ public void readHistoryBuffer(Parcel in) throws ParcelFormatException {
+ final int version = in.readInt();
+ if (version != BatteryStatsHistory.VERSION) {
+ Slog.w("BatteryStats", "readHistoryBuffer: version got " + version
+ + ", expected " + BatteryStatsHistory.VERSION + "; erasing old stats");
+ return;
+ }
+
+ final long historyBaseTime = in.readLong();
+
+ mHistoryBuffer.setDataSize(0);
+ mHistoryBuffer.setDataPosition(0);
+
+ int bufSize = in.readInt();
+ int curPos = in.dataPosition();
+ if (bufSize >= (mMaxHistoryBufferSize * 100)) {
+ throw new ParcelFormatException(
+ "File corrupt: history data buffer too large " + bufSize);
+ } else if ((bufSize & ~3) != bufSize) {
+ throw new ParcelFormatException(
+ "File corrupt: history data buffer not aligned " + bufSize);
+ } else {
+ if (DEBUG) {
+ Slog.i(TAG, "***************** READING NEW HISTORY: " + bufSize
+ + " bytes at " + curPos);
+ }
+ mHistoryBuffer.appendFrom(in, curPos, bufSize);
+ in.setDataPosition(curPos + bufSize);
+ }
+
+ if (DEBUG) {
+ StringBuilder sb = new StringBuilder(128);
+ sb.append("****************** OLD mHistoryBaseTimeMs: ");
+ TimeUtils.formatDuration(mHistoryBaseTimeMs, sb);
+ Slog.i(TAG, sb.toString());
+ }
+ mHistoryBaseTimeMs = historyBaseTime;
+ if (DEBUG) {
+ StringBuilder sb = new StringBuilder(128);
+ sb.append("****************** NEW mHistoryBaseTimeMs: ");
+ TimeUtils.formatDuration(mHistoryBaseTimeMs, sb);
+ Slog.i(TAG, sb.toString());
+ }
+
+ // We are just arbitrarily going to insert 1 minute from the sample of
+ // the last run until samples in this run.
+ if (mHistoryBaseTimeMs > 0) {
+ long oldnow = mClock.elapsedRealtime();
+ mHistoryBaseTimeMs = mHistoryBaseTimeMs - oldnow + 1;
+ if (DEBUG) {
+ StringBuilder sb = new StringBuilder(128);
+ sb.append("****************** ADJUSTED mHistoryBaseTimeMs: ");
+ TimeUtils.formatDuration(mHistoryBaseTimeMs, sb);
+ Slog.i(TAG, sb.toString());
+ }
+ }
+ }
+
+ private void writeHistoryBuffer(Parcel out) {
+ if (DEBUG) {
+ StringBuilder sb = new StringBuilder(128);
+ sb.append("****************** WRITING mHistoryBaseTimeMs: ");
+ TimeUtils.formatDuration(mHistoryBaseTimeMs, sb);
+ sb.append(" mLastHistoryElapsedRealtimeMs: ");
+ TimeUtils.formatDuration(mLastHistoryElapsedRealtimeMs, sb);
+ Slog.i(TAG, sb.toString());
+ }
+ out.writeInt(BatteryStatsHistory.VERSION);
+ out.writeLong(mHistoryBaseTimeMs + mLastHistoryElapsedRealtimeMs);
+ out.writeInt(mHistoryBuffer.dataSize());
+ if (DEBUG) {
+ Slog.i(TAG, "***************** WRITING HISTORY: "
+ + mHistoryBuffer.dataSize() + " bytes at " + out.dataPosition());
+ }
+ out.appendFrom(mHistoryBuffer, 0, mHistoryBuffer.dataSize());
+ }
+
+ private void writeParcelToFileLocked(Parcel p, AtomicFile file) {
+ FileOutputStream fos = null;
+ mWriteLock.lock();
+ try {
+ final long startTimeMs = SystemClock.uptimeMillis();
+ fos = file.startWrite();
+ fos.write(p.marshall());
+ fos.flush();
+ file.finishWrite(fos);
+ if (DEBUG) {
+ Slog.d(TAG, "writeParcelToFileLocked file:" + file.getBaseFile().getPath()
+ + " duration ms:" + (SystemClock.uptimeMillis() - startTimeMs)
+ + " bytes:" + p.dataSize());
+ }
+ com.android.internal.logging.EventLogTags.writeCommitSysConfigFile(
+ "batterystats", SystemClock.uptimeMillis() - startTimeMs);
+ } catch (IOException e) {
+ Slog.w(TAG, "Error writing battery statistics", e);
+ file.failWrite(fos);
+ } finally {
+ mWriteLock.unlock();
+ }
+ }
+
+ /**
+ * Returns the total number of history tags in the tag pool.
+ */
+ public int getHistoryStringPoolSize() {
+ return mHistoryTagPool.size();
+ }
+
+ /**
+ * Returns the total number of bytes occupied by the history tag pool.
+ */
+ public int getHistoryStringPoolBytes() {
+ return mNumHistoryTagChars;
+ }
+
+ /**
+ * Returns the string held by the requested history tag.
+ */
+ public String getHistoryTagPoolString(int index) {
+ ensureHistoryTagArray();
+ HistoryTag historyTag = mHistoryTags.get(index);
+ return historyTag != null ? historyTag.string : null;
+ }
+
+ /**
+ * Returns the UID held by the requested history tag.
+ */
+ public int getHistoryTagPoolUid(int index) {
+ ensureHistoryTagArray();
+ HistoryTag historyTag = mHistoryTags.get(index);
+ return historyTag != null ? historyTag.uid : Process.INVALID_UID;
+ }
+
+ private void ensureHistoryTagArray() {
+ if (mHistoryTags != null) {
+ return;
+ }
+
+ mHistoryTags = new SparseArray<>(mHistoryTagPool.size());
+ for (Map.Entry<HistoryTag, Integer> entry : mHistoryTagPool.entrySet()) {
+ mHistoryTags.put(entry.getValue() & ~BatteryStatsHistory.TAG_FIRST_OCCURRENCE_FLAG,
+ entry.getKey());
+ }
+ }
}
diff --git a/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java b/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java
index de8b414..1bf878cb 100644
--- a/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java
+++ b/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java
@@ -36,7 +36,6 @@
public BatteryStatsHistoryIterator(@NonNull BatteryStatsHistory history) {
mBatteryStatsHistory = history;
- mBatteryStatsHistory.startIteratingHistory();
}
/**
@@ -231,4 +230,11 @@
out.batteryTemperature = (short) ((batteryLevelInt & 0x01ff8000) >>> 15);
out.batteryVoltage = (char) ((batteryLevelInt & 0x00007ffe) >>> 1);
}
+
+ /**
+ * Should be called when iteration is complete.
+ */
+ public void close() {
+ mBatteryStatsHistory.finishIteratingHistory();
+ }
}
diff --git a/core/java/com/android/internal/os/BinderCallsStats.java b/core/java/com/android/internal/os/BinderCallsStats.java
index fa6fa55..0a29fc52 100644
--- a/core/java/com/android/internal/os/BinderCallsStats.java
+++ b/core/java/com/android/internal/os/BinderCallsStats.java
@@ -1169,15 +1169,7 @@
}
- /** @hide */
- public static void startForWifi(Context context) {
- new BinderCallsStats.SettingsObserver(
- context,
- new BinderCallsStats(
- new BinderCallsStats.Injector(),
- com.android.internal.os.BinderLatencyProto.Dims.WIFI));
- }
/**
* Settings observer for other processes (not system_server).
diff --git a/core/java/com/android/internal/view/inline/InlineTooltipUi.java b/core/java/com/android/internal/view/inline/InlineTooltipUi.java
index 3eae89e..836786d 100644
--- a/core/java/com/android/internal/view/inline/InlineTooltipUi.java
+++ b/core/java/com/android/internal/view/inline/InlineTooltipUi.java
@@ -170,9 +170,9 @@
int delayTimeMs = mShowDelayConfigMs;
try {
- final float scale = Settings.Global.getFloat(
+ final float scale = WindowManager.fixScale(Settings.Global.getFloat(
anchor.getContext().getContentResolver(),
- Settings.Global.ANIMATOR_DURATION_SCALE);
+ Settings.Global.ANIMATOR_DURATION_SCALE));
delayTimeMs *= scale;
} catch (Settings.SettingNotFoundException e) {
// do nothing
diff --git a/core/jni/android_hardware_HardwareBuffer.cpp b/core/jni/android_hardware_HardwareBuffer.cpp
index f462523..5fcc46e 100644
--- a/core/jni/android_hardware_HardwareBuffer.cpp
+++ b/core/jni/android_hardware_HardwareBuffer.cpp
@@ -163,7 +163,7 @@
static jlong android_hardware_HardwareBuffer_getUsage(JNIEnv* env,
jobject clazz, jlong nativeObject) {
GraphicBuffer* buffer = GraphicBufferWrapper_to_GraphicBuffer(nativeObject);
- return AHardwareBuffer_convertFromGrallocUsageBits(buffer->getUsage());
+ return static_cast<jlong>(AHardwareBuffer_convertFromGrallocUsageBits(buffer->getUsage()));
}
static jlong android_hardware_HardwareBuffer_estimateSize(jlong nativeObject) {
@@ -177,7 +177,12 @@
const uint32_t bufferStride =
buffer->getStride() > 0 ? buffer->getStride() : buffer->getWidth();
- return static_cast<jlong>(buffer->getHeight() * bufferStride * bpp);
+ return static_cast<jlong>(static_cast<uint64_t>(buffer->getHeight() * bufferStride * bpp));
+}
+
+static jlong android_hardware_HardwareBuffer_getId(jlong nativeObject) {
+ GraphicBuffer* buffer = GraphicBufferWrapper_to_GraphicBuffer(nativeObject);
+ return static_cast<jlong>(buffer->getId());
}
// ----------------------------------------------------------------------------
@@ -223,16 +228,6 @@
}
}
-GraphicBuffer* android_hardware_HardwareBuffer_getNativeGraphicBuffer(
- JNIEnv* env, jobject hardwareBufferObj) {
- if (env->IsInstanceOf(hardwareBufferObj, gHardwareBufferClassInfo.clazz)) {
- return GraphicBufferWrapper_to_GraphicBuffer(
- env->GetLongField(hardwareBufferObj, gHardwareBufferClassInfo.mNativeObject));
- } else {
- return nullptr;
- }
-}
-
jobject android_hardware_HardwareBuffer_createFromAHardwareBuffer(
JNIEnv* env, AHardwareBuffer* hardwareBuffer) {
GraphicBuffer* buffer = AHardwareBuffer_to_GraphicBuffer(hardwareBuffer);
@@ -295,6 +290,7 @@
// --------------- @CriticalNative ----------------------
{ "nEstimateSize", "(J)J", (void*) android_hardware_HardwareBuffer_estimateSize },
+ { "nGetId", "(J)J", (void*) android_hardware_HardwareBuffer_getId },
};
// clang-format on
diff --git a/core/jni/android_util_Process.cpp b/core/jni/android_util_Process.cpp
index 9501c8d..b9d5ee4 100644
--- a/core/jni/android_util_Process.cpp
+++ b/core/jni/android_util_Process.cpp
@@ -1252,20 +1252,6 @@
return fd;
}
-void android_os_Process_freezeCgroupUID(JNIEnv* env, jobject clazz, jint uid, jboolean freeze) {
- bool success = true;
-
- if (freeze) {
- success = SetUserProfiles(uid, {"Frozen"});
- } else {
- success = SetUserProfiles(uid, {"Unfrozen"});
- }
-
- if (!success) {
- jniThrowRuntimeException(env, "Could not apply user profile");
- }
-}
-
static const JNINativeMethod methods[] = {
{"getUidForName", "(Ljava/lang/String;)I", (void*)android_os_Process_getUidForName},
{"getGidForName", "(Ljava/lang/String;)I", (void*)android_os_Process_getGidForName},
@@ -1307,7 +1293,6 @@
{"killProcessGroup", "(II)I", (void*)android_os_Process_killProcessGroup},
{"removeAllProcessGroups", "()V", (void*)android_os_Process_removeAllProcessGroups},
{"nativePidFdOpen", "(II)I", (void*)android_os_Process_nativePidFdOpen},
- {"freezeCgroupUid", "(IZ)V", (void*)android_os_Process_freezeCgroupUID},
};
int register_android_os_Process(JNIEnv* env)
diff --git a/core/jni/include/android_runtime/android_hardware_HardwareBuffer.h b/core/jni/include/android_runtime/android_hardware_HardwareBuffer.h
index dfd8035..964c28f 100644
--- a/core/jni/include/android_runtime/android_hardware_HardwareBuffer.h
+++ b/core/jni/include/android_runtime/android_hardware_HardwareBuffer.h
@@ -28,10 +28,6 @@
extern AHardwareBuffer* android_hardware_HardwareBuffer_getNativeHardwareBuffer(
JNIEnv* env, jobject hardwareBufferObj);
-/* Gets the underlying GraphicBuffer for a HardwareBuffer. */
-extern GraphicBuffer* android_hardware_HardwareBuffer_getNativeGraphicBuffer(
- JNIEnv* env, jobject hardwareBufferObj);
-
/* Returns a HardwareBuffer wrapper for the underlying AHardwareBuffer. */
extern jobject android_hardware_HardwareBuffer_createFromAHardwareBuffer(
JNIEnv* env, AHardwareBuffer* hardwareBuffer);
diff --git a/core/proto/android/providers/settings/secure.proto b/core/proto/android/providers/settings/secure.proto
index 322354b..789ceff 100644
--- a/core/proto/android/providers/settings/secure.proto
+++ b/core/proto/android/providers/settings/secure.proto
@@ -89,6 +89,14 @@
// Setting for accessibility magnification for following typing.
optional SettingProto accessibility_magnification_follow_typing_enabled = 43 [ (android.privacy).dest = DEST_AUTOMATIC ];
optional SettingProto accessibility_software_cursor_enabled = 44 [ (android.privacy).dest = DEST_AUTOMATIC ];
+
+ message SoftwareCursorSettings {
+ optional SettingProto trigger_hints_enabled = 1 [ (android.privacy).dest = DEST_AUTOMATIC ];
+ optional SettingProto keyboard_shift_enabled = 2 [ (android.privacy).dest = DEST_AUTOMATIC ];
+ }
+
+ optional SoftwareCursorSettings accessibility_software_cursor_settings = 45 [ (android.privacy).dest = DEST_AUTOMATIC ];
+
}
optional Accessibility accessibility = 2;
diff --git a/core/tests/coretests/src/android/view/InsetsControllerTest.java b/core/tests/coretests/src/android/view/InsetsControllerTest.java
index ed6a649..6e59b83 100644
--- a/core/tests/coretests/src/android/view/InsetsControllerTest.java
+++ b/core/tests/coretests/src/android/view/InsetsControllerTest.java
@@ -236,6 +236,21 @@
}
@Test
+ public void testSystemDrivenInsetsAnimationLoggingListener_onReady() {
+ prepareControls();
+ // only the original thread that created view hierarchy can touch its views
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+ WindowInsetsAnimationControlListener loggingListener =
+ mock(WindowInsetsAnimationControlListener.class);
+ mController.setSystemDrivenInsetsAnimationLoggingListener(loggingListener);
+ mController.getSourceConsumer(ITYPE_IME).onWindowFocusGained(true);
+ // since there is no focused view, forcefully make IME visible.
+ mController.show(Type.ime(), true /* fromIme */);
+ verify(loggingListener).onReady(notNull(), anyInt());
+ });
+ }
+
+ @Test
public void testAnimationEndState() {
InsetsSourceControl[] controls = prepareControls();
InsetsSourceControl navBar = controls[0];
diff --git a/core/tests/coretests/src/android/view/PendingInsetsControllerTest.java b/core/tests/coretests/src/android/view/PendingInsetsControllerTest.java
index 03c8b1b..690b3587 100644
--- a/core/tests/coretests/src/android/view/PendingInsetsControllerTest.java
+++ b/core/tests/coretests/src/android/view/PendingInsetsControllerTest.java
@@ -213,6 +213,25 @@
}
@Test
+ public void testSystemDrivenInsetsAnimationLoggingListener() {
+ WindowInsetsAnimationControlListener listener =
+ mock(WindowInsetsAnimationControlListener.class);
+ mPendingInsetsController.setSystemDrivenInsetsAnimationLoggingListener(listener);
+ mPendingInsetsController.replayAndAttach(mReplayedController);
+ verify(mReplayedController).setSystemDrivenInsetsAnimationLoggingListener(eq(listener));
+ }
+
+ @Test
+ public void testSystemDrivenInsetsAnimationLoggingListener_direct() {
+ mPendingInsetsController.replayAndAttach(mReplayedController);
+ WindowInsetsAnimationControlListener listener =
+ mock(WindowInsetsAnimationControlListener.class);
+ mPendingInsetsController.setSystemDrivenInsetsAnimationLoggingListener(listener);
+ verify(mReplayedController).setSystemDrivenInsetsAnimationLoggingListener(
+ eq(listener));
+ }
+
+ @Test
public void testDetachReattach() {
mPendingInsetsController.show(systemBars());
mPendingInsetsController.setSystemBarsBehavior(BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE);
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
index d42fca2..9fb7d19 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
@@ -51,28 +51,37 @@
@VisibleForTesting
final Map<IBinder, TaskFragmentInfo> mFragmentInfos = new ArrayMap<>();
+ @NonNull
private final TaskFragmentCallback mCallback;
+
@VisibleForTesting
+ @Nullable
TaskFragmentAnimationController mAnimationController;
/**
* Callback that notifies the controller about changes to task fragments.
*/
interface TaskFragmentCallback {
- void onTaskFragmentAppeared(@NonNull TaskFragmentInfo taskFragmentInfo);
- void onTaskFragmentInfoChanged(@NonNull TaskFragmentInfo taskFragmentInfo);
- void onTaskFragmentVanished(@NonNull TaskFragmentInfo taskFragmentInfo);
- void onTaskFragmentParentInfoChanged(int taskId, @NonNull Configuration parentConfig);
- void onActivityReparentToTask(int taskId, @NonNull Intent activityIntent,
- @NonNull IBinder activityToken);
- void onTaskFragmentError(@Nullable TaskFragmentInfo taskFragmentInfo, int opType);
+ void onTaskFragmentAppeared(@NonNull WindowContainerTransaction wct,
+ @NonNull TaskFragmentInfo taskFragmentInfo);
+ void onTaskFragmentInfoChanged(@NonNull WindowContainerTransaction wct,
+ @NonNull TaskFragmentInfo taskFragmentInfo);
+ void onTaskFragmentVanished(@NonNull WindowContainerTransaction wct,
+ @NonNull TaskFragmentInfo taskFragmentInfo);
+ void onTaskFragmentParentInfoChanged(@NonNull WindowContainerTransaction wct,
+ int taskId, @NonNull Configuration parentConfig);
+ void onActivityReparentedToTask(@NonNull WindowContainerTransaction wct,
+ int taskId, @NonNull Intent activityIntent, @NonNull IBinder activityToken);
+ void onTaskFragmentError(@NonNull WindowContainerTransaction wct,
+ @Nullable TaskFragmentInfo taskFragmentInfo, int opType);
}
/**
* @param executor callbacks from WM Core are posted on this executor. It should be tied to the
* UI thread that all other calls into methods of this class are also on.
*/
- JetpackTaskFragmentOrganizer(@NonNull Executor executor, TaskFragmentCallback callback) {
+ JetpackTaskFragmentOrganizer(@NonNull Executor executor,
+ @NonNull TaskFragmentCallback callback) {
super(executor);
mCallback = callback;
}
@@ -147,41 +156,31 @@
* @param wct WindowContainerTransaction in which the task fragment should be resized.
* @param fragmentToken token of an existing TaskFragment.
*/
- void expandTaskFragment(WindowContainerTransaction wct, IBinder fragmentToken) {
+ void expandTaskFragment(@NonNull WindowContainerTransaction wct,
+ @NonNull IBinder fragmentToken) {
resizeTaskFragment(wct, fragmentToken, new Rect());
setAdjacentTaskFragments(wct, fragmentToken, null /* secondary */, null /* splitRule */);
updateWindowingMode(wct, fragmentToken, WINDOWING_MODE_UNDEFINED);
}
/**
- * Expands an existing TaskFragment to fill parent.
- * @param fragmentToken token of an existing TaskFragment.
- */
- void expandTaskFragment(IBinder fragmentToken) {
- WindowContainerTransaction wct = new WindowContainerTransaction();
- expandTaskFragment(wct, fragmentToken);
- applyTransaction(wct);
- }
-
- /**
* Expands an Activity to fill parent by moving it to a new TaskFragment.
* @param fragmentToken token to create new TaskFragment with.
* @param activity activity to move to the fill-parent TaskFragment.
*/
- void expandActivity(IBinder fragmentToken, Activity activity) {
- final WindowContainerTransaction wct = new WindowContainerTransaction();
+ void expandActivity(@NonNull WindowContainerTransaction wct, @NonNull IBinder fragmentToken,
+ @NonNull Activity activity) {
createTaskFragmentAndReparentActivity(
wct, fragmentToken, activity.getActivityToken(), new Rect(),
WINDOWING_MODE_UNDEFINED, activity);
- applyTransaction(wct);
}
/**
* @param ownerToken The token of the activity that creates this task fragment. It does not
* have to be a child of this task fragment, but must belong to the same task.
*/
- void createTaskFragment(WindowContainerTransaction wct, IBinder fragmentToken,
- IBinder ownerToken, @NonNull Rect bounds, @WindowingMode int windowingMode) {
+ void createTaskFragment(@NonNull WindowContainerTransaction wct, @NonNull IBinder fragmentToken,
+ @NonNull IBinder ownerToken, @NonNull Rect bounds, @WindowingMode int windowingMode) {
final TaskFragmentCreationParams fragmentOptions =
createFragmentOptions(fragmentToken, ownerToken, bounds, windowingMode);
wct.createTaskFragment(fragmentOptions);
@@ -191,9 +190,9 @@
* @param ownerToken The token of the activity that creates this task fragment. It does not
* have to be a child of this task fragment, but must belong to the same task.
*/
- private void createTaskFragmentAndReparentActivity(
- WindowContainerTransaction wct, IBinder fragmentToken, IBinder ownerToken,
- @NonNull Rect bounds, @WindowingMode int windowingMode, Activity activity) {
+ private void createTaskFragmentAndReparentActivity(@NonNull WindowContainerTransaction wct,
+ @NonNull IBinder fragmentToken, @NonNull IBinder ownerToken, @NonNull Rect bounds,
+ @WindowingMode int windowingMode, @NonNull Activity activity) {
createTaskFragment(wct, fragmentToken, ownerToken, bounds, windowingMode);
wct.reparentActivityToTaskFragment(fragmentToken, activity.getActivityToken());
}
@@ -202,9 +201,9 @@
* @param ownerToken The token of the activity that creates this task fragment. It does not
* have to be a child of this task fragment, but must belong to the same task.
*/
- private void createTaskFragmentAndStartActivity(
- WindowContainerTransaction wct, IBinder fragmentToken, IBinder ownerToken,
- @NonNull Rect bounds, @WindowingMode int windowingMode, Intent activityIntent,
+ private void createTaskFragmentAndStartActivity(@NonNull WindowContainerTransaction wct,
+ @NonNull IBinder fragmentToken, @NonNull IBinder ownerToken, @NonNull Rect bounds,
+ @WindowingMode int windowingMode, @NonNull Intent activityIntent,
@Nullable Bundle activityOptions) {
createTaskFragment(wct, fragmentToken, ownerToken, bounds, windowingMode);
wct.startActivityInTaskFragment(fragmentToken, ownerToken, activityIntent, activityOptions);
@@ -225,8 +224,8 @@
wct.setAdjacentTaskFragments(primary, secondary, adjacentParams);
}
- TaskFragmentCreationParams createFragmentOptions(IBinder fragmentToken, IBinder ownerToken,
- Rect bounds, @WindowingMode int windowingMode) {
+ TaskFragmentCreationParams createFragmentOptions(@NonNull IBinder fragmentToken,
+ @NonNull IBinder ownerToken, @NonNull Rect bounds, @WindowingMode int windowingMode) {
if (mFragmentInfos.containsKey(fragmentToken)) {
throw new IllegalArgumentException(
"There is an existing TaskFragment with fragmentToken=" + fragmentToken);
@@ -241,7 +240,7 @@
.build();
}
- void resizeTaskFragment(WindowContainerTransaction wct, IBinder fragmentToken,
+ void resizeTaskFragment(@NonNull WindowContainerTransaction wct, @NonNull IBinder fragmentToken,
@Nullable Rect bounds) {
if (!mFragmentInfos.containsKey(fragmentToken)) {
throw new IllegalArgumentException(
@@ -253,8 +252,8 @@
wct.setBounds(mFragmentInfos.get(fragmentToken).getToken(), bounds);
}
- void updateWindowingMode(WindowContainerTransaction wct, IBinder fragmentToken,
- @WindowingMode int windowingMode) {
+ void updateWindowingMode(@NonNull WindowContainerTransaction wct,
+ @NonNull IBinder fragmentToken, @WindowingMode int windowingMode) {
if (!mFragmentInfos.containsKey(fragmentToken)) {
throw new IllegalArgumentException(
"Can't find an existing TaskFragment with fragmentToken=" + fragmentToken);
@@ -262,7 +261,8 @@
wct.setWindowingMode(mFragmentInfos.get(fragmentToken).getToken(), windowingMode);
}
- void deleteTaskFragment(WindowContainerTransaction wct, IBinder fragmentToken) {
+ void deleteTaskFragment(@NonNull WindowContainerTransaction wct,
+ @NonNull IBinder fragmentToken) {
if (!mFragmentInfos.containsKey(fragmentToken)) {
throw new IllegalArgumentException(
"Can't find an existing TaskFragment with fragmentToken=" + fragmentToken);
@@ -272,59 +272,55 @@
@Override
public void onTaskFragmentAppeared(@NonNull TaskFragmentInfo taskFragmentInfo) {
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
final IBinder fragmentToken = taskFragmentInfo.getFragmentToken();
mFragmentInfos.put(fragmentToken, taskFragmentInfo);
-
- if (mCallback != null) {
- mCallback.onTaskFragmentAppeared(taskFragmentInfo);
- }
+ mCallback.onTaskFragmentAppeared(wct, taskFragmentInfo);
+ applyTransaction(wct);
}
@Override
public void onTaskFragmentInfoChanged(@NonNull TaskFragmentInfo taskFragmentInfo) {
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
final IBinder fragmentToken = taskFragmentInfo.getFragmentToken();
mFragmentInfos.put(fragmentToken, taskFragmentInfo);
-
- if (mCallback != null) {
- mCallback.onTaskFragmentInfoChanged(taskFragmentInfo);
- }
+ mCallback.onTaskFragmentInfoChanged(wct, taskFragmentInfo);
+ applyTransaction(wct);
}
@Override
public void onTaskFragmentVanished(@NonNull TaskFragmentInfo taskFragmentInfo) {
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
mFragmentInfos.remove(taskFragmentInfo.getFragmentToken());
-
- if (mCallback != null) {
- mCallback.onTaskFragmentVanished(taskFragmentInfo);
- }
+ mCallback.onTaskFragmentVanished(wct, taskFragmentInfo);
+ applyTransaction(wct);
}
@Override
public void onTaskFragmentParentInfoChanged(int taskId, @NonNull Configuration parentConfig) {
- if (mCallback != null) {
- mCallback.onTaskFragmentParentInfoChanged(taskId, parentConfig);
- }
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ mCallback.onTaskFragmentParentInfoChanged(wct, taskId, parentConfig);
+ applyTransaction(wct);
}
@Override
- public void onActivityReparentToTask(int taskId, @NonNull Intent activityIntent,
+ public void onActivityReparentedToTask(int taskId, @NonNull Intent activityIntent,
@NonNull IBinder activityToken) {
- if (mCallback != null) {
- mCallback.onActivityReparentToTask(taskId, activityIntent, activityToken);
- }
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ mCallback.onActivityReparentedToTask(wct, taskId, activityIntent, activityToken);
+ applyTransaction(wct);
}
@Override
public void onTaskFragmentError(@NonNull IBinder errorCallbackToken,
@Nullable TaskFragmentInfo taskFragmentInfo,
int opType, @NonNull Throwable exception) {
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
if (taskFragmentInfo != null) {
final IBinder fragmentToken = taskFragmentInfo.getFragmentToken();
mFragmentInfos.put(fragmentToken, taskFragmentInfo);
}
-
- if (mCallback != null) {
- mCallback.onTaskFragmentError(taskFragmentInfo, opType);
- }
+ mCallback.onTaskFragmentError(wct, taskFragmentInfo, opType);
+ applyTransaction(wct);
}
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java
index f09a910..c8ac0fc 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java
@@ -16,17 +16,21 @@
package androidx.window.extensions.embedding;
-import android.annotation.NonNull;
import android.app.Activity;
import android.util.Pair;
import android.util.Size;
+import androidx.annotation.NonNull;
+
/**
* Client-side descriptor of a split that holds two containers.
*/
class SplitContainer {
+ @NonNull
private final TaskFragmentContainer mPrimaryContainer;
+ @NonNull
private final TaskFragmentContainer mSecondaryContainer;
+ @NonNull
private final SplitRule mSplitRule;
SplitContainer(@NonNull TaskFragmentContainer primaryContainer,
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
index dad0739..0597809f 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -145,35 +145,36 @@
}
@Override
- public void onTaskFragmentAppeared(@NonNull TaskFragmentInfo taskFragmentInfo) {
+ public void onTaskFragmentAppeared(@NonNull WindowContainerTransaction wct,
+ @NonNull TaskFragmentInfo taskFragmentInfo) {
synchronized (mLock) {
TaskFragmentContainer container = getContainer(taskFragmentInfo.getFragmentToken());
if (container == null) {
return;
}
- container.setInfo(taskFragmentInfo);
+ container.setInfo(wct, taskFragmentInfo);
if (container.isFinished()) {
- mPresenter.cleanupContainer(container, false /* shouldFinishDependent */);
+ mPresenter.cleanupContainer(wct, container, false /* shouldFinishDependent */);
} else {
// Update with the latest Task configuration.
- mPresenter.updateContainer(container);
+ updateContainer(wct, container);
}
updateCallbackIfNecessary();
}
}
@Override
- public void onTaskFragmentInfoChanged(@NonNull TaskFragmentInfo taskFragmentInfo) {
+ public void onTaskFragmentInfoChanged(@NonNull WindowContainerTransaction wct,
+ @NonNull TaskFragmentInfo taskFragmentInfo) {
synchronized (mLock) {
TaskFragmentContainer container = getContainer(taskFragmentInfo.getFragmentToken());
if (container == null) {
return;
}
- final WindowContainerTransaction wct = new WindowContainerTransaction();
final boolean wasInPip = isInPictureInPicture(container);
- container.setInfo(taskFragmentInfo);
+ container.setInfo(wct, taskFragmentInfo);
final boolean isInPip = isInPictureInPicture(container);
// Check if there are no running activities - consider the container empty if there are
// no non-finishing activities left.
@@ -183,15 +184,15 @@
// Instead, the original split should be cleanup, and the dependent may be
// expanded to fullscreen.
cleanupForEnterPip(wct, container);
- mPresenter.cleanupContainer(container, false /* shouldFinishDependent */, wct);
+ mPresenter.cleanupContainer(wct, container, false /* shouldFinishDependent */);
} else if (taskFragmentInfo.isTaskClearedForReuse()) {
// Do not finish the dependents if this TaskFragment was cleared due to
// launching activity in the Task.
- mPresenter.cleanupContainer(container, false /* shouldFinishDependent */, wct);
+ mPresenter.cleanupContainer(wct, container, false /* shouldFinishDependent */);
} else if (!container.isWaitingActivityAppear()) {
// Do not finish the container before the expected activity appear until
// timeout.
- mPresenter.cleanupContainer(container, true /* shouldFinishDependent */, wct);
+ mPresenter.cleanupContainer(wct, container, true /* shouldFinishDependent */);
}
} else if (wasInPip && isInPip) {
// No update until exit PIP.
@@ -208,13 +209,13 @@
// needed.
updateContainer(wct, container);
}
- mPresenter.applyTransaction(wct);
updateCallbackIfNecessary();
}
}
@Override
- public void onTaskFragmentVanished(@NonNull TaskFragmentInfo taskFragmentInfo) {
+ public void onTaskFragmentVanished(@NonNull WindowContainerTransaction wct,
+ @NonNull TaskFragmentInfo taskFragmentInfo) {
synchronized (mLock) {
final TaskFragmentContainer container = getContainer(
taskFragmentInfo.getFragmentToken());
@@ -225,9 +226,7 @@
final TaskFragmentContainer newTopContainer = getTopActiveContainer(
container.getTaskId());
if (newTopContainer != null) {
- final WindowContainerTransaction wct = new WindowContainerTransaction();
updateContainer(wct, newTopContainer);
- mPresenter.applyTransaction(wct);
}
updateCallbackIfNecessary();
}
@@ -236,7 +235,8 @@
}
@Override
- public void onTaskFragmentParentInfoChanged(int taskId, @NonNull Configuration parentConfig) {
+ public void onTaskFragmentParentInfoChanged(@NonNull WindowContainerTransaction wct,
+ int taskId, @NonNull Configuration parentConfig) {
synchronized (mLock) {
onTaskConfigurationChanged(taskId, parentConfig);
if (isInPictureInPicture(parentConfig)) {
@@ -256,7 +256,7 @@
final TaskFragmentContainer container = containers.get(i);
// Wait until onTaskFragmentAppeared to update new container.
if (!container.isFinished() && !container.isWaitingActivityAppear()) {
- mPresenter.updateContainer(container);
+ updateContainer(wct, container);
}
}
updateCallbackIfNecessary();
@@ -264,7 +264,8 @@
}
@Override
- public void onActivityReparentToTask(int taskId, @NonNull Intent activityIntent,
+ public void onActivityReparentedToTask(@NonNull WindowContainerTransaction wct,
+ int taskId, @NonNull Intent activityIntent,
@NonNull IBinder activityToken) {
synchronized (mLock) {
// If the activity belongs to the current app process, we treat it as a new activity
@@ -275,10 +276,10 @@
// launching to top. We allow split as primary for activity reparent because the
// activity may be split as primary before it is reparented out. In that case, we
// want to show it as primary again when it is reparented back.
- if (!resolveActivityToContainer(activity, true /* isOnReparent */)) {
+ if (!resolveActivityToContainer(wct, activity, true /* isOnReparent */)) {
// When there is no embedding rule matched, try to place it in the top container
// like a normal launch.
- placeActivityInTopContainer(activity);
+ placeActivityInTopContainer(wct, activity);
}
updateCallbackIfNecessary();
return;
@@ -293,7 +294,6 @@
// If the activity belongs to a different app process, we treat it as starting new
// intent, since both actions might result in a new activity that should appear in an
// organized TaskFragment.
- final WindowContainerTransaction wct = new WindowContainerTransaction();
TaskFragmentContainer targetContainer = resolveStartActivityIntent(wct, taskId,
activityIntent, null /* launchingActivity */);
if (targetContainer == null) {
@@ -306,14 +306,14 @@
}
wct.reparentActivityToTaskFragment(targetContainer.getTaskFragmentToken(),
activityToken);
- mPresenter.applyTransaction(wct);
// Because the activity does not belong to the organizer process, we wait until
// onTaskFragmentAppeared to trigger updateCallbackIfNecessary().
}
}
@Override
- public void onTaskFragmentError(@Nullable TaskFragmentInfo taskFragmentInfo, int opType) {
+ public void onTaskFragmentError(@NonNull WindowContainerTransaction wct,
+ @Nullable TaskFragmentInfo taskFragmentInfo, int opType) {
synchronized (mLock) {
switch (opType) {
case HIERARCHY_OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT:
@@ -329,10 +329,11 @@
}
// Update the latest taskFragmentInfo and perform necessary clean-up
- container.setInfo(taskFragmentInfo);
+ container.setInfo(wct, taskFragmentInfo);
container.clearPendingAppearedActivities();
if (container.isEmpty()) {
- mPresenter.cleanupContainer(container, false /* shouldFinishDependent */);
+ mPresenter.cleanupContainer(wct, container,
+ false /* shouldFinishDependent */);
}
break;
}
@@ -343,7 +344,7 @@
}
}
- /** Called on receiving {@link #onTaskFragmentVanished(TaskFragmentInfo)} for cleanup. */
+ /** Called on receiving {@link #onTaskFragmentVanished} for cleanup. */
private void cleanupTaskFragment(@NonNull IBinder taskFragmentToken) {
for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
final TaskContainer taskContainer = mTaskContainers.valueAt(i);
@@ -422,10 +423,12 @@
}
@VisibleForTesting
- void onActivityCreated(@NonNull Activity launchedActivity) {
+ @GuardedBy("mLock")
+ void onActivityCreated(@NonNull WindowContainerTransaction wct,
+ @NonNull Activity launchedActivity) {
// TODO(b/229680885): we don't support launching into primary yet because we want to always
// launch the new activity on top.
- resolveActivityToContainer(launchedActivity, false /* isOnReparent */);
+ resolveActivityToContainer(wct, launchedActivity, false /* isOnReparent */);
updateCallbackIfNecessary();
}
@@ -440,7 +443,8 @@
*/
@VisibleForTesting
@GuardedBy("mLock")
- boolean resolveActivityToContainer(@NonNull Activity activity, boolean isOnReparent) {
+ boolean resolveActivityToContainer(@NonNull WindowContainerTransaction wct,
+ @NonNull Activity activity, boolean isOnReparent) {
if (isInPictureInPicture(activity) || activity.isFinishing()) {
// We don't embed activity when it is in PIP, or finishing. Return true since we don't
// want any extra handling.
@@ -472,12 +476,12 @@
// 1. Whether the new launched activity should always expand.
if (shouldExpand(activity, null /* intent */)) {
- expandActivity(activity);
+ expandActivity(wct, activity);
return true;
}
// 2. Whether the new launched activity should launch a placeholder.
- if (launchPlaceholderIfNecessary(activity, !isOnReparent)) {
+ if (launchPlaceholderIfNecessary(wct, activity, !isOnReparent)) {
return true;
}
@@ -492,11 +496,11 @@
// Can't find any activity below.
return false;
}
- if (putActivitiesIntoSplitIfNecessary(activityBelow, activity)) {
+ if (putActivitiesIntoSplitIfNecessary(wct, activityBelow, activity)) {
// Have split rule of [ activityBelow | launchedActivity ].
return true;
}
- if (isOnReparent && putActivitiesIntoSplitIfNecessary(activity, activityBelow)) {
+ if (isOnReparent && putActivitiesIntoSplitIfNecessary(wct, activity, activityBelow)) {
// Have split rule of [ launchedActivity | activityBelow].
return true;
}
@@ -519,19 +523,20 @@
// Can't find the top activity on the other split TaskFragment.
return false;
}
- if (putActivitiesIntoSplitIfNecessary(otherTopActivity, activity)) {
+ if (putActivitiesIntoSplitIfNecessary(wct, otherTopActivity, activity)) {
// Have split rule of [ otherTopActivity | launchedActivity ].
return true;
}
// Have split rule of [ launchedActivity | otherTopActivity].
- return isOnReparent && putActivitiesIntoSplitIfNecessary(activity, otherTopActivity);
+ return isOnReparent && putActivitiesIntoSplitIfNecessary(wct, activity, otherTopActivity);
}
/**
* Places the given activity to the top most TaskFragment in the task if there is any.
*/
@VisibleForTesting
- void placeActivityInTopContainer(@NonNull Activity activity) {
+ void placeActivityInTopContainer(@NonNull WindowContainerTransaction wct,
+ @NonNull Activity activity) {
if (getContainerWithActivity(activity) != null) {
// The activity has already been put in a TaskFragment. This is likely to be done by
// the server when the activity is started.
@@ -547,20 +552,20 @@
return;
}
targetContainer.addPendingAppearedActivity(activity);
- final WindowContainerTransaction wct = new WindowContainerTransaction();
wct.reparentActivityToTaskFragment(targetContainer.getTaskFragmentToken(),
activity.getActivityToken());
- mPresenter.applyTransaction(wct);
}
/**
* Starts an activity to side of the launchingActivity with the provided split config.
*/
- private void startActivityToSide(@NonNull Activity launchingActivity, @NonNull Intent intent,
+ @GuardedBy("mLock")
+ private void startActivityToSide(@NonNull WindowContainerTransaction wct,
+ @NonNull Activity launchingActivity, @NonNull Intent intent,
@Nullable Bundle options, @NonNull SplitRule sideRule,
@Nullable Consumer<Exception> failureCallback, boolean isPlaceholder) {
try {
- mPresenter.startActivityToSide(launchingActivity, intent, options, sideRule,
+ mPresenter.startActivityToSide(wct, launchingActivity, intent, options, sideRule,
isPlaceholder);
} catch (Exception e) {
if (failureCallback != null) {
@@ -573,15 +578,17 @@
* Expands the given activity by either expanding the TaskFragment it is currently in or putting
* it into a new expanded TaskFragment.
*/
- private void expandActivity(@NonNull Activity activity) {
+ @GuardedBy("mLock")
+ private void expandActivity(@NonNull WindowContainerTransaction wct,
+ @NonNull Activity activity) {
final TaskFragmentContainer container = getContainerWithActivity(activity);
if (shouldContainerBeExpanded(container)) {
// Make sure that the existing container is expanded.
- mPresenter.expandTaskFragment(container.getTaskFragmentToken());
+ mPresenter.expandTaskFragment(wct, container.getTaskFragmentToken());
} else {
// Put activity into a new expanded container.
final TaskFragmentContainer newContainer = newContainer(activity, getTaskId(activity));
- mPresenter.expandActivity(newContainer.getTaskFragmentToken(), activity);
+ mPresenter.expandActivity(wct, newContainer.getTaskFragmentToken(), activity);
}
}
@@ -667,8 +674,8 @@
* and returns {@code true}. Otherwise, returns {@code false}.
*/
@GuardedBy("mLock")
- private boolean putActivitiesIntoSplitIfNecessary(@NonNull Activity primaryActivity,
- @NonNull Activity secondaryActivity) {
+ private boolean putActivitiesIntoSplitIfNecessary(@NonNull WindowContainerTransaction wct,
+ @NonNull Activity primaryActivity, @NonNull Activity secondaryActivity) {
final SplitPairRule splitRule = getSplitRule(primaryActivity, secondaryActivity);
if (splitRule == null) {
return false;
@@ -686,23 +693,23 @@
return true;
}
secondaryContainer.addPendingAppearedActivity(secondaryActivity);
- final WindowContainerTransaction wct = new WindowContainerTransaction();
if (mPresenter.expandSplitContainerIfNeeded(wct, splitContainer, primaryActivity,
secondaryActivity, null /* secondaryIntent */)
!= RESULT_EXPAND_FAILED_NO_TF_INFO) {
wct.reparentActivityToTaskFragment(
secondaryContainer.getTaskFragmentToken(),
secondaryActivity.getActivityToken());
- mPresenter.applyTransaction(wct);
return true;
}
}
// Create new split pair.
- mPresenter.createNewSplitContainer(primaryActivity, secondaryActivity, splitRule);
+ mPresenter.createNewSplitContainer(wct, primaryActivity, secondaryActivity, splitRule);
return true;
}
- private void onActivityConfigurationChanged(@NonNull Activity activity) {
+ @GuardedBy("mLock")
+ private void onActivityConfigurationChanged(@NonNull WindowContainerTransaction wct,
+ @NonNull Activity activity) {
if (activity.isFinishing()) {
// Do nothing if the activity is currently finishing.
return;
@@ -721,7 +728,7 @@
}
// Check if activity requires a placeholder
- launchPlaceholderIfNecessary(activity, false /* isOnCreated */);
+ launchPlaceholderIfNecessary(wct, activity, false /* isOnCreated */);
}
@VisibleForTesting
@@ -741,7 +748,22 @@
* creation.
*/
void onTaskFragmentAppearEmptyTimeout(@NonNull TaskFragmentContainer container) {
- mPresenter.cleanupContainer(container, false /* shouldFinishDependent */);
+ synchronized (mLock) {
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ onTaskFragmentAppearEmptyTimeout(wct, container);
+ mPresenter.applyTransaction(wct);
+ }
+ }
+
+ /**
+ * Called when we have been waiting too long for the TaskFragment to become non-empty after
+ * creation.
+ */
+ void onTaskFragmentAppearEmptyTimeout(@NonNull WindowContainerTransaction wct,
+ @NonNull TaskFragmentContainer container) {
+ synchronized (mLock) {
+ mPresenter.cleanupContainer(wct, container, false /* shouldFinishDependent */);
+ }
}
/**
@@ -971,6 +993,7 @@
}
/** Cleanups all the dependencies when the TaskFragment is entering PIP. */
+ @GuardedBy("mLock")
private void cleanupForEnterPip(@NonNull WindowContainerTransaction wct,
@NonNull TaskFragmentContainer container) {
final TaskContainer taskContainer = container.getTaskContainer();
@@ -1084,9 +1107,10 @@
* Updates the presentation of the container. If the container is part of the split or should
* have a placeholder, it will also update the other part of the split.
*/
+ @GuardedBy("mLock")
void updateContainer(@NonNull WindowContainerTransaction wct,
@NonNull TaskFragmentContainer container) {
- if (launchPlaceholderIfNecessary(container)) {
+ if (launchPlaceholderIfNecessary(wct, container)) {
// Placeholder was launched, the positions will be updated when the activity is added
// to the secondary container.
return;
@@ -1111,7 +1135,7 @@
// Skip position update - one or both containers are finished.
return;
}
- if (dismissPlaceholderIfNecessary(splitContainer)) {
+ if (dismissPlaceholderIfNecessary(wct, splitContainer)) {
// Placeholder was finished, the positions will be updated when its container is emptied
return;
}
@@ -1173,16 +1197,20 @@
/**
* Checks if the container requires a placeholder and launches it if necessary.
*/
- private boolean launchPlaceholderIfNecessary(@NonNull TaskFragmentContainer container) {
+ @GuardedBy("mLock")
+ private boolean launchPlaceholderIfNecessary(@NonNull WindowContainerTransaction wct,
+ @NonNull TaskFragmentContainer container) {
final Activity topActivity = container.getTopNonFinishingActivity();
if (topActivity == null) {
return false;
}
- return launchPlaceholderIfNecessary(topActivity, false /* isOnCreated */);
+ return launchPlaceholderIfNecessary(wct, topActivity, false /* isOnCreated */);
}
- boolean launchPlaceholderIfNecessary(@NonNull Activity activity, boolean isOnCreated) {
+ @GuardedBy("mLock")
+ boolean launchPlaceholderIfNecessary(@NonNull WindowContainerTransaction wct,
+ @NonNull Activity activity, boolean isOnCreated) {
if (activity.isFinishing()) {
return false;
}
@@ -1216,7 +1244,7 @@
// TODO(b/190433398): Handle failed request
final Bundle options = getPlaceholderOptions(activity, isOnCreated);
- startActivityToSide(activity, placeholderRule.getPlaceholderIntent(), options,
+ startActivityToSide(wct, activity, placeholderRule.getPlaceholderIntent(), options,
placeholderRule, null /* failureCallback */, true /* isPlaceholder */);
return true;
}
@@ -1243,7 +1271,9 @@
}
@VisibleForTesting
- boolean dismissPlaceholderIfNecessary(@NonNull SplitContainer splitContainer) {
+ @GuardedBy("mLock")
+ boolean dismissPlaceholderIfNecessary(@NonNull WindowContainerTransaction wct,
+ @NonNull SplitContainer splitContainer) {
if (!splitContainer.isPlaceholderContainer()) {
return false;
}
@@ -1257,7 +1287,7 @@
return false;
}
- mPresenter.cleanupContainer(splitContainer.getSecondaryContainer(),
+ mPresenter.cleanupContainer(wct, splitContainer.getSecondaryContainer(),
false /* shouldFinishDependent */);
return true;
}
@@ -1523,7 +1553,8 @@
private final class LifecycleCallbacks extends EmptyLifecycleCallbacksAdapter {
@Override
- public void onActivityPreCreated(Activity activity, Bundle savedInstanceState) {
+ public void onActivityPreCreated(@NonNull Activity activity,
+ @Nullable Bundle savedInstanceState) {
synchronized (mLock) {
final IBinder activityToken = activity.getActivityToken();
final IBinder initialTaskFragmentToken = getInitialTaskFragmentToken(activity);
@@ -1552,25 +1583,30 @@
}
@Override
- public void onActivityPostCreated(Activity activity, Bundle savedInstanceState) {
+ public void onActivityPostCreated(@NonNull Activity activity,
+ @Nullable Bundle savedInstanceState) {
// Calling after Activity#onCreate is complete to allow the app launch something
// first. In case of a configured placeholder activity we want to make sure
// that we don't launch it if an activity itself already requested something to be
// launched to side.
synchronized (mLock) {
- SplitController.this.onActivityCreated(activity);
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ SplitController.this.onActivityCreated(wct, activity);
+ mPresenter.applyTransaction(wct);
}
}
@Override
- public void onActivityConfigurationChanged(Activity activity) {
+ public void onActivityConfigurationChanged(@NonNull Activity activity) {
synchronized (mLock) {
- SplitController.this.onActivityConfigurationChanged(activity);
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ SplitController.this.onActivityConfigurationChanged(wct, activity);
+ mPresenter.applyTransaction(wct);
}
}
@Override
- public void onActivityPostDestroyed(Activity activity) {
+ public void onActivityPostDestroyed(@NonNull Activity activity) {
synchronized (mLock) {
SplitController.this.onActivityDestroyed(activity);
}
@@ -1582,7 +1618,7 @@
private final Handler mHandler = new Handler(Looper.getMainLooper());
@Override
- public void execute(Runnable r) {
+ public void execute(@NonNull Runnable r) {
mHandler.post(r);
}
}
@@ -1662,7 +1698,7 @@
* If the two rules have the same presentation, we can reuse the same {@link SplitContainer} if
* there is any.
*/
- private static boolean canReuseContainer(SplitRule rule1, SplitRule rule2) {
+ private static boolean canReuseContainer(@NonNull SplitRule rule1, @NonNull SplitRule rule2) {
if (!isContainerReusableRule(rule1) || !isContainerReusableRule(rule2)) {
return false;
}
@@ -1670,7 +1706,8 @@
}
/** Whether the two rules have the same presentation. */
- private static boolean haveSamePresentation(SplitPairRule rule1, SplitPairRule rule2) {
+ private static boolean haveSamePresentation(@NonNull SplitPairRule rule1,
+ @NonNull SplitPairRule rule2) {
// TODO(b/231655482): add util method to do the comparison in SplitPairRule.
return rule1.getSplitRatio() == rule2.getSplitRatio()
&& rule1.getLayoutDirection() == rule2.getLayoutDirection()
@@ -1684,7 +1721,7 @@
* Whether it is ok for other rule to reuse the {@link TaskFragmentContainer} of the given
* rule.
*/
- private static boolean isContainerReusableRule(SplitRule rule) {
+ private static boolean isContainerReusableRule(@NonNull SplitRule rule) {
// We don't expect to reuse the placeholder rule.
if (!(rule instanceof SplitPairRule)) {
return false;
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
index a89847a..2b069d7 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
@@ -102,37 +102,18 @@
private final SplitController mController;
- SplitPresenter(@NonNull Executor executor, SplitController controller) {
+ SplitPresenter(@NonNull Executor executor, @NonNull SplitController controller) {
super(executor, controller);
mController = controller;
registerOrganizer();
}
/**
- * Updates the presentation of the provided container.
- */
- void updateContainer(@NonNull TaskFragmentContainer container) {
- final WindowContainerTransaction wct = new WindowContainerTransaction();
- mController.updateContainer(wct, container);
- applyTransaction(wct);
- }
-
- /**
* Deletes the specified container and all other associated and dependent containers in the same
* transaction.
*/
- void cleanupContainer(@NonNull TaskFragmentContainer container, boolean shouldFinishDependent) {
- final WindowContainerTransaction wct = new WindowContainerTransaction();
- cleanupContainer(container, shouldFinishDependent, wct);
- applyTransaction(wct);
- }
-
- /**
- * Deletes the specified container and all other associated and dependent containers in the same
- * transaction.
- */
- void cleanupContainer(@NonNull TaskFragmentContainer container, boolean shouldFinishDependent,
- @NonNull WindowContainerTransaction wct) {
+ void cleanupContainer(@NonNull WindowContainerTransaction wct,
+ @NonNull TaskFragmentContainer container, boolean shouldFinishDependent) {
container.finish(shouldFinishDependent, this, wct, mController);
final TaskFragmentContainer newTopContainer = mController.getTopActiveContainer(
@@ -190,10 +171,9 @@
* created and the activity will be re-parented to it.
* @param rule The split rule to be applied to the container.
*/
- void createNewSplitContainer(@NonNull Activity primaryActivity,
- @NonNull Activity secondaryActivity, @NonNull SplitPairRule rule) {
- final WindowContainerTransaction wct = new WindowContainerTransaction();
-
+ void createNewSplitContainer(@NonNull WindowContainerTransaction wct,
+ @NonNull Activity primaryActivity, @NonNull Activity secondaryActivity,
+ @NonNull SplitPairRule rule) {
final Rect parentBounds = getParentContainerBounds(primaryActivity);
final Pair<Size, Size> minDimensionsPair = getActivitiesMinDimensionsPair(primaryActivity,
secondaryActivity);
@@ -219,8 +199,6 @@
minDimensionsPair);
mController.registerSplit(wct, primaryContainer, primaryActivity, secondaryContainer, rule);
-
- applyTransaction(wct);
}
/**
@@ -262,7 +240,8 @@
* @param rule The split rule to be applied to the container.
* @param isPlaceholder Whether the launch is a placeholder.
*/
- void startActivityToSide(@NonNull Activity launchingActivity, @NonNull Intent activityIntent,
+ void startActivityToSide(@NonNull WindowContainerTransaction wct,
+ @NonNull Activity launchingActivity, @NonNull Intent activityIntent,
@Nullable Bundle activityOptions, @NonNull SplitRule rule, boolean isPlaceholder) {
final Rect parentBounds = getParentContainerBounds(launchingActivity);
final Pair<Size, Size> minDimensionsPair = getActivityIntentMinDimensionsPair(
@@ -284,7 +263,6 @@
launchingActivity, taskId);
final int windowingMode = mController.getTaskContainer(taskId)
.getWindowingModeForSplitTaskFragment(primaryRectBounds);
- final WindowContainerTransaction wct = new WindowContainerTransaction();
mController.registerSplit(wct, primaryContainer, launchingActivity, secondaryContainer,
rule);
startActivityToSide(wct, primaryContainer.getTaskFragmentToken(), primaryRectBounds,
@@ -294,7 +272,6 @@
// When placeholder is launched in split, we should keep the focus on the primary.
wct.requestFocusOnTaskFragment(primaryContainer.getTaskFragmentToken());
}
- applyTransaction(wct);
}
/**
@@ -502,14 +479,14 @@
}
@NonNull
- static Pair<Size, Size> getActivitiesMinDimensionsPair(Activity primaryActivity,
- Activity secondaryActivity) {
+ static Pair<Size, Size> getActivitiesMinDimensionsPair(@NonNull Activity primaryActivity,
+ @NonNull Activity secondaryActivity) {
return new Pair<>(getMinDimensions(primaryActivity), getMinDimensions(secondaryActivity));
}
@NonNull
- static Pair<Size, Size> getActivityIntentMinDimensionsPair(Activity primaryActivity,
- Intent secondaryIntent) {
+ static Pair<Size, Size> getActivityIntentMinDimensionsPair(@NonNull Activity primaryActivity,
+ @NonNull Intent secondaryIntent) {
return new Pair<>(getMinDimensions(primaryActivity), getMinDimensions(secondaryIntent));
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
index 0ea5603..77e26c0 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
@@ -21,8 +21,6 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.app.Activity;
import android.app.WindowConfiguration;
import android.app.WindowConfiguration.WindowingMode;
@@ -31,6 +29,9 @@
import android.util.ArraySet;
import android.window.TaskFragmentInfo;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationController.java
index f721341..ee2e139 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationController.java
@@ -30,6 +30,8 @@
import android.view.RemoteAnimationDefinition;
import android.window.TaskFragmentOrganizer;
+import androidx.annotation.NonNull;
+
import com.android.internal.annotations.VisibleForTesting;
/** Controls the TaskFragment remote animations. */
@@ -45,7 +47,7 @@
/** Task Ids that we have registered for remote animation. */
private final ArraySet<Integer> mRegisterTasks = new ArraySet<>();
- TaskFragmentAnimationController(TaskFragmentOrganizer organizer) {
+ TaskFragmentAnimationController(@NonNull TaskFragmentOrganizer organizer) {
mOrganizer = organizer;
mDefinition = new RemoteAnimationDefinition();
final RemoteAnimationAdapter animationAdapter =
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java
index c4f3709..8af2d9c 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java
@@ -112,6 +112,7 @@
}
/** Creates the animator given the transition type and windows. */
+ @NonNull
private Animator createAnimator(@WindowManager.TransitionOldType int transit,
@NonNull RemoteAnimationTarget[] targets,
@NonNull IRemoteAnimationFinishedCallback finishedCallback) {
@@ -161,6 +162,7 @@
}
/** List of {@link TaskFragmentAnimationAdapter} to handle animations on all window targets. */
+ @NonNull
private List<TaskFragmentAnimationAdapter> createAnimationAdapters(
@WindowManager.TransitionOldType int transit,
@NonNull RemoteAnimationTarget[] targets) {
@@ -180,12 +182,14 @@
}
}
+ @NonNull
private List<TaskFragmentAnimationAdapter> createOpenAnimationAdapters(
@NonNull RemoteAnimationTarget[] targets) {
return createOpenCloseAnimationAdapters(targets, true /* isOpening */,
mAnimationSpec::loadOpenAnimation);
}
+ @NonNull
private List<TaskFragmentAnimationAdapter> createCloseAnimationAdapters(
@NonNull RemoteAnimationTarget[] targets) {
return createOpenCloseAnimationAdapters(targets, false /* isOpening */,
@@ -196,6 +200,7 @@
* Creates {@link TaskFragmentAnimationAdapter} for OPEN and CLOSE types of transition.
* @param isOpening {@code true} for OPEN type, {@code false} for CLOSE type.
*/
+ @NonNull
private List<TaskFragmentAnimationAdapter> createOpenCloseAnimationAdapters(
@NonNull RemoteAnimationTarget[] targets, boolean isOpening,
@NonNull BiFunction<RemoteAnimationTarget, Rect, Animation> animationProvider) {
@@ -238,6 +243,7 @@
return adapters;
}
+ @NonNull
private TaskFragmentAnimationAdapter createOpenCloseAnimationAdapter(
@NonNull RemoteAnimationTarget target,
@NonNull BiFunction<RemoteAnimationTarget, Rect, Animation> animationProvider,
@@ -259,6 +265,7 @@
return new TaskFragmentAnimationAdapter(animation, target);
}
+ @NonNull
private List<TaskFragmentAnimationAdapter> createChangeAnimationAdapters(
@NonNull RemoteAnimationTarget[] targets) {
final List<TaskFragmentAnimationAdapter> adapters = new ArrayList<>();
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationSpec.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationSpec.java
index 5cc496a..97d42391b 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationSpec.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationSpec.java
@@ -26,6 +26,7 @@
import android.os.Handler;
import android.provider.Settings;
import android.view.RemoteAnimationTarget;
+import android.view.WindowManager;
import android.view.animation.AlphaAnimation;
import android.view.animation.Animation;
import android.view.animation.AnimationSet;
@@ -68,16 +69,14 @@
// The transition animation should be adjusted based on the developer option.
final ContentResolver resolver = mContext.getContentResolver();
- mTransitionAnimationScaleSetting = Settings.Global.getFloat(resolver,
- Settings.Global.TRANSITION_ANIMATION_SCALE,
- mContext.getResources().getFloat(
- R.dimen.config_appTransitionAnimationDurationScaleDefault));
+ mTransitionAnimationScaleSetting = getTransitionAnimationScaleSetting();
resolver.registerContentObserver(
Settings.Global.getUriFor(Settings.Global.TRANSITION_ANIMATION_SCALE), false,
new SettingsObserver(handler));
}
/** For target that doesn't need to be animated. */
+ @NonNull
static Animation createNoopAnimation(@NonNull RemoteAnimationTarget target) {
// Noop but just keep the target showing/hiding.
final float alpha = target.mode == MODE_CLOSING ? 0f : 1f;
@@ -85,6 +84,7 @@
}
/** Animation for target that is opening in a change transition. */
+ @NonNull
Animation createChangeBoundsOpenAnimation(@NonNull RemoteAnimationTarget target) {
final Rect bounds = target.localBounds;
// The target will be animated in from left or right depends on its position.
@@ -101,6 +101,7 @@
}
/** Animation for target that is closing in a change transition. */
+ @NonNull
Animation createChangeBoundsCloseAnimation(@NonNull RemoteAnimationTarget target) {
final Rect bounds = target.localBounds;
// The target will be animated out to left or right depends on its position.
@@ -121,6 +122,7 @@
* @return the return array always has two elements. The first one is for the start leash, and
* the second one is for the end leash.
*/
+ @NonNull
Animation[] createChangeBoundsChangeAnimations(@NonNull RemoteAnimationTarget target) {
// Both start bounds and end bounds are in screen coordinates. We will post translate
// to the local coordinates in TaskFragmentAnimationAdapter#onAnimationUpdate
@@ -177,6 +179,7 @@
return new Animation[]{startSet, endSet};
}
+ @NonNull
Animation loadOpenAnimation(@NonNull RemoteAnimationTarget target,
@NonNull Rect wholeAnimationBounds) {
final boolean isEnter = target.mode != MODE_CLOSING;
@@ -198,6 +201,7 @@
return animation;
}
+ @NonNull
Animation loadCloseAnimation(@NonNull RemoteAnimationTarget target,
@NonNull Rect wholeAnimationBounds) {
final boolean isEnter = target.mode != MODE_CLOSING;
@@ -217,6 +221,12 @@
return animation;
}
+ private float getTransitionAnimationScaleSetting() {
+ return WindowManager.fixScale(Settings.Global.getFloat(mContext.getContentResolver(),
+ Settings.Global.TRANSITION_ANIMATION_SCALE, mContext.getResources().getFloat(
+ R.dimen.config_appTransitionAnimationDurationScaleDefault)));
+ }
+
private class SettingsObserver extends ContentObserver {
SettingsObserver(@NonNull Handler handler) {
super(handler);
@@ -224,9 +234,7 @@
@Override
public void onChange(boolean selfChange) {
- mTransitionAnimationScaleSetting = Settings.Global.getFloat(
- mContext.getContentResolver(), Settings.Global.TRANSITION_ANIMATION_SCALE,
- mTransitionAnimationScaleSetting);
+ mTransitionAnimationScaleSetting = getTransitionAnimationScaleSetting();
}
}
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
index 37f5b6d..11c0db3 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
@@ -18,8 +18,6 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.app.Activity;
import android.app.WindowConfiguration.WindowingMode;
import android.content.Intent;
@@ -30,6 +28,9 @@
import android.window.TaskFragmentInfo;
import android.window.WindowContainerTransaction;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
import com.android.internal.annotations.VisibleForTesting;
import java.util.ArrayList;
@@ -175,6 +176,7 @@
&& mInfo.getActivities().size() == collectNonFinishingActivities().size();
}
+ @NonNull
ActivityStack toActivityStack() {
return new ActivityStack(collectNonFinishingActivities(), isEmpty());
}
@@ -249,19 +251,22 @@
return mInfo;
}
- void setInfo(@NonNull TaskFragmentInfo info) {
+ void setInfo(@NonNull WindowContainerTransaction wct, @NonNull TaskFragmentInfo info) {
if (!mIsFinished && mInfo == null && info.isEmpty()) {
// onTaskFragmentAppeared with empty info. We will remove the TaskFragment if no
// pending appeared intent/activities. Otherwise, wait and removing the TaskFragment if
// it is still empty after timeout.
- mAppearEmptyTimeout = () -> {
- mAppearEmptyTimeout = null;
- mController.onTaskFragmentAppearEmptyTimeout(this);
- };
if (mPendingAppearedIntent != null || !mPendingAppearedActivities.isEmpty()) {
+ mAppearEmptyTimeout = () -> {
+ mAppearEmptyTimeout = null;
+ // Call without the pass-in wct when timeout. We need to applyWct directly
+ // in this case.
+ mController.onTaskFragmentAppearEmptyTimeout(this);
+ };
mController.getHandler().postDelayed(mAppearEmptyTimeout, APPEAR_EMPTY_TIMEOUT_MS);
} else {
- mAppearEmptyTimeout.run();
+ mAppearEmptyTimeout = null;
+ mController.onTaskFragmentAppearEmptyTimeout(wct, this);
}
} else if (mAppearEmptyTimeout != null && !info.isEmpty()) {
mController.getHandler().removeCallbacks(mAppearEmptyTimeout);
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java
index 4d25952..21cf7a6 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java
@@ -56,6 +56,8 @@
* Build/Install/Run:
* atest WMJetpackUnitTests:JetpackTaskFragmentOrganizerTest
*/
+// Suppress GuardedBy warning on unit tests
+@SuppressWarnings("GuardedBy")
@Presubmit
@SmallTest
@RunWith(AndroidJUnit4.class)
@@ -119,7 +121,7 @@
new Intent(), taskContainer, mSplitController);
final TaskFragmentInfo info = createMockInfo(container);
mOrganizer.mFragmentInfos.put(container.getTaskFragmentToken(), info);
- container.setInfo(info);
+ container.setInfo(mTransaction, info);
mOrganizer.expandTaskFragment(mTransaction, container.getTaskFragmentToken());
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
index 4bc5033..07758d2 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
@@ -89,6 +89,8 @@
* Build/Install/Run:
* atest WMJetpackUnitTests:SplitControllerTest
*/
+// Suppress GuardedBy warning on unit tests
+@SuppressWarnings("GuardedBy")
@Presubmit
@SmallTest
@RunWith(AndroidJUnit4.class)
@@ -158,14 +160,14 @@
final TaskFragmentInfo info = mock(TaskFragmentInfo.class);
doReturn(new ArrayList<>()).when(info).getActivities();
doReturn(true).when(info).isEmpty();
- tf1.setInfo(info);
+ tf1.setInfo(mTransaction, info);
assertWithMessage("Must return tf because we are waiting for tf1 to become non-empty after"
+ " creation.")
.that(mSplitController.getTopActiveContainer(TASK_ID)).isEqualTo(tf1);
doReturn(false).when(info).isEmpty();
- tf1.setInfo(info);
+ tf1.setInfo(mTransaction, info);
assertWithMessage("Must return null because tf1 becomes empty.")
.that(mSplitController.getTopActiveContainer(TASK_ID)).isNull();
@@ -177,7 +179,7 @@
doReturn(tf.getTaskFragmentToken()).when(mInfo).getFragmentToken();
// The TaskFragment has been removed in the server, we only need to cleanup the reference.
- mSplitController.onTaskFragmentVanished(mInfo);
+ mSplitController.onTaskFragmentVanished(mTransaction, mInfo);
verify(mSplitPresenter, never()).deleteTaskFragment(any(), any());
verify(mSplitController).removeContainer(tf);
@@ -187,9 +189,10 @@
@Test
public void testOnTaskFragmentAppearEmptyTimeout() {
final TaskFragmentContainer tf = mSplitController.newContainer(mActivity, TASK_ID);
- mSplitController.onTaskFragmentAppearEmptyTimeout(tf);
+ mSplitController.onTaskFragmentAppearEmptyTimeout(mTransaction, tf);
- verify(mSplitPresenter).cleanupContainer(tf, false /* shouldFinishDependent */);
+ verify(mSplitPresenter).cleanupContainer(mTransaction, tf,
+ false /* shouldFinishDependent */);
}
@Test
@@ -229,8 +232,8 @@
spyOn(tf);
doReturn(mActivity).when(tf).getTopNonFinishingActivity();
doReturn(true).when(tf).isEmpty();
- doReturn(true).when(mSplitController).launchPlaceholderIfNecessary(mActivity,
- false /* isOnCreated */);
+ doReturn(true).when(mSplitController).launchPlaceholderIfNecessary(mTransaction,
+ mActivity, false /* isOnCreated */);
doNothing().when(mSplitPresenter).updateSplitContainer(any(), any(), any());
mSplitController.updateContainer(mTransaction, tf);
@@ -250,7 +253,7 @@
mSplitController.updateContainer(mTransaction, tf);
- verify(mSplitController, never()).dismissPlaceholderIfNecessary(any());
+ verify(mSplitController, never()).dismissPlaceholderIfNecessary(any(), any());
// Verify if tf is not in the top splitContainer,
final SplitContainer splitContainer = mock(SplitContainer.class);
@@ -264,7 +267,7 @@
mSplitController.updateContainer(mTransaction, tf);
- verify(mSplitController, never()).dismissPlaceholderIfNecessary(any());
+ verify(mSplitController, never()).dismissPlaceholderIfNecessary(any(), any());
// Verify if one or both containers in the top SplitContainer are finished,
// dismissPlaceholder() won't be called.
@@ -273,12 +276,12 @@
mSplitController.updateContainer(mTransaction, tf);
- verify(mSplitController, never()).dismissPlaceholderIfNecessary(any());
+ verify(mSplitController, never()).dismissPlaceholderIfNecessary(any(), any());
// Verify if placeholder should be dismissed, updateSplitContainer() won't be called.
doReturn(false).when(tf).isFinished();
doReturn(true).when(mSplitController)
- .dismissPlaceholderIfNecessary(splitContainer);
+ .dismissPlaceholderIfNecessary(mTransaction, splitContainer);
mSplitController.updateContainer(mTransaction, tf);
@@ -286,7 +289,7 @@
// Verify if the top active split is updated if both of its containers are not finished.
doReturn(false).when(mSplitController)
- .dismissPlaceholderIfNecessary(splitContainer);
+ .dismissPlaceholderIfNecessary(mTransaction, splitContainer);
mSplitController.updateContainer(mTransaction, tf);
@@ -315,34 +318,36 @@
@Test
public void testOnActivityCreated() {
- mSplitController.onActivityCreated(mActivity);
+ mSplitController.onActivityCreated(mTransaction, mActivity);
// Disallow to split as primary because we want the new launch to be always on top.
- verify(mSplitController).resolveActivityToContainer(mActivity, false /* isOnReparent */);
+ verify(mSplitController).resolveActivityToContainer(mTransaction, mActivity,
+ false /* isOnReparent */);
}
@Test
- public void testOnActivityReparentToTask_sameProcess() {
- mSplitController.onActivityReparentToTask(TASK_ID, new Intent(),
+ public void testOnActivityReparentedToTask_sameProcess() {
+ mSplitController.onActivityReparentedToTask(mTransaction, TASK_ID, new Intent(),
mActivity.getActivityToken());
// Treated as on activity created, but allow to split as primary.
- verify(mSplitController).resolveActivityToContainer(mActivity, true /* isOnReparent */);
+ verify(mSplitController).resolveActivityToContainer(mTransaction,
+ mActivity, true /* isOnReparent */);
// Try to place the activity to the top TaskFragment when there is no matched rule.
- verify(mSplitController).placeActivityInTopContainer(mActivity);
+ verify(mSplitController).placeActivityInTopContainer(mTransaction, mActivity);
}
@Test
- public void testOnActivityReparentToTask_diffProcess() {
+ public void testOnActivityReparentedToTask_diffProcess() {
// Create an empty TaskFragment to initialize for the Task.
mSplitController.newContainer(new Intent(), mActivity, TASK_ID);
final IBinder activityToken = new Binder();
final Intent intent = new Intent();
- mSplitController.onActivityReparentToTask(TASK_ID, intent, activityToken);
+ mSplitController.onActivityReparentedToTask(mTransaction, TASK_ID, intent, activityToken);
// Treated as starting new intent
- verify(mSplitController, never()).resolveActivityToContainer(any(), anyBoolean());
+ verify(mSplitController, never()).resolveActivityToContainer(any(), any(), anyBoolean());
verify(mSplitController).resolveStartActivityIntent(any(), eq(TASK_ID), eq(intent),
isNull());
}
@@ -504,26 +509,29 @@
@Test
public void testPlaceActivityInTopContainer() {
- mSplitController.placeActivityInTopContainer(mActivity);
+ mSplitController.placeActivityInTopContainer(mTransaction, mActivity);
- verify(mSplitPresenter, never()).applyTransaction(any());
+ verify(mTransaction, never()).reparentActivityToTaskFragment(any(), any());
- mSplitController.newContainer(new Intent(), mActivity, TASK_ID);
- mSplitController.placeActivityInTopContainer(mActivity);
+ // Place in the top container if there is no other rule matched.
+ final TaskFragmentContainer topContainer = mSplitController
+ .newContainer(new Intent(), mActivity, TASK_ID);
+ mSplitController.placeActivityInTopContainer(mTransaction, mActivity);
- verify(mSplitPresenter).applyTransaction(any());
+ verify(mTransaction).reparentActivityToTaskFragment(topContainer.getTaskFragmentToken(),
+ mActivity.getActivityToken());
// Not reparent if activity is in a TaskFragment.
- clearInvocations(mSplitPresenter);
+ clearInvocations(mTransaction);
mSplitController.newContainer(mActivity, TASK_ID);
- mSplitController.placeActivityInTopContainer(mActivity);
+ mSplitController.placeActivityInTopContainer(mTransaction, mActivity);
- verify(mSplitPresenter, never()).applyTransaction(any());
+ verify(mTransaction, never()).reparentActivityToTaskFragment(any(), any());
}
@Test
public void testResolveActivityToContainer_noRuleMatched() {
- final boolean result = mSplitController.resolveActivityToContainer(mActivity,
+ final boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity,
false /* isOnReparent */);
assertFalse(result);
@@ -535,7 +543,7 @@
setupExpandRule(mActivity);
// When the activity is not in any TaskFragment, create a new expanded TaskFragment for it.
- final boolean result = mSplitController.resolveActivityToContainer(mActivity,
+ final boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity,
false /* isOnReparent */);
final TaskFragmentContainer container = mSplitController.getContainerWithActivity(
mActivity);
@@ -543,7 +551,8 @@
assertTrue(result);
assertNotNull(container);
verify(mSplitController).newContainer(mActivity, TASK_ID);
- verify(mSplitPresenter).expandActivity(container.getTaskFragmentToken(), mActivity);
+ verify(mSplitPresenter).expandActivity(mTransaction, container.getTaskFragmentToken(),
+ mActivity);
}
@Test
@@ -552,11 +561,11 @@
// When the activity is not in any TaskFragment, create a new expanded TaskFragment for it.
final TaskFragmentContainer container = mSplitController.newContainer(mActivity, TASK_ID);
- final boolean result = mSplitController.resolveActivityToContainer(mActivity,
+ final boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity,
false /* isOnReparent */);
assertTrue(result);
- verify(mSplitPresenter).expandTaskFragment(container.getTaskFragmentToken());
+ verify(mSplitPresenter).expandTaskFragment(mTransaction, container.getTaskFragmentToken());
}
@Test
@@ -566,14 +575,15 @@
// When the activity is not in any TaskFragment, create a new expanded TaskFragment for it.
final Activity activity = createMockActivity();
addSplitTaskFragments(activity, mActivity);
- final boolean result = mSplitController.resolveActivityToContainer(mActivity,
+ final boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity,
false /* isOnReparent */);
final TaskFragmentContainer container = mSplitController.getContainerWithActivity(
mActivity);
assertTrue(result);
assertNotNull(container);
- verify(mSplitPresenter).expandActivity(container.getTaskFragmentToken(), mActivity);
+ verify(mSplitPresenter).expandActivity(mTransaction, container.getTaskFragmentToken(),
+ mActivity);
}
@Test
@@ -583,11 +593,11 @@
(SplitPlaceholderRule) mSplitController.getSplitRules().get(0);
// Launch placeholder if the activity is not in any TaskFragment.
- final boolean result = mSplitController.resolveActivityToContainer(mActivity,
+ final boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity,
false /* isOnReparent */);
assertTrue(result);
- verify(mSplitPresenter).startActivityToSide(mActivity, PLACEHOLDER_INTENT,
+ verify(mSplitPresenter).startActivityToSide(mTransaction, mActivity, PLACEHOLDER_INTENT,
mSplitController.getPlaceholderOptions(mActivity, true /* isOnCreated */),
placeholderRule, true /* isPlaceholder */);
}
@@ -600,11 +610,11 @@
final Activity activity = createMockActivity();
mSplitController.newContainer(mActivity, TASK_ID);
mSplitController.newContainer(activity, TASK_ID);
- final boolean result = mSplitController.resolveActivityToContainer(mActivity,
+ final boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity,
false /* isOnReparent */);
assertFalse(result);
- verify(mSplitPresenter, never()).startActivityToSide(any(), any(), any(), any(),
+ verify(mSplitPresenter, never()).startActivityToSide(any(), any(), any(), any(), any(),
anyBoolean());
}
@@ -616,11 +626,11 @@
// Launch placeholder if the activity is in the topmost expanded TaskFragment.
mSplitController.newContainer(mActivity, TASK_ID);
- final boolean result = mSplitController.resolveActivityToContainer(mActivity,
+ final boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity,
false /* isOnReparent */);
assertTrue(result);
- verify(mSplitPresenter).startActivityToSide(mActivity, PLACEHOLDER_INTENT,
+ verify(mSplitPresenter).startActivityToSide(mTransaction, mActivity, PLACEHOLDER_INTENT,
mSplitController.getPlaceholderOptions(mActivity, true /* isOnCreated */),
placeholderRule, true /* isPlaceholder */);
}
@@ -632,11 +642,11 @@
// Don't launch placeholder if the activity is in primary split.
final Activity secondaryActivity = createMockActivity();
addSplitTaskFragments(mActivity, secondaryActivity);
- final boolean result = mSplitController.resolveActivityToContainer(mActivity,
+ final boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity,
false /* isOnReparent */);
assertFalse(result);
- verify(mSplitPresenter, never()).startActivityToSide(any(), any(), any(), any(),
+ verify(mSplitPresenter, never()).startActivityToSide(any(), any(), any(), any(), any(),
anyBoolean());
}
@@ -649,11 +659,11 @@
// Launch placeholder if the activity is in secondary split.
final Activity primaryActivity = createMockActivity();
addSplitTaskFragments(primaryActivity, mActivity);
- final boolean result = mSplitController.resolveActivityToContainer(mActivity,
+ final boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity,
false /* isOnReparent */);
assertTrue(result);
- verify(mSplitPresenter).startActivityToSide(mActivity, PLACEHOLDER_INTENT,
+ verify(mSplitPresenter).startActivityToSide(mTransaction, mActivity, PLACEHOLDER_INTENT,
mSplitController.getPlaceholderOptions(mActivity, true /* isOnCreated */),
placeholderRule, true /* isPlaceholder */);
}
@@ -676,7 +686,7 @@
secondaryContainer,
splitRule);
clearInvocations(mSplitController);
- final boolean result = mSplitController.resolveActivityToContainer(mActivity,
+ final boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity,
false /* isOnReparent */);
assertTrue(result);
@@ -705,7 +715,7 @@
final Activity launchedActivity = createMockActivity();
primaryContainer.addPendingAppearedActivity(launchedActivity);
- assertFalse(mSplitController.resolveActivityToContainer(launchedActivity,
+ assertFalse(mSplitController.resolveActivityToContainer(mTransaction, launchedActivity,
false /* isOnReparent */));
}
@@ -717,7 +727,7 @@
// Activity is already in secondary split, no need to create new split.
addSplitTaskFragments(primaryActivity, mActivity);
clearInvocations(mSplitController);
- final boolean result = mSplitController.resolveActivityToContainer(mActivity,
+ final boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity,
false /* isOnReparent */);
assertTrue(result);
@@ -735,7 +745,7 @@
addSplitTaskFragments(primaryActivity, secondaryActivity);
mSplitController.getContainerWithActivity(secondaryActivity)
.addPendingAppearedActivity(mActivity);
- final boolean result = mSplitController.resolveActivityToContainer(mActivity,
+ final boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity,
false /* isOnReparent */);
assertFalse(result);
@@ -760,7 +770,7 @@
mActivity,
secondaryContainer,
placeholderRule);
- final boolean result = mSplitController.resolveActivityToContainer(mActivity,
+ final boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity,
false /* isOnReparent */);
assertTrue(result);
@@ -774,7 +784,7 @@
final TaskFragmentContainer container = mSplitController.newContainer(activityBelow,
TASK_ID);
container.addPendingAppearedActivity(mActivity);
- final boolean result = mSplitController.resolveActivityToContainer(mActivity,
+ final boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity,
false /* isOnReparent */);
assertTrue(result);
@@ -790,14 +800,15 @@
final TaskFragmentContainer container = mSplitController.newContainer(activityBelow,
TASK_ID);
container.addPendingAppearedActivity(mActivity);
- boolean result = mSplitController.resolveActivityToContainer(mActivity,
+ boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity,
false /* isOnReparent */);
assertFalse(result);
assertEquals(container, mSplitController.getContainerWithActivity(mActivity));
// Allow to split as primary.
- result = mSplitController.resolveActivityToContainer(mActivity, true /* isOnReparent */);
+ result = mSplitController.resolveActivityToContainer(mTransaction, mActivity,
+ true /* isOnReparent */);
assertTrue(result);
assertSplitPair(mActivity, activityBelow);
@@ -815,7 +826,7 @@
final TaskFragmentContainer secondaryContainer = mSplitController.getContainerWithActivity(
activityBelow);
secondaryContainer.addPendingAppearedActivity(mActivity);
- final boolean result = mSplitController.resolveActivityToContainer(mActivity,
+ final boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity,
false /* isOnReparent */);
final TaskFragmentContainer container = mSplitController.getContainerWithActivity(
mActivity);
@@ -836,14 +847,15 @@
final TaskFragmentContainer primaryContainer = mSplitController.getContainerWithActivity(
primaryActivity);
primaryContainer.addPendingAppearedActivity(mActivity);
- boolean result = mSplitController.resolveActivityToContainer(mActivity,
+ boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity,
false /* isOnReparent */);
assertFalse(result);
assertEquals(primaryContainer, mSplitController.getContainerWithActivity(mActivity));
- result = mSplitController.resolveActivityToContainer(mActivity, true /* isOnReparent */);
+ result = mSplitController.resolveActivityToContainer(mTransaction, mActivity,
+ true /* isOnReparent */);
assertTrue(result);
assertSplitPair(mActivity, primaryActivity);
@@ -861,7 +873,7 @@
container.addPendingAppearedActivity(mActivity);
// Allow to split as primary.
- boolean result = mSplitController.resolveActivityToContainer(mActivity,
+ boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity,
true /* isOnReparent */);
assertTrue(result);
@@ -879,15 +891,13 @@
TASK_ID);
container.addPendingAppearedActivity(mActivity);
- boolean result = mSplitController.resolveActivityToContainer(mActivity,
+ boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity,
false /* isOnReparent */);
assertTrue(result);
assertSplitPair(activityBelow, mActivity, true /* matchParentBounds */);
}
- // Suppress GuardedBy warning on unit tests
- @SuppressWarnings("GuardedBy")
@Test
public void testResolveActivityToContainer_minDimensions_shouldExpandSplitContainer() {
final Activity primaryActivity = createMockActivity();
@@ -899,14 +909,14 @@
doReturn(secondaryActivity).when(mSplitController).findActivityBelow(eq(mActivity));
clearInvocations(mSplitPresenter);
- boolean result = mSplitController.resolveActivityToContainer(mActivity,
+ boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity,
false /* isOnReparent */);
assertTrue(result);
assertSplitPair(primaryActivity, mActivity, true /* matchParentBounds */);
assertEquals(mSplitController.getContainerWithActivity(secondaryActivity),
mSplitController.getContainerWithActivity(mActivity));
- verify(mSplitPresenter, never()).createNewSplitContainer(any(), any(), any());
+ verify(mSplitPresenter, never()).createNewSplitContainer(any(), any(), any(), any());
}
@Test
@@ -914,7 +924,7 @@
doReturn(new Binder()).when(mSplitController).getInitialTaskFragmentToken(mActivity);
// No need to handle when the new launched activity is in an unknown TaskFragment.
- assertTrue(mSplitController.resolveActivityToContainer(mActivity,
+ assertTrue(mSplitController.resolveActivityToContainer(mTransaction, mActivity,
false /* isOnReparent */));
}
@@ -993,7 +1003,7 @@
private void setupTaskFragmentInfo(@NonNull TaskFragmentContainer container,
@NonNull Activity activity) {
final TaskFragmentInfo info = createMockTaskFragmentInfo(container, activity);
- container.setInfo(info);
+ container.setInfo(mTransaction, info);
mSplitPresenter.mFragmentInfos.put(container.getTaskFragmentToken(), info);
}
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java
index d7931966..3fdf8e5 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java
@@ -78,6 +78,8 @@
* Build/Install/Run:
* atest WMJetpackUnitTests:SplitPresenterTest
*/
+// Suppress GuardedBy warning on unit tests
+@SuppressWarnings("GuardedBy")
@Presubmit
@SmallTest
@RunWith(AndroidJUnit4.class)
@@ -226,8 +228,9 @@
mTransaction, splitContainer, mActivity, secondaryActivity,
null /* secondaryIntent */));
- primaryTf.setInfo(createMockTaskFragmentInfo(primaryTf, mActivity));
- secondaryTf.setInfo(createMockTaskFragmentInfo(secondaryTf, secondaryActivity));
+ primaryTf.setInfo(mTransaction, createMockTaskFragmentInfo(primaryTf, mActivity));
+ secondaryTf.setInfo(mTransaction,
+ createMockTaskFragmentInfo(secondaryTf, secondaryActivity));
assertEquals(RESULT_EXPANDED, mPresenter.expandSplitContainerIfNeeded(mTransaction,
splitContainer, mActivity, secondaryActivity, null /* secondaryIntent */));
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java
index 44c7e6c..6cbecff 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java
@@ -19,6 +19,8 @@
import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_ID;
import static androidx.window.extensions.embedding.EmbeddingTestUtils.createMockTaskFragmentInfo;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static org.junit.Assert.assertEquals;
@@ -36,7 +38,6 @@
import android.app.Activity;
import android.content.Intent;
import android.os.Binder;
-import android.os.Handler;
import android.os.IBinder;
import android.platform.test.annotations.Presubmit;
import android.window.TaskFragmentInfo;
@@ -62,25 +63,27 @@
* Build/Install/Run:
* atest WMJetpackUnitTests:TaskFragmentContainerTest
*/
+// Suppress GuardedBy warning on unit tests
+@SuppressWarnings("GuardedBy")
@Presubmit
@SmallTest
@RunWith(AndroidJUnit4.class)
public class TaskFragmentContainerTest {
@Mock
private SplitPresenter mPresenter;
- @Mock
private SplitController mController;
@Mock
private TaskFragmentInfo mInfo;
@Mock
- private Handler mHandler;
+ private WindowContainerTransaction mTransaction;
private Activity mActivity;
private Intent mIntent;
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
- doReturn(mHandler).when(mController).getHandler();
+ mController = new SplitController();
+ spyOn(mController);
mActivity = createMockActivity();
mIntent = new Intent();
}
@@ -123,7 +126,7 @@
// Remove all references after the container has appeared in server.
doReturn(new ArrayList<>()).when(mInfo).getActivities();
- container.setInfo(mInfo);
+ container.setInfo(mTransaction, mInfo);
container.finish(true /* shouldFinishDependent */, mPresenter, wct, mController);
verify(mActivity, never()).finish();
@@ -137,7 +140,7 @@
final TaskFragmentContainer container0 = new TaskFragmentContainer(mActivity,
null /* pendingAppearedIntent */, taskContainer, mController);
final TaskFragmentInfo info = createMockTaskFragmentInfo(container0, mActivity);
- container0.setInfo(info);
+ container0.setInfo(mTransaction, info);
// Request to reparent the activity to a new TaskFragment.
final TaskFragmentContainer container1 = new TaskFragmentContainer(mActivity,
null /* pendingAppearedIntent */, taskContainer, mController);
@@ -163,7 +166,7 @@
final TaskFragmentInfo info0 = createMockTaskFragmentInfo(pendingActivityContainer,
mActivity);
- pendingActivityContainer.setInfo(info0);
+ pendingActivityContainer.setInfo(mTransaction, info0);
assertTrue(pendingActivityContainer.mPendingAppearedActivities.isEmpty());
@@ -175,7 +178,7 @@
final TaskFragmentInfo info1 = createMockTaskFragmentInfo(pendingIntentContainer,
mActivity);
- pendingIntentContainer.setInfo(info1);
+ pendingIntentContainer.setInfo(mTransaction, info1);
assertNull(pendingIntentContainer.getPendingAppearedIntent());
}
@@ -191,18 +194,19 @@
final TaskFragmentInfo info = mock(TaskFragmentInfo.class);
doReturn(new ArrayList<>()).when(info).getActivities();
doReturn(true).when(info).isEmpty();
- container.setInfo(info);
+ container.setInfo(mTransaction, info);
assertTrue(container.isWaitingActivityAppear());
doReturn(false).when(info).isEmpty();
- container.setInfo(info);
+ container.setInfo(mTransaction, info);
assertFalse(container.isWaitingActivityAppear());
}
@Test
public void testAppearEmptyTimeout() {
+ doNothing().when(mController).onTaskFragmentAppearEmptyTimeout(any(), any());
final TaskContainer taskContainer = new TaskContainer(TASK_ID);
final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */,
mIntent, taskContainer, mController);
@@ -213,20 +217,20 @@
final TaskFragmentInfo info = mock(TaskFragmentInfo.class);
container.mInfo = null;
doReturn(true).when(info).isEmpty();
- container.setInfo(info);
+ container.setInfo(mTransaction, info);
assertNotNull(container.mAppearEmptyTimeout);
// Not set if it is not appeared empty.
doReturn(new ArrayList<>()).when(info).getActivities();
doReturn(false).when(info).isEmpty();
- container.setInfo(info);
+ container.setInfo(mTransaction, info);
assertNull(container.mAppearEmptyTimeout);
// Remove timeout after the container becomes non-empty.
doReturn(false).when(info).isEmpty();
- container.setInfo(info);
+ container.setInfo(mTransaction, info);
assertNull(container.mAppearEmptyTimeout);
@@ -234,7 +238,7 @@
container.mInfo = null;
container.setPendingAppearedIntent(mIntent);
doReturn(true).when(info).isEmpty();
- container.setInfo(info);
+ container.setInfo(mTransaction, info);
container.mAppearEmptyTimeout.run();
assertNull(container.mAppearEmptyTimeout);
@@ -260,7 +264,7 @@
final List<IBinder> runningActivities = Lists.newArrayList(activity0.getActivityToken(),
activity1.getActivityToken());
doReturn(runningActivities).when(mInfo).getActivities();
- container.setInfo(mInfo);
+ container.setInfo(mTransaction, mInfo);
activities = container.collectNonFinishingActivities();
assertEquals(3, activities.size());
@@ -295,7 +299,7 @@
final Activity activity = createMockActivity();
final List<IBinder> runningActivities = Lists.newArrayList(activity.getActivityToken());
doReturn(runningActivities).when(mInfo).getActivities();
- container.setInfo(mInfo);
+ container.setInfo(mTransaction, mInfo);
assertEquals(activity, container.getBottomMostActivity());
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/RootDisplayAreaOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/RootDisplayAreaOrganizer.java
index 764e650..b085b73 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/RootDisplayAreaOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/RootDisplayAreaOrganizer.java
@@ -16,14 +16,20 @@
package com.android.wm.shell;
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE;
+
+import android.app.WindowConfiguration;
import android.util.SparseArray;
import android.view.SurfaceControl;
import android.window.DisplayAreaAppearedInfo;
import android.window.DisplayAreaInfo;
import android.window.DisplayAreaOrganizer;
+import android.window.WindowContainerTransaction;
import androidx.annotation.NonNull;
+import com.android.internal.protolog.common.ProtoLog;
+
import java.io.PrintWriter;
import java.util.List;
import java.util.concurrent.Executor;
@@ -102,10 +108,44 @@
mDisplayAreasInfo.put(displayId, displayAreaInfo);
}
+ /**
+ * Create a {@link WindowContainerTransaction} to update display windowing mode.
+ *
+ * @param displayId display id to update windowing mode for
+ * @param windowingMode target {@link WindowConfiguration.WindowingMode}
+ * @return {@link WindowContainerTransaction} with pending operation to set windowing mode
+ */
+ public WindowContainerTransaction prepareWindowingModeChange(int displayId,
+ @WindowConfiguration.WindowingMode int windowingMode) {
+ WindowContainerTransaction wct = new WindowContainerTransaction();
+ DisplayAreaInfo displayAreaInfo = mDisplayAreasInfo.get(displayId);
+ if (displayAreaInfo == null) {
+ ProtoLog.e(WM_SHELL_DESKTOP_MODE,
+ "unable to update windowing mode for display %d display not found", displayId);
+ return wct;
+ }
+
+ ProtoLog.d(WM_SHELL_DESKTOP_MODE,
+ "setWindowingMode: displayId=%d current wmMode=%d new wmMode=%d", displayId,
+ displayAreaInfo.configuration.windowConfiguration.getWindowingMode(),
+ windowingMode);
+
+ wct.setWindowingMode(displayAreaInfo.token, windowingMode);
+ return wct;
+ }
+
public void dump(@NonNull PrintWriter pw, String prefix) {
final String innerPrefix = prefix + " ";
final String childPrefix = innerPrefix + " ";
pw.println(prefix + this);
+
+ for (int i = 0; i < mDisplayAreasInfo.size(); i++) {
+ int displayId = mDisplayAreasInfo.keyAt(i);
+ DisplayAreaInfo displayAreaInfo = mDisplayAreasInfo.get(displayId);
+ int windowingMode =
+ displayAreaInfo.configuration.windowConfiguration.getWindowingMode();
+ pw.println(innerPrefix + "# displayId=" + displayId + " wmMode=" + windowingMode);
+ }
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
index 6ae0f9b..d5d4935 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
@@ -22,6 +22,7 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE;
import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_TASK_ORG;
import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS;
@@ -46,6 +47,7 @@
import android.window.StartingWindowRemovalInfo;
import android.window.TaskAppearedInfo;
import android.window.TaskOrganizer;
+import android.window.WindowContainerTransaction;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.common.ProtoLog;
@@ -690,6 +692,49 @@
taskListener.reparentChildSurfaceToTask(taskId, sc, t);
}
+ /**
+ * Create a {@link WindowContainerTransaction} to clear task bounds.
+ *
+ * @param displayId display id for tasks that will have bounds cleared
+ * @return {@link WindowContainerTransaction} with pending operations to clear bounds
+ */
+ public WindowContainerTransaction prepareClearBoundsForTasks(int displayId) {
+ ProtoLog.d(WM_SHELL_DESKTOP_MODE, "prepareClearBoundsForTasks: displayId=%d", displayId);
+ WindowContainerTransaction wct = new WindowContainerTransaction();
+ for (int i = 0; i < mTasks.size(); i++) {
+ RunningTaskInfo taskInfo = mTasks.valueAt(i).getTaskInfo();
+ if (taskInfo.displayId == displayId) {
+ ProtoLog.d(WM_SHELL_DESKTOP_MODE, "clearing bounds for token=%s taskInfo=%s",
+ taskInfo.token, taskInfo);
+ wct.setBounds(taskInfo.token, null);
+ }
+ }
+ return wct;
+ }
+
+ /**
+ * Create a {@link WindowContainerTransaction} to clear task level freeform setting.
+ *
+ * @param displayId display id for tasks that will have windowing mode reset to {@link
+ * WindowConfiguration#WINDOWING_MODE_UNDEFINED}
+ * @return {@link WindowContainerTransaction} with pending operations to clear windowing mode
+ */
+ public WindowContainerTransaction prepareClearFreeformForTasks(int displayId) {
+ ProtoLog.d(WM_SHELL_DESKTOP_MODE, "prepareClearFreeformForTasks: displayId=%d", displayId);
+ WindowContainerTransaction wct = new WindowContainerTransaction();
+ for (int i = 0; i < mTasks.size(); i++) {
+ RunningTaskInfo taskInfo = mTasks.valueAt(i).getTaskInfo();
+ if (taskInfo.displayId == displayId
+ && taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) {
+ ProtoLog.d(WM_SHELL_DESKTOP_MODE,
+ "clearing windowing mode for token=%s taskInfo=%s", taskInfo.token,
+ taskInfo);
+ wct.setWindowingMode(taskInfo.token, WINDOWING_MODE_UNDEFINED);
+ }
+ }
+ return wct;
+ }
+
private void logSizeCompatRestartButtonEventReported(@NonNull TaskAppearedInfo info,
int event) {
ActivityInfo topActivityInfo = info.getTaskInfo().topActivityInfo;
@@ -816,7 +861,14 @@
final int key = mTasks.keyAt(i);
final TaskAppearedInfo info = mTasks.valueAt(i);
final TaskListener listener = getTaskListener(info.getTaskInfo());
- pw.println(innerPrefix + "#" + i + " task=" + key + " listener=" + listener);
+ final int windowingMode = info.getTaskInfo().getWindowingMode();
+ String pkg = "";
+ if (info.getTaskInfo().baseActivity != null) {
+ pkg = info.getTaskInfo().baseActivity.getPackageName();
+ }
+ Rect bounds = info.getTaskInfo().getConfiguration().windowConfiguration.getBounds();
+ pw.println(innerPrefix + "#" + i + " task=" + key + " listener=" + listener
+ + " wmMode=" + windowingMode + " pkg=" + pkg + " bounds=" + bounds);
}
pw.println();
@@ -826,6 +878,7 @@
final TaskListener listener = mLaunchCookieToListener.valueAt(i);
pw.println(innerPrefix + "#" + i + " cookie=" + key + " listener=" + listener);
}
+
}
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java
index d5875c0..e270edb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java
@@ -221,8 +221,7 @@
}
final Display display = mDisplayController.getDisplay(mDisplayId);
SurfaceControlViewHost viewRoot =
- new SurfaceControlViewHost(
- view.getContext(), display, wwm, true /* useSfChoreographer */);
+ new SurfaceControlViewHost(view.getContext(), display, wwm);
attrs.flags |= FLAG_HARDWARE_ACCELERATED;
viewRoot.setView(view, attrs);
mViewRoots.put(view, viewRoot);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellConcurrencyModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellConcurrencyModule.java
index 35a309a..0cc545a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellConcurrencyModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellConcurrencyModule.java
@@ -20,7 +20,6 @@
import static android.os.Process.THREAD_PRIORITY_DISPLAY;
import static android.os.Process.THREAD_PRIORITY_TOP_APP_BOOST;
-import android.animation.AnimationHandler;
import android.content.Context;
import android.os.Build;
import android.os.Handler;
@@ -31,11 +30,9 @@
import androidx.annotation.Nullable;
-import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
import com.android.wm.shell.R;
import com.android.wm.shell.common.HandlerExecutor;
import com.android.wm.shell.common.ShellExecutor;
-import com.android.wm.shell.common.annotations.ChoreographerSfVsync;
import com.android.wm.shell.common.annotations.ExternalMainThread;
import com.android.wm.shell.common.annotations.ShellAnimationThread;
import com.android.wm.shell.common.annotations.ShellBackgroundThread;
@@ -195,30 +192,6 @@
}
/**
- * Provide a Shell main-thread AnimationHandler. The AnimationHandler can be set on
- * {@link android.animation.ValueAnimator}s and will ensure that the animation will run on
- * the Shell main-thread with the SF vsync.
- */
- @WMSingleton
- @Provides
- @ChoreographerSfVsync
- public static AnimationHandler provideShellMainExecutorSfVsyncAnimationHandler(
- @ShellMainThread ShellExecutor mainExecutor) {
- try {
- AnimationHandler handler = new AnimationHandler();
- mainExecutor.executeBlocking(() -> {
- // This is called on the animation thread since it calls
- // Choreographer.getSfInstance() which returns a thread-local Choreographer instance
- // that uses the SF vsync
- handler.setProvider(new SfVsyncFrameCallbackProvider());
- });
- return handler;
- } catch (InterruptedException e) {
- throw new RuntimeException("Failed to initialize SfVsync animation handler in 1s", e);
- }
- }
-
- /**
* Provides a Shell background thread Handler for low priority background tasks.
*/
@WMSingleton
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index 2ca9c3b..2bcc134 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -27,6 +27,7 @@
import com.android.internal.logging.UiEventLogger;
import com.android.internal.statusbar.IStatusBarService;
import com.android.launcher3.icons.IconProvider;
+import com.android.wm.shell.RootDisplayAreaOrganizer;
import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.TaskViewTransitions;
@@ -48,6 +49,8 @@
import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.common.annotations.ShellBackgroundThread;
import com.android.wm.shell.common.annotations.ShellMainThread;
+import com.android.wm.shell.desktopmode.DesktopModeConstants;
+import com.android.wm.shell.desktopmode.DesktopModeController;
import com.android.wm.shell.draganddrop.DragAndDropController;
import com.android.wm.shell.freeform.FreeformComponents;
import com.android.wm.shell.freeform.FreeformTaskListener;
@@ -574,6 +577,27 @@
}
//
+ // Desktop mode (optional feature)
+ //
+
+ @WMSingleton
+ @Provides
+ static Optional<DesktopModeController> provideDesktopModeController(
+ Context context, ShellInit shellInit,
+ ShellTaskOrganizer shellTaskOrganizer,
+ RootDisplayAreaOrganizer rootDisplayAreaOrganizer,
+ @ShellMainThread Handler mainHandler
+ ) {
+ if (DesktopModeConstants.IS_FEATURE_ENABLED) {
+ return Optional.of(new DesktopModeController(context, shellInit, shellTaskOrganizer,
+ rootDisplayAreaOrganizer,
+ mainHandler));
+ } else {
+ return Optional.empty();
+ }
+ }
+
+ //
// Misc
//
@@ -583,7 +607,8 @@
@ShellCreateTriggerOverride
@Provides
static Object provideIndependentShellComponentsToCreate(
- SplitscreenPipMixedHandler splitscreenPipMixedHandler) {
+ SplitscreenPipMixedHandler splitscreenPipMixedHandler,
+ Optional<DesktopModeController> desktopModeController) {
return new Object();
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeConstants.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeConstants.java
new file mode 100644
index 0000000..e62a63a
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeConstants.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2022 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.wm.shell.desktopmode;
+
+import android.os.SystemProperties;
+
+/**
+ * Constants for desktop mode feature
+ */
+public class DesktopModeConstants {
+
+ /**
+ * Flag to indicate whether desktop mode is available on the device
+ */
+ public static final boolean IS_FEATURE_ENABLED = SystemProperties.getBoolean(
+ "persist.wm.debug.desktop_mode", false);
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
new file mode 100644
index 0000000..5849e16
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2022 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.wm.shell.desktopmode;
+
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE;
+
+import android.content.Context;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.window.WindowContainerTransaction;
+
+import androidx.annotation.Nullable;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.RootDisplayAreaOrganizer;
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.annotations.ShellMainThread;
+import com.android.wm.shell.sysui.ShellInit;
+
+/**
+ * Handles windowing changes when desktop mode system setting changes
+ */
+public class DesktopModeController {
+
+ private final Context mContext;
+ private final ShellTaskOrganizer mShellTaskOrganizer;
+ private final RootDisplayAreaOrganizer mRootDisplayAreaOrganizer;
+ private final SettingsObserver mSettingsObserver;
+
+ public DesktopModeController(Context context, ShellInit shellInit,
+ ShellTaskOrganizer shellTaskOrganizer,
+ RootDisplayAreaOrganizer rootDisplayAreaOrganizer,
+ @ShellMainThread Handler mainHandler) {
+ mContext = context;
+ mShellTaskOrganizer = shellTaskOrganizer;
+ mRootDisplayAreaOrganizer = rootDisplayAreaOrganizer;
+ mSettingsObserver = new SettingsObserver(mContext, mainHandler);
+ shellInit.addInitCallback(this::onInit, this);
+ }
+
+ private void onInit() {
+ ProtoLog.d(WM_SHELL_DESKTOP_MODE, "Initialize DesktopModeController");
+ mSettingsObserver.observe();
+ }
+
+ @VisibleForTesting
+ void updateDesktopModeEnabled(boolean enabled) {
+ ProtoLog.d(WM_SHELL_DESKTOP_MODE, "updateDesktopModeState: enabled=%s", enabled);
+
+ int displayId = mContext.getDisplayId();
+
+ WindowContainerTransaction wct = new WindowContainerTransaction();
+ // Reset freeform windowing mode that is set per task level (tasks should inherit
+ // container value)
+ wct.merge(mShellTaskOrganizer.prepareClearFreeformForTasks(displayId), true /* transfer */);
+ int targetWindowingMode;
+ if (enabled) {
+ targetWindowingMode = WINDOWING_MODE_FREEFORM;
+ } else {
+ targetWindowingMode = WINDOWING_MODE_FULLSCREEN;
+ // Clear any resized bounds
+ wct.merge(mShellTaskOrganizer.prepareClearBoundsForTasks(displayId),
+ true /* transfer */);
+ }
+ wct.merge(mRootDisplayAreaOrganizer.prepareWindowingModeChange(displayId,
+ targetWindowingMode), true /* transfer */);
+ mRootDisplayAreaOrganizer.applyTransaction(wct);
+ }
+
+ /**
+ * A {@link ContentObserver} for listening to changes to {@link Settings.System#DESKTOP_MODE}
+ */
+ private final class SettingsObserver extends ContentObserver {
+
+ private final Uri mDesktopModeSetting = Settings.System.getUriFor(
+ Settings.System.DESKTOP_MODE);
+
+ private final Context mContext;
+
+ SettingsObserver(Context context, Handler handler) {
+ super(handler);
+ mContext = context;
+ }
+
+ public void observe() {
+ // TODO(b/242867463): listen for setting change for all users
+ mContext.getContentResolver().registerContentObserver(mDesktopModeSetting,
+ false /* notifyForDescendants */, this /* observer */, UserHandle.USER_CURRENT);
+ }
+
+ @Override
+ public void onChange(boolean selfChange, @Nullable Uri uri) {
+ if (mDesktopModeSetting.equals(uri)) {
+ ProtoLog.d(WM_SHELL_DESKTOP_MODE, "Received update for desktop mode setting");
+ desktopModeSettingChanged();
+ }
+ }
+
+ private void desktopModeSettingChanged() {
+ boolean enabled = isDesktopModeEnabled();
+ updateDesktopModeEnabled(enabled);
+ }
+
+ private boolean isDesktopModeEnabled() {
+ try {
+ int result = Settings.System.getIntForUser(mContext.getContentResolver(),
+ Settings.System.DESKTOP_MODE, UserHandle.USER_CURRENT);
+ ProtoLog.d(WM_SHELL_DESKTOP_MODE, "isDesktopModeEnabled=%s", result);
+ return result != 0;
+ } catch (Settings.SettingNotFoundException e) {
+ ProtoLog.e(WM_SHELL_DESKTOP_MODE, "Failed to read DESKTOP_MODE setting %s", e);
+ return false;
+ }
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java
index 504dc02..a0a8f9f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java
@@ -31,8 +31,6 @@
import android.util.Size;
import android.view.MotionEvent;
import android.view.SurfaceControl;
-import android.view.SyncRtSurfaceTransactionApplier;
-import android.view.SyncRtSurfaceTransactionApplier.SurfaceParams;
import android.view.WindowManagerGlobal;
import com.android.internal.protolog.common.ProtoLog;
@@ -42,6 +40,7 @@
import com.android.wm.shell.pip.PipMediaController;
import com.android.wm.shell.pip.PipMediaController.ActionListener;
import com.android.wm.shell.pip.PipMenuController;
+import com.android.wm.shell.pip.PipSurfaceTransactionHelper;
import com.android.wm.shell.pip.PipUiEventLogger;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.splitscreen.SplitScreenController;
@@ -115,6 +114,10 @@
private final ShellExecutor mMainExecutor;
private final Handler mMainHandler;
+ private final PipSurfaceTransactionHelper.SurfaceControlTransactionFactory
+ mSurfaceControlTransactionFactory;
+ private final float[] mTmpTransform = new float[9];
+
private final ArrayList<Listener> mListeners = new ArrayList<>();
private final SystemWindows mSystemWindows;
private final Optional<SplitScreenController> mSplitScreenController;
@@ -124,7 +127,6 @@
private RemoteAction mCloseAction;
private List<RemoteAction> mMediaActions;
- private SyncRtSurfaceTransactionApplier mApplier;
private int mMenuState;
private PipMenuView mPipMenuView;
@@ -150,6 +152,9 @@
mMainHandler = mainHandler;
mSplitScreenController = splitScreenOptional;
mPipUiEventLogger = pipUiEventLogger;
+
+ mSurfaceControlTransactionFactory =
+ new PipSurfaceTransactionHelper.VsyncSurfaceControlTransactionFactory();
}
public boolean isMenuVisible() {
@@ -194,7 +199,6 @@
return;
}
- mApplier = null;
mSystemWindows.removeView(mPipMenuView);
mPipMenuView = null;
}
@@ -289,7 +293,7 @@
willResizeMenu, withDelay, showResizeHandle, Debug.getCallers(5, " "));
}
- if (!maybeCreateSyncApplier()) {
+ if (!checkPipMenuState()) {
return;
}
@@ -312,7 +316,7 @@
return;
}
- if (!maybeCreateSyncApplier()) {
+ if (!checkPipMenuState()) {
return;
}
@@ -328,18 +332,15 @@
mTmpSourceRectF.set(mTmpSourceBounds);
mTmpDestinationRectF.set(destinationBounds);
mMoveTransform.setRectToRect(mTmpSourceRectF, mTmpDestinationRectF, Matrix.ScaleToFit.FILL);
- SurfaceControl surfaceControl = getSurfaceControl();
- SurfaceParams params = new SurfaceParams.Builder(surfaceControl)
- .withMatrix(mMoveTransform)
- .build();
+ final SurfaceControl surfaceControl = getSurfaceControl();
+ final SurfaceControl.Transaction menuTx =
+ mSurfaceControlTransactionFactory.getTransaction();
+ menuTx.setMatrix(surfaceControl, mMoveTransform, mTmpTransform);
if (pipLeash != null && t != null) {
- SurfaceParams pipParams = new SurfaceParams.Builder(pipLeash)
- .withMergeTransaction(t)
- .build();
- mApplier.scheduleApply(params, pipParams);
- } else {
- mApplier.scheduleApply(params);
+ // Merge the two transactions, vsyncId has been set on menuTx.
+ menuTx.merge(t);
}
+ menuTx.apply();
}
/**
@@ -353,36 +354,29 @@
return;
}
- if (!maybeCreateSyncApplier()) {
+ if (!checkPipMenuState()) {
return;
}
- SurfaceControl surfaceControl = getSurfaceControl();
- SurfaceParams params = new SurfaceParams.Builder(surfaceControl)
- .withWindowCrop(destinationBounds)
- .build();
+ final SurfaceControl surfaceControl = getSurfaceControl();
+ final SurfaceControl.Transaction menuTx =
+ mSurfaceControlTransactionFactory.getTransaction();
+ menuTx.setCrop(surfaceControl, destinationBounds);
if (pipLeash != null && t != null) {
- SurfaceParams pipParams = new SurfaceParams.Builder(pipLeash)
- .withMergeTransaction(t)
- .build();
- mApplier.scheduleApply(params, pipParams);
- } else {
- mApplier.scheduleApply(params);
+ // Merge the two transactions, vsyncId has been set on menuTx.
+ menuTx.merge(t);
}
+ menuTx.apply();
}
- private boolean maybeCreateSyncApplier() {
+ private boolean checkPipMenuState() {
if (mPipMenuView == null || mPipMenuView.getViewRootImpl() == null) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: Not going to move PiP, either menu or its parent is not created.", TAG);
return false;
}
- if (mApplier == null) {
- mApplier = new SyncRtSurfaceTransactionApplier(mPipMenuView);
- }
-
- return mApplier != null;
+ return true;
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipInputConsumer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipInputConsumer.java
index 0f3ff36..8e3376f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipInputConsumer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipInputConsumer.java
@@ -146,11 +146,8 @@
"%s: Failed to create input consumer, %s", TAG, e);
}
mMainExecutor.execute(() -> {
- // Choreographer.getSfInstance() must be called on the thread that the input event
- // receiver should be receiving events
- // TODO(b/222697646): remove getSfInstance usage and use vsyncId for transactions
mInputEventReceiver = new InputEventReceiver(inputChannel,
- Looper.myLooper(), Choreographer.getSfInstance());
+ Looper.myLooper(), Choreographer.getInstance());
if (mRegistrationListener != null) {
mRegistrationListener.onRegistrationChanged(true /* isRegistered */);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java
index abf1a95..89d85e4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java
@@ -625,8 +625,7 @@
class PipResizeInputEventReceiver extends BatchedInputEventReceiver {
PipResizeInputEventReceiver(InputChannel channel, Looper looper) {
- // TODO(b/222697646): remove getSfInstance usage and use vsyncId for transactions
- super(channel, looper, Choreographer.getSfInstance());
+ super(channel, looper, Choreographer.getInstance());
}
public void onInputEvent(InputEvent event) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
index b296151..93c7529 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
@@ -46,6 +46,8 @@
Consts.TAG_WM_SHELL),
WM_SHELL_SYSUI_EVENTS(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
Consts.TAG_WM_SHELL),
+ WM_SHELL_DESKTOP_MODE(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
+ Consts.TAG_WM_SHELL),
TEST_GROUP(true, true, false, "WindowManagerShellProtoLogTest");
private final boolean mEnabled;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
index 9335438..26d0ec6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
@@ -23,6 +23,7 @@
import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
+import static android.view.WindowManager.fixScale;
import static android.window.TransitionInfo.FLAG_IS_INPUT_METHOD;
import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT;
@@ -167,10 +168,7 @@
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "addHandler: Remote");
ContentResolver resolver = mContext.getContentResolver();
- mTransitionAnimationScaleSetting = Settings.Global.getFloat(resolver,
- Settings.Global.TRANSITION_ANIMATION_SCALE,
- mContext.getResources().getFloat(
- R.dimen.config_appTransitionAnimationDurationScaleDefault));
+ mTransitionAnimationScaleSetting = getTransitionAnimationScaleSetting();
dispatchAnimScaleSetting(mTransitionAnimationScaleSetting);
resolver.registerContentObserver(
@@ -185,6 +183,12 @@
}
}
+ private float getTransitionAnimationScaleSetting() {
+ return fixScale(Settings.Global.getFloat(mContext.getContentResolver(),
+ Settings.Global.TRANSITION_ANIMATION_SCALE, mContext.getResources().getFloat(
+ R.dimen.config_appTransitionAnimationDurationScaleDefault)));
+ }
+
public ShellTransitions asRemoteTransitions() {
return mImpl;
}
@@ -963,9 +967,7 @@
@Override
public void onChange(boolean selfChange) {
super.onChange(selfChange);
- mTransitionAnimationScaleSetting = Settings.Global.getFloat(
- mContext.getContentResolver(), Settings.Global.TRANSITION_ANIMATION_SCALE,
- mTransitionAnimationScaleSetting);
+ mTransitionAnimationScaleSetting = getTransitionAnimationScaleSetting();
mMainExecutor.execute(() -> dispatchAnimScaleSetting(mTransitionAnimationScaleSetting));
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
index 98b5ee9..dc3deb1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
@@ -34,6 +34,7 @@
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.desktopmode.DesktopModeConstants;
/**
* Defines visuals and behaviors of a window decoration of a caption bar and shadows. It works with
@@ -163,7 +164,13 @@
View caption = mResult.mRootView.findViewById(R.id.caption);
caption.setOnTouchListener(mOnCaptionTouchListener);
View maximize = caption.findViewById(R.id.maximize_window);
- maximize.setOnClickListener(mOnCaptionButtonClickListener);
+ if (DesktopModeConstants.IS_FEATURE_ENABLED) {
+ // Hide maximize button when desktop mode is available
+ maximize.setVisibility(View.GONE);
+ } else {
+ maximize.setVisibility(View.VISIBLE);
+ maximize.setOnClickListener(mOnCaptionButtonClickListener);
+ }
View close = caption.findViewById(R.id.close_window);
close.setOnClickListener(mOnCaptionButtonClickListener);
View minimize = caption.findViewById(R.id.minimize_window);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
index 506a4c0..5e64a06 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
@@ -248,7 +248,7 @@
lp.setTrustedOverlay();
if (mViewHost == null) {
mViewHost = mSurfaceControlViewHostFactory.create(mDecorWindowContext, mDisplay,
- mCaptionWindowManager, true);
+ mCaptionWindowManager);
mViewHost.setView(outResult.mRootView, lp);
} else {
mViewHost.relayout(lp);
@@ -345,9 +345,8 @@
}
interface SurfaceControlViewHostFactory {
- default SurfaceControlViewHost create(
- Context c, Display d, WindowlessWindowManager wmm, boolean useSfChoreographer) {
- return new SurfaceControlViewHost(c, d, wmm, useSfChoreographer);
+ default SurfaceControlViewHost create(Context c, Display d, WindowlessWindowManager wmm) {
+ return new SurfaceControlViewHost(c, d, wmm);
}
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
index f865649..b29c436 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
@@ -16,9 +16,11 @@
package com.android.wm.shell;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.view.Display.DEFAULT_DISPLAY;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
@@ -30,6 +32,8 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.junit.Assume.assumeFalse;
@@ -38,9 +42,11 @@
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
import android.app.ActivityManager.RunningTaskInfo;
import android.app.TaskInfo;
+import android.app.WindowConfiguration;
import android.content.LocusId;
import android.content.pm.ParceledListSlice;
import android.os.Binder;
@@ -53,6 +59,8 @@
import android.window.ITaskOrganizerController;
import android.window.TaskAppearedInfo;
import android.window.WindowContainerToken;
+import android.window.WindowContainerTransaction;
+import android.window.WindowContainerTransaction.Change;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
@@ -628,6 +636,71 @@
verify(mTaskOrganizerController).restartTaskTopActivityProcessIfVisible(task1.token);
}
+ @Test
+ public void testPrepareClearBoundsForTasks() {
+ RunningTaskInfo task1 = createTaskInfo(1, WINDOWING_MODE_UNDEFINED);
+ task1.displayId = 1;
+ MockToken token1 = new MockToken();
+ task1.token = token1.token();
+ mOrganizer.onTaskAppeared(task1, null);
+
+ RunningTaskInfo task2 = createTaskInfo(2, WINDOWING_MODE_UNDEFINED);
+ task2.displayId = 1;
+ MockToken token2 = new MockToken();
+ task2.token = token2.token();
+ mOrganizer.onTaskAppeared(task2, null);
+
+ RunningTaskInfo otherDisplayTask = createTaskInfo(3, WINDOWING_MODE_UNDEFINED);
+ otherDisplayTask.displayId = 2;
+ MockToken otherDisplayToken = new MockToken();
+ otherDisplayTask.token = otherDisplayToken.token();
+ mOrganizer.onTaskAppeared(otherDisplayTask, null);
+
+ WindowContainerTransaction wct = mOrganizer.prepareClearBoundsForTasks(1);
+
+ assertEquals(wct.getChanges().size(), 2);
+ Change boundsChange1 = wct.getChanges().get(token1.binder());
+ assertNotNull(boundsChange1);
+ assertNotEquals(
+ (boundsChange1.getWindowSetMask() & WindowConfiguration.WINDOW_CONFIG_BOUNDS), 0);
+ assertTrue(boundsChange1.getConfiguration().windowConfiguration.getBounds().isEmpty());
+
+ Change boundsChange2 = wct.getChanges().get(token2.binder());
+ assertNotNull(boundsChange2);
+ assertNotEquals(
+ (boundsChange2.getWindowSetMask() & WindowConfiguration.WINDOW_CONFIG_BOUNDS), 0);
+ assertTrue(boundsChange2.getConfiguration().windowConfiguration.getBounds().isEmpty());
+ }
+
+ @Test
+ public void testPrepareClearFreeformForTasks() {
+ RunningTaskInfo task1 = createTaskInfo(1, WINDOWING_MODE_FREEFORM);
+ task1.displayId = 1;
+ MockToken token1 = new MockToken();
+ task1.token = token1.token();
+ mOrganizer.onTaskAppeared(task1, null);
+
+ RunningTaskInfo task2 = createTaskInfo(2, WINDOWING_MODE_MULTI_WINDOW);
+ task2.displayId = 1;
+ MockToken token2 = new MockToken();
+ task2.token = token2.token();
+ mOrganizer.onTaskAppeared(task2, null);
+
+ RunningTaskInfo otherDisplayTask = createTaskInfo(3, WINDOWING_MODE_FREEFORM);
+ otherDisplayTask.displayId = 2;
+ MockToken otherDisplayToken = new MockToken();
+ otherDisplayTask.token = otherDisplayToken.token();
+ mOrganizer.onTaskAppeared(otherDisplayTask, null);
+
+ WindowContainerTransaction wct = mOrganizer.prepareClearFreeformForTasks(1);
+
+ // Only task with freeform windowing mode and the right display should be updated
+ assertEquals(wct.getChanges().size(), 1);
+ Change wmModeChange1 = wct.getChanges().get(token1.binder());
+ assertNotNull(wmModeChange1);
+ assertEquals(wmModeChange1.getWindowingMode(), WINDOWING_MODE_UNDEFINED);
+ }
+
private static RunningTaskInfo createTaskInfo(int taskId, int windowingMode) {
RunningTaskInfo taskInfo = new RunningTaskInfo();
taskInfo.taskId = taskId;
@@ -635,4 +708,22 @@
return taskInfo;
}
+ private static class MockToken {
+ private final WindowContainerToken mToken;
+ private final IBinder mBinder;
+
+ MockToken() {
+ mToken = mock(WindowContainerToken.class);
+ mBinder = mock(IBinder.class);
+ when(mToken.asBinder()).thenReturn(mBinder);
+ }
+
+ WindowContainerToken token() {
+ return mToken;
+ }
+
+ IBinder binder() {
+ return mBinder;
+ }
+ }
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java
new file mode 100644
index 0000000..58f20da
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java
@@ -0,0 +1,194 @@
+/*
+ * Copyright (C) 2022 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.wm.shell.desktopmode;
+
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.WindowConfiguration;
+import android.os.Handler;
+import android.os.IBinder;
+import android.testing.AndroidTestingRunner;
+import android.window.WindowContainerToken;
+import android.window.WindowContainerTransaction;
+import android.window.WindowContainerTransaction.Change;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.wm.shell.RootDisplayAreaOrganizer;
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.sysui.ShellInit;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class DesktopModeControllerTest extends ShellTestCase {
+
+ @Mock
+ private ShellTaskOrganizer mShellTaskOrganizer;
+ @Mock
+ private RootDisplayAreaOrganizer mRootDisplayAreaOrganizer;
+ @Mock
+ private ShellExecutor mTestExecutor;
+ @Mock
+ private Handler mMockHandler;
+
+ private DesktopModeController mController;
+ private ShellInit mShellInit;
+
+ @Before
+ public void setUp() {
+ mShellInit = Mockito.spy(new ShellInit(mTestExecutor));
+
+ mController = new DesktopModeController(mContext, mShellInit, mShellTaskOrganizer,
+ mRootDisplayAreaOrganizer, mMockHandler);
+
+ mShellInit.init();
+ }
+
+ @Test
+ public void instantiate_addInitCallback() {
+ verify(mShellInit, times(1)).addInitCallback(any(), any());
+ }
+
+ @Test
+ public void testDesktopModeEnabled_taskWmClearedDisplaySetToFreeform() {
+ // Create a fake WCT to simulate setting task windowing mode to undefined
+ WindowContainerTransaction taskWct = new WindowContainerTransaction();
+ MockToken taskMockToken = new MockToken();
+ taskWct.setWindowingMode(taskMockToken.token(), WINDOWING_MODE_UNDEFINED);
+ when(mShellTaskOrganizer.prepareClearFreeformForTasks(mContext.getDisplayId())).thenReturn(
+ taskWct);
+
+ // Create a fake WCT to simulate setting display windowing mode to freeform
+ WindowContainerTransaction displayWct = new WindowContainerTransaction();
+ MockToken displayMockToken = new MockToken();
+ displayWct.setWindowingMode(displayMockToken.token(), WINDOWING_MODE_FREEFORM);
+ when(mRootDisplayAreaOrganizer.prepareWindowingModeChange(mContext.getDisplayId(),
+ WINDOWING_MODE_FREEFORM)).thenReturn(displayWct);
+
+ // The test
+ mController.updateDesktopModeEnabled(true);
+
+ ArgumentCaptor<WindowContainerTransaction> arg = ArgumentCaptor.forClass(
+ WindowContainerTransaction.class);
+ verify(mRootDisplayAreaOrganizer).applyTransaction(arg.capture());
+
+ // WCT should have 2 changes - clear task wm mode and set display wm mode
+ WindowContainerTransaction wct = arg.getValue();
+ assertThat(wct.getChanges()).hasSize(2);
+
+ // Verify executed WCT has a change for setting task windowing mode to undefined
+ Change taskWmModeChange = wct.getChanges().get(taskMockToken.binder());
+ assertThat(taskWmModeChange).isNotNull();
+ assertThat(taskWmModeChange.getWindowingMode()).isEqualTo(WINDOWING_MODE_UNDEFINED);
+
+ // Verify executed WCT has a change for setting display windowing mode to freeform
+ Change displayWmModeChange = wct.getChanges().get(displayMockToken.binder());
+ assertThat(displayWmModeChange).isNotNull();
+ assertThat(displayWmModeChange.getWindowingMode()).isEqualTo(WINDOWING_MODE_FREEFORM);
+ }
+
+ @Test
+ public void testDesktopModeDisabled_taskWmAndBoundsClearedDisplaySetToFullscreen() {
+ // Create a fake WCT to simulate setting task windowing mode to undefined
+ WindowContainerTransaction taskWmWct = new WindowContainerTransaction();
+ MockToken taskWmMockToken = new MockToken();
+ taskWmWct.setWindowingMode(taskWmMockToken.token(), WINDOWING_MODE_UNDEFINED);
+ when(mShellTaskOrganizer.prepareClearFreeformForTasks(mContext.getDisplayId())).thenReturn(
+ taskWmWct);
+
+ // Create a fake WCT to simulate clearing task bounds
+ WindowContainerTransaction taskBoundsWct = new WindowContainerTransaction();
+ MockToken taskBoundsMockToken = new MockToken();
+ taskBoundsWct.setBounds(taskBoundsMockToken.token(), null);
+ when(mShellTaskOrganizer.prepareClearBoundsForTasks(mContext.getDisplayId())).thenReturn(
+ taskBoundsWct);
+
+ // Create a fake WCT to simulate setting display windowing mode to fullscreen
+ WindowContainerTransaction displayWct = new WindowContainerTransaction();
+ MockToken displayMockToken = new MockToken();
+ displayWct.setWindowingMode(displayMockToken.token(), WINDOWING_MODE_FULLSCREEN);
+ when(mRootDisplayAreaOrganizer.prepareWindowingModeChange(mContext.getDisplayId(),
+ WINDOWING_MODE_FULLSCREEN)).thenReturn(displayWct);
+
+ // The test
+ mController.updateDesktopModeEnabled(false);
+
+ ArgumentCaptor<WindowContainerTransaction> arg = ArgumentCaptor.forClass(
+ WindowContainerTransaction.class);
+ verify(mRootDisplayAreaOrganizer).applyTransaction(arg.capture());
+
+ // WCT should have 3 changes - clear task wm mode and bounds and set display wm mode
+ WindowContainerTransaction wct = arg.getValue();
+ assertThat(wct.getChanges()).hasSize(3);
+
+ // Verify executed WCT has a change for setting task windowing mode to undefined
+ Change taskWmModeChange = wct.getChanges().get(taskWmMockToken.binder());
+ assertThat(taskWmModeChange).isNotNull();
+ assertThat(taskWmModeChange.getWindowingMode()).isEqualTo(WINDOWING_MODE_UNDEFINED);
+
+ // Verify executed WCT has a change for clearing task bounds
+ Change taskBoundsChange = wct.getChanges().get(taskBoundsMockToken.binder());
+ assertThat(taskBoundsChange).isNotNull();
+ assertThat(taskBoundsChange.getWindowSetMask()
+ & WindowConfiguration.WINDOW_CONFIG_BOUNDS).isNotEqualTo(0);
+ assertThat(taskBoundsChange.getConfiguration().windowConfiguration.getBounds().isEmpty())
+ .isTrue();
+
+ // Verify executed WCT has a change for setting display windowing mode to fullscreen
+ Change displayWmModeChange = wct.getChanges().get(displayMockToken.binder());
+ assertThat(displayWmModeChange).isNotNull();
+ assertThat(displayWmModeChange.getWindowingMode()).isEqualTo(WINDOWING_MODE_FULLSCREEN);
+ }
+
+ private static class MockToken {
+ private final WindowContainerToken mToken;
+ private final IBinder mBinder;
+
+ MockToken() {
+ mToken = mock(WindowContainerToken.class);
+ mBinder = mock(IBinder.class);
+ when(mToken.asBinder()).thenReturn(mBinder);
+ }
+
+ WindowContainerToken token() {
+ return mToken;
+ }
+
+ IBinder binder() {
+ return mBinder;
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
index 226843e..e11be31 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
@@ -24,7 +24,6 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.anyBoolean;
import static org.mockito.Mockito.argThat;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.eq;
@@ -107,7 +106,7 @@
mMockSurfaceControlFinishT = createMockSurfaceControlTransaction();
doReturn(mMockSurfaceControlViewHost).when(mMockSurfaceControlViewHostFactory)
- .create(any(), any(), any(), anyBoolean());
+ .create(any(), any(), any());
}
@Test
@@ -148,8 +147,7 @@
verify(decorContainerSurfaceBuilder, never()).build();
verify(taskBackgroundSurfaceBuilder, never()).build();
- verify(mMockSurfaceControlViewHostFactory, never())
- .create(any(), any(), any(), anyBoolean());
+ verify(mMockSurfaceControlViewHostFactory, never()).create(any(), any(), any());
verify(mMockSurfaceControlFinishT).hide(taskSurface);
@@ -207,8 +205,7 @@
verify(mMockSurfaceControlStartT).setLayer(taskBackgroundSurface, -1);
verify(mMockSurfaceControlStartT).show(taskBackgroundSurface);
- verify(mMockSurfaceControlViewHostFactory)
- .create(any(), eq(defaultDisplay), any(), anyBoolean());
+ verify(mMockSurfaceControlViewHostFactory).create(any(), eq(defaultDisplay), any());
verify(mMockSurfaceControlViewHost)
.setView(same(mMockView),
argThat(lp -> lp.height == 64
@@ -326,8 +323,7 @@
verify(mMockDisplayController).removeDisplayWindowListener(same(listener));
assertThat(mRelayoutResult.mRootView).isSameInstanceAs(mMockView);
- verify(mMockSurfaceControlViewHostFactory)
- .create(any(), eq(mockDisplay), any(), anyBoolean());
+ verify(mMockSurfaceControlViewHostFactory).create(any(), eq(mockDisplay), any());
verify(mMockSurfaceControlViewHost).setView(same(mMockView), any());
}
diff --git a/location/java/android/location/util/identity/CallerIdentity.java b/location/java/android/location/util/identity/CallerIdentity.java
index ade0ea4..2f8d92b 100644
--- a/location/java/android/location/util/identity/CallerIdentity.java
+++ b/location/java/android/location/util/identity/CallerIdentity.java
@@ -124,15 +124,22 @@
packageName, attributionTag, listenerId);
}
+ // in some tests these constants are loaded too early leading to an "incorrect" view of the
+ // current pid and uid. load lazily to prevent this problem in tests.
+ private static class Loader {
+ private static final int MY_UID = Process.myUid();
+ private static final int MY_PID = Process.myPid();
+ }
+
private final int mUid;
private final int mPid;
private final String mPackageName;
- private final @Nullable String mAttributionTag;
+ @Nullable private final String mAttributionTag;
- private final @Nullable String mListenerId;
+ @Nullable private final String mListenerId;
private CallerIdentity(int uid, int pid, String packageName,
@Nullable String attributionTag, @Nullable String listenerId) {
@@ -181,6 +188,24 @@
return mUid == Process.SYSTEM_UID;
}
+ /** Returns true if this identity represents the same user this code is running in. */
+ public boolean isMyUser() {
+ return UserHandle.getUserId(mUid) == UserHandle.getUserId(Loader.MY_UID);
+ }
+
+ /** Returns true if this identity represents the same uid this code is running in. */
+ public boolean isMyUid() {
+ return mUid == Loader.MY_UID;
+ }
+
+ /**
+ * Returns true if this identity represents the same process this code is running in. Returns
+ * false if the identity process is unknown.
+ */
+ public boolean isMyProcess() {
+ return mPid == Loader.MY_PID;
+ }
+
/**
* Adds this identity to the worksource supplied, or if not worksource is supplied, creates a
* new worksource representing this identity.
diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java
index c666140..650f360 100644
--- a/media/java/android/media/AudioSystem.java
+++ b/media/java/android/media/AudioSystem.java
@@ -1299,8 +1299,8 @@
/** @hide */ public static final String DEVICE_OUT_REMOTE_SUBMIX_NAME = "remote_submix";
/** @hide */ public static final String DEVICE_OUT_TELEPHONY_TX_NAME = "telephony_tx";
/** @hide */ public static final String DEVICE_OUT_LINE_NAME = "line";
- /** @hide */ public static final String DEVICE_OUT_HDMI_ARC_NAME = "hmdi_arc";
- /** @hide */ public static final String DEVICE_OUT_HDMI_EARC_NAME = "hmdi_earc";
+ /** @hide */ public static final String DEVICE_OUT_HDMI_ARC_NAME = "hdmi_arc";
+ /** @hide */ public static final String DEVICE_OUT_HDMI_EARC_NAME = "hdmi_earc";
/** @hide */ public static final String DEVICE_OUT_SPDIF_NAME = "spdif";
/** @hide */ public static final String DEVICE_OUT_FM_NAME = "fm_transmitter";
/** @hide */ public static final String DEVICE_OUT_AUX_LINE_NAME = "aux_line";
diff --git a/packages/SettingsLib/Spa/.idea/codeStyles/Project.xml b/packages/SettingsLib/Spa/.idea/codeStyles/Project.xml
index 318eeef..cb757d3 100644
--- a/packages/SettingsLib/Spa/.idea/codeStyles/Project.xml
+++ b/packages/SettingsLib/Spa/.idea/codeStyles/Project.xml
@@ -4,6 +4,9 @@
<option name="NAME_COUNT_TO_USE_STAR_IMPORT" value="2147483647" />
<option name="NAME_COUNT_TO_USE_STAR_IMPORT_FOR_MEMBERS" value="2147483647" />
</JetCodeStyleSettings>
+ <XML>
+ <option name="XML_KEEP_LINE_BREAKS" value="true" />
+ </XML>
<codeStyleSettings language="XML">
<option name="FORCE_REARRANGE_MODE" value="1" />
<indentOptions>
diff --git a/packages/SettingsLib/Spa/gallery/AndroidManifest.xml b/packages/SettingsLib/Spa/gallery/AndroidManifest.xml
index 914a45b..b5be7cd 100644
--- a/packages/SettingsLib/Spa/gallery/AndroidManifest.xml
+++ b/packages/SettingsLib/Spa/gallery/AndroidManifest.xml
@@ -18,7 +18,8 @@
package="com.android.settingslib.spa.gallery">
<application
- android:label="@string/app_name"
+ android:icon="@mipmap/ic_launcher"
+ android:label="@string/app_label"
android:supportsRtl="true">
<activity
android:name="com.android.settingslib.spa.gallery.MainActivity"
diff --git a/packages/SettingsLib/Spa/gallery/res/mipmap-anydpi-v26/ic_launcher.xml b/packages/SettingsLib/Spa/gallery/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 0000000..623ef56
--- /dev/null
+++ b/packages/SettingsLib/Spa/gallery/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2022 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.
+ -->
+
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+ <background android:drawable="@mipmap/ic_launcher_background" />
+ <foreground android:drawable="@mipmap/ic_launcher_foreground" />
+</adaptive-icon>
diff --git a/packages/SettingsLib/Spa/gallery/res/mipmap-xxxhdpi/ic_launcher.png b/packages/SettingsLib/Spa/gallery/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000..c3f1ab9
--- /dev/null
+++ b/packages/SettingsLib/Spa/gallery/res/mipmap-xxxhdpi/ic_launcher.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/gallery/res/mipmap-xxxhdpi/ic_launcher_background.png b/packages/SettingsLib/Spa/gallery/res/mipmap-xxxhdpi/ic_launcher_background.png
new file mode 100644
index 0000000..7da1549
--- /dev/null
+++ b/packages/SettingsLib/Spa/gallery/res/mipmap-xxxhdpi/ic_launcher_background.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/gallery/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/packages/SettingsLib/Spa/gallery/res/mipmap-xxxhdpi/ic_launcher_foreground.png
new file mode 100644
index 0000000..187964f
--- /dev/null
+++ b/packages/SettingsLib/Spa/gallery/res/mipmap-xxxhdpi/ic_launcher_foreground.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/gallery/res/values/strings.xml b/packages/SettingsLib/Spa/gallery/res/values/strings.xml
index 510e6c2..0d08d68 100644
--- a/packages/SettingsLib/Spa/gallery/res/values/strings.xml
+++ b/packages/SettingsLib/Spa/gallery/res/values/strings.xml
@@ -1,3 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2022 The Android Open Source Project
@@ -14,6 +15,8 @@
limitations under the License.
-->
<resources>
+ <!-- Gallery App label. [DO NOT TRANSLATE] -->
+ <string name="app_label" translatable="false">Gallery</string>
<!-- Gallery App name. [DO NOT TRANSLATE] -->
<string name="app_name" translatable="false">SpaLib Gallery</string>
</resources>
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/HomePage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/HomePage.kt
index 0d17cd2..ee077f4 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/HomePage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/HomePage.kt
@@ -54,6 +54,7 @@
ArgumentPageProvider.EntryItem(stringParam = "foo", intParam = 0)
SliderPageProvider.EntryItem()
+ SettingsPagerPageProvider.EntryItem()
FooterPageProvider.EntryItem()
}
}
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/PageRepository.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/PageRepository.kt
index 8f38d82..6465225 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/PageRepository.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/PageRepository.kt
@@ -33,6 +33,7 @@
SwitchPreferencePageProvider,
ArgumentPageProvider,
SliderPageProvider,
+ SettingsPagerPageProvider,
FooterPageProvider,
),
startDestination = Destinations.Home,
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SettingsPagerPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SettingsPagerPage.kt
new file mode 100644
index 0000000..5351ea6
--- /dev/null
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SettingsPagerPage.kt
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2022 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.settingslib.spa.gallery.page
+
+import android.os.Bundle
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.tooling.preview.Preview
+import com.android.settingslib.spa.framework.api.SettingsPageProvider
+import com.android.settingslib.spa.framework.compose.navigator
+import com.android.settingslib.spa.framework.theme.SettingsTheme
+import com.android.settingslib.spa.widget.preference.Preference
+import com.android.settingslib.spa.widget.preference.PreferenceModel
+import com.android.settingslib.spa.widget.scaffold.SettingsPager
+import com.android.settingslib.spa.widget.ui.SettingsTitle
+
+object SettingsPagerPageProvider : SettingsPageProvider {
+ override val name = "SettingsPager"
+
+ @Composable
+ override fun Page(arguments: Bundle?) {
+ SettingsPagerPage()
+ }
+
+ @Composable
+ fun EntryItem() {
+ Preference(object : PreferenceModel {
+ override val title = "Sample SettingsPager"
+ override val onClick = navigator(name)
+ })
+ }
+}
+
+@Composable
+private fun SettingsPagerPage() {
+ SettingsPager(listOf("Personal", "Work")) {
+ Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
+ SettingsTitle(title = "Page $it")
+ }
+ }
+}
+
+@Preview(showBackground = true)
+@Composable
+private fun SettingsPagerPagePreview() {
+ SettingsTheme {
+ SettingsPagerPage()
+ }
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsShape.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsShape.kt
new file mode 100644
index 0000000..20020d0
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsShape.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2022 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.settingslib.spa.framework.theme
+
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.ui.unit.dp
+
+object SettingsShape {
+ val CornerMedium = RoundedCornerShape(12.dp)
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsPager.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsPager.kt
new file mode 100644
index 0000000..1ec2390
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsPager.kt
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2022 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.settingslib.spa.widget.scaffold
+
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.TabRow
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import com.android.settingslib.spa.framework.theme.SettingsDimension
+
+@Composable
+fun SettingsPager(titles: List<String>, content: @Composable (page: Int) -> Unit) {
+ check(titles.isNotEmpty())
+ if (titles.size == 1) {
+ content(0)
+ return
+ }
+
+ Column {
+ var currentPage by rememberSaveable { mutableStateOf(0) }
+
+ TabRow(
+ selectedTabIndex = currentPage,
+ modifier = Modifier.padding(horizontal = SettingsDimension.itemPaddingEnd),
+ containerColor = Color.Transparent,
+ indicator = {},
+ divider = {},
+ ) {
+ titles.forEachIndexed { page, title ->
+ SettingsTab(
+ title = title,
+ selected = currentPage == page,
+ onClick = { currentPage = page },
+ )
+ }
+ }
+
+ content(currentPage)
+ }
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsTab.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsTab.kt
new file mode 100644
index 0000000..16d8dbc
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsTab.kt
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2022 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.settingslib.spa.widget.scaffold
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Tab
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import com.android.settingslib.spa.framework.theme.SettingsShape
+import com.android.settingslib.spa.framework.theme.SettingsTheme
+
+@Composable
+internal fun SettingsTab(
+ title: String,
+ selected: Boolean,
+ onClick: () -> Unit,
+) {
+ Tab(
+ selected = selected,
+ onClick = onClick,
+ modifier = Modifier
+ .height(48.dp)
+ .padding(horizontal = 4.dp, vertical = 6.dp)
+ .clip(SettingsShape.CornerMedium)
+ .background(color = when {
+ selected -> SettingsTheme.colorScheme.primaryContainer
+ else -> SettingsTheme.colorScheme.surface
+ }),
+ ) {
+ Text(
+ text = title,
+ style = MaterialTheme.typography.labelLarge,
+ color = when {
+ selected -> SettingsTheme.colorScheme.onPrimaryContainer
+ else -> SettingsTheme.colorScheme.secondaryText
+ },
+ )
+ }
+}
+
+@Preview
+@Composable
+private fun SettingsTabPreview() {
+ SettingsTheme {
+ Column {
+ SettingsTab(title = "Personal", selected = true) {}
+ SettingsTab(title = "Work", selected = false) {}
+ }
+ }
+}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/SettingsPagerKtTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/SettingsPagerKtTest.kt
new file mode 100644
index 0000000..f608e10
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/SettingsPagerKtTest.kt
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2022 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.settingslib.spa.widget.scaffold
+
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.assertIsNotSelected
+import androidx.compose.ui.test.assertIsSelected
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.test.performClick
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spa.widget.ui.SettingsTitle
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class SettingsPagerKtTest {
+ @get:Rule
+ val composeTestRule = createComposeRule()
+
+ @Test
+ fun twoPage_initialState() {
+ composeTestRule.setContent {
+ TestTwoPage()
+ }
+
+ composeTestRule.onNodeWithText("Personal").assertIsSelected()
+ composeTestRule.onNodeWithText("Page 0").assertIsDisplayed()
+ composeTestRule.onNodeWithText("Work").assertIsNotSelected()
+ composeTestRule.onNodeWithText("Page 1").assertDoesNotExist()
+ }
+
+ @Test
+ fun twoPage_afterSwitch() {
+ composeTestRule.setContent {
+ TestTwoPage()
+ }
+
+ composeTestRule.onNodeWithText("Work").performClick()
+
+ composeTestRule.onNodeWithText("Personal").assertIsNotSelected()
+ composeTestRule.onNodeWithText("Page 0").assertDoesNotExist()
+ composeTestRule.onNodeWithText("Work").assertIsSelected()
+ composeTestRule.onNodeWithText("Page 1").assertIsDisplayed()
+ }
+
+ @Test
+ fun onePage_initialState() {
+ composeTestRule.setContent {
+ SettingsPager(listOf("Personal")) {
+ SettingsTitle(title = "Page $it")
+ }
+ }
+
+ composeTestRule.onNodeWithText("Personal").assertDoesNotExist()
+ composeTestRule.onNodeWithText("Page 0").assertIsDisplayed()
+ composeTestRule.onNodeWithText("Page 1").assertDoesNotExist()
+ }
+}
+
+@Composable
+private fun TestTwoPage() {
+ SettingsPager(listOf("Personal", "Work")) {
+ SettingsTitle(title = "Page $it")
+ }
+}
diff --git a/packages/SettingsLib/SpaPrivileged/Android.bp b/packages/SettingsLib/SpaPrivileged/Android.bp
index 48f7ff2..ecbb219 100644
--- a/packages/SettingsLib/SpaPrivileged/Android.bp
+++ b/packages/SettingsLib/SpaPrivileged/Android.bp
@@ -29,5 +29,4 @@
"androidx.compose.runtime_runtime",
],
kotlincflags: ["-Xjvm-default=all"],
- min_sdk_version: "31",
}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/enterprise/EnterpriseRepository.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/enterprise/EnterpriseRepository.kt
new file mode 100644
index 0000000..ae0cb77
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/enterprise/EnterpriseRepository.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2022 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.settingslib.spaprivileged.framework.enterprise
+
+import android.app.admin.DevicePolicyManager
+import android.app.admin.DevicePolicyResources.Strings.Settings.PERSONAL_CATEGORY_HEADER
+import android.app.admin.DevicePolicyResources.Strings.Settings.WORK_CATEGORY_HEADER
+import android.content.Context
+import com.android.settingslib.spaprivileged.R
+
+class EnterpriseRepository(private val context: Context) {
+ private val resources by lazy {
+ checkNotNull(context.getSystemService(DevicePolicyManager::class.java)).resources
+ }
+
+ fun getEnterpriseString(updatableStringId: String, resId: Int): String =
+ resources.getString(updatableStringId) { context.getString(resId) }
+
+ fun getProfileTitle(isManagedProfile: Boolean): String = if (isManagedProfile) {
+ getEnterpriseString(WORK_CATEGORY_HEADER, R.string.category_work)
+ } else {
+ getEnterpriseString(PERSONAL_CATEGORY_HEADER, R.string.category_personal)
+ }
+}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/scaffold/WorkProfilePager.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/scaffold/WorkProfilePager.kt
new file mode 100644
index 0000000..09864a1
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/scaffold/WorkProfilePager.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2022 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.settingslib.spaprivileged.template.scaffold
+
+import android.content.pm.UserInfo
+import android.os.UserHandle
+import android.os.UserManager
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.ui.platform.LocalContext
+import com.android.settingslib.spa.widget.scaffold.SettingsPager
+import com.android.settingslib.spaprivileged.framework.enterprise.EnterpriseRepository
+
+@Composable
+fun WorkProfilePager(content: @Composable (userInfo: UserInfo) -> Unit) {
+ val context = LocalContext.current
+ val profiles = remember {
+ val userManager = checkNotNull(context.getSystemService(UserManager::class.java))
+ userManager.getProfiles(UserHandle.myUserId())
+ }
+ val titles = remember {
+ val enterpriseRepository = EnterpriseRepository(context)
+ profiles.map {
+ enterpriseRepository.getProfileTitle(isManagedProfile = it.isManagedProfile)
+ }
+ }
+
+ SettingsPager(titles) { page ->
+ content(profiles[page])
+ }
+}
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
index 42b992f..bc9490f 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
@@ -212,6 +212,8 @@
Settings.Secure.STATUS_BAR_SHOW_VIBRATE_ICON,
Settings.Secure.WEAR_TALKBACK_ENABLED,
Settings.Secure.HBM_SETTING_KEY,
- Settings.Secure.ACCESSIBILITY_SOFTWARE_CURSOR_ENABLED
+ Settings.Secure.ACCESSIBILITY_SOFTWARE_CURSOR_ENABLED,
+ Settings.Secure.ACCESSIBILITY_SOFTWARE_CURSOR_TRIGGER_HINTS_ENABLED,
+ Settings.Secure.ACCESSIBILITY_SOFTWARE_CURSOR_KEYBOARD_SHIFT_ENABLED,
};
}
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index 14b5855..2c99d71 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -346,5 +346,9 @@
VALIDATORS.put(Secure.WEAR_TALKBACK_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.HBM_SETTING_KEY, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.ACCESSIBILITY_SOFTWARE_CURSOR_ENABLED, BOOLEAN_VALIDATOR);
+ VALIDATORS.put(
+ Secure.ACCESSIBILITY_SOFTWARE_CURSOR_TRIGGER_HINTS_ENABLED, BOOLEAN_VALIDATOR);
+ VALIDATORS.put(
+ Secure.ACCESSIBILITY_SOFTWARE_CURSOR_KEYBOARD_SHIFT_ENABLED, BOOLEAN_VALIDATOR);
}
}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index c3b645e..a2ffcf3 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -1828,6 +1828,12 @@
dumpSetting(s, p,
Settings.Secure.ACCESSIBILITY_SOFTWARE_CURSOR_ENABLED,
SecureSettingsProto.Accessibility.ACCESSIBILITY_SOFTWARE_CURSOR_ENABLED);
+ dumpSetting(s, p,
+ Settings.Secure.ACCESSIBILITY_SOFTWARE_CURSOR_TRIGGER_HINTS_ENABLED,
+ SecureSettingsProto.Accessibility.SoftwareCursorSettings.TRIGGER_HINTS_ENABLED);
+ dumpSetting(s, p,
+ Settings.Secure.ACCESSIBILITY_SOFTWARE_CURSOR_KEYBOARD_SHIFT_ENABLED,
+ SecureSettingsProto.Accessibility.SoftwareCursorSettings.KEYBOARD_SHIFT_ENABLED);
p.end(accessibilityToken);
final long adaptiveSleepToken = p.start(SecureSettingsProto.ADAPTIVE_SLEEP);
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 634df39..a25f567 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -238,6 +238,9 @@
"com.android.systemui",
],
plugins: ["dagger2-compiler"],
+ lint: {
+ test: true,
+ },
}
// Opt-in config for optimizing the SystemUI target using R8.
diff --git a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java
index 47ea27f..0839338 100644
--- a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java
@@ -30,6 +30,8 @@
import android.widget.ImageView;
import android.widget.TextView;
+import androidx.core.graphics.ColorUtils;
+
import com.android.settingslib.Utils;
import com.android.systemui.R;
import com.android.systemui.animation.Interpolators;
@@ -42,7 +44,8 @@
* @hide
*/
final class WirelessChargingLayout extends FrameLayout {
- private static final long RIPPLE_ANIMATION_DURATION = 1500;
+ private static final long CIRCLE_RIPPLE_ANIMATION_DURATION = 1500;
+ private static final long ROUNDED_BOX_RIPPLE_ANIMATION_DURATION = 1750;
private static final int SCRIM_COLOR = 0x4C000000;
private static final int SCRIM_FADE_DURATION = 300;
private RippleView mRippleView;
@@ -131,17 +134,30 @@
"backgroundColor", SCRIM_COLOR, Color.TRANSPARENT);
scrimFadeOutAnimator.setDuration(SCRIM_FADE_DURATION);
scrimFadeOutAnimator.setInterpolator(Interpolators.LINEAR);
- scrimFadeOutAnimator.setStartDelay(RIPPLE_ANIMATION_DURATION - SCRIM_FADE_DURATION);
+ scrimFadeOutAnimator.setStartDelay((rippleShape == RippleShape.CIRCLE
+ ? CIRCLE_RIPPLE_ANIMATION_DURATION : ROUNDED_BOX_RIPPLE_ANIMATION_DURATION)
+ - SCRIM_FADE_DURATION);
AnimatorSet animatorSetScrim = new AnimatorSet();
animatorSetScrim.playTogether(scrimFadeInAnimator, scrimFadeOutAnimator);
animatorSetScrim.start();
mRippleView = findViewById(R.id.wireless_charging_ripple);
mRippleView.setupShader(rippleShape);
+ if (mRippleView.getRippleShape() == RippleShape.ROUNDED_BOX) {
+ mRippleView.setDuration(ROUNDED_BOX_RIPPLE_ANIMATION_DURATION);
+ mRippleView.setSparkleStrength(0.22f);
+ int color = Utils.getColorAttr(mRippleView.getContext(),
+ android.R.attr.colorAccent).getDefaultColor();
+ mRippleView.setColor(ColorUtils.setAlphaComponent(color, 28));
+ } else {
+ mRippleView.setDuration(CIRCLE_RIPPLE_ANIMATION_DURATION);
+ mRippleView.setColor(Utils.getColorAttr(mRippleView.getContext(),
+ android.R.attr.colorAccent).getDefaultColor());
+ }
+
OnAttachStateChangeListener listener = new OnAttachStateChangeListener() {
@Override
public void onViewAttachedToWindow(View view) {
- mRippleView.setDuration(RIPPLE_ANIMATION_DURATION);
mRippleView.startRipple();
mRippleView.removeOnAttachStateChangeListener(this);
}
@@ -232,13 +248,13 @@
int height = getMeasuredHeight();
mRippleView.setCenter(width * 0.5f, height * 0.5f);
if (mRippleView.getRippleShape() == RippleShape.ROUNDED_BOX) {
- mRippleView.setMaxSize(width * 1.5f, height * 1.5f);
+ // Those magic numbers are introduced for visual polish. This aspect ratio maps with
+ // the tablet's docking station.
+ mRippleView.setMaxSize(width * 1.36f, height * 1.46f);
} else {
float maxSize = Math.max(width, height);
mRippleView.setMaxSize(maxSize, maxSize);
}
- mRippleView.setColor(Utils.getColorAttr(mRippleView.getContext(),
- android.R.attr.colorAccent).getDefaultColor());
}
super.onLayout(changed, left, top, right, bottom);
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.java b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
index b29176b..42e6b02 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
@@ -151,8 +151,8 @@
public static final ResourceBooleanFlag STATUS_BAR_USER_SWITCHER =
new ResourceBooleanFlag(602, R.bool.flag_user_switcher_chip);
- public static final UnreleasedFlag STATUS_BAR_LETTERBOX_APPEARANCE =
- new UnreleasedFlag(603, false);
+ public static final ReleasedFlag STATUS_BAR_LETTERBOX_APPEARANCE =
+ new ReleasedFlag(603, false);
public static final UnreleasedFlag NEW_STATUS_BAR_PIPELINE = new UnreleasedFlag(604, true);
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt
index 458ed40..9260020 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt
@@ -36,6 +36,7 @@
import com.android.systemui.dreams.DreamOverlayStateController
import com.android.systemui.keyguard.WakefulnessLifecycle
import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.shade.NotifPanelEvents
import com.android.systemui.statusbar.CrossFadeHelper
import com.android.systemui.statusbar.NotificationLockscreenUserManager
import com.android.systemui.statusbar.StatusBarState
@@ -85,13 +86,22 @@
private val bypassController: KeyguardBypassController,
private val mediaCarouselController: MediaCarouselController,
private val notifLockscreenUserManager: NotificationLockscreenUserManager,
+ private val keyguardViewController: KeyguardViewController,
+ private val dreamOverlayStateController: DreamOverlayStateController,
configurationController: ConfigurationController,
wakefulnessLifecycle: WakefulnessLifecycle,
- private val keyguardViewController: KeyguardViewController,
- private val dreamOverlayStateController: DreamOverlayStateController
+ panelEventsEvents: NotifPanelEvents,
) {
/**
+ * Whether we "skip" QQS during panel expansion.
+ *
+ * This means that when expanding the panel we go directly to QS. Also when we are on QS and
+ * start closing the panel, it fully collapses instead of going to QQS.
+ */
+ private var skipQqsOnExpansion: Boolean = false
+
+ /**
* The root overlay of the hierarchy. This is where the media notification is attached to
* whenever the view is transitioning from one host to another. It also make sure that the
* view is always in its final state when it is attached to a view host.
@@ -504,6 +514,13 @@
mediaCarouselController.updateUserVisibility = {
mediaCarouselController.mediaCarouselScrollHandler.visibleToUser = isVisibleToUser()
}
+
+ panelEventsEvents.registerListener(object : NotifPanelEvents.Listener {
+ override fun onExpandImmediateChanged(isExpandImmediateEnabled: Boolean) {
+ skipQqsOnExpansion = isExpandImmediateEnabled
+ updateDesiredLocation()
+ }
+ })
}
private fun updateConfiguration() {
@@ -701,6 +718,9 @@
if (isCurrentlyInGuidedTransformation()) {
return false
}
+ if (skipQqsOnExpansion) {
+ return false
+ }
// This is an invalid transition, and can happen when using the camera gesture from the
// lock screen. Disallow.
if (previousLocation == LOCATION_LOCKSCREEN &&
@@ -852,6 +872,9 @@
* otherwise
*/
private fun getTransformationProgress(): Float {
+ if (skipQqsOnExpansion) {
+ return -1.0f
+ }
val progress = getQSTransformationProgress()
if (statusbarState != StatusBarState.KEYGUARD && progress >= 0) {
return progress
@@ -1042,6 +1065,10 @@
// reattach it without an animation
return LOCATION_LOCKSCREEN
}
+ if (skipQqsOnExpansion) {
+ // When doing an immediate expand or collapse, we want to keep it in QS.
+ return LOCATION_QS
+ }
return location
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
index 7e263d8..08cf57c 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
@@ -296,27 +296,17 @@
mMediaOutputController.setRefreshing(true);
// Update header icon
final int iconRes = getHeaderIconRes();
- final IconCompat iconCompat = getHeaderIcon();
- final Drawable appSourceDrawable = getAppSourceIcon();
+ final IconCompat headerIcon = getHeaderIcon();
+ final IconCompat appSourceIcon = getAppSourceIcon();
boolean colorSetUpdated = false;
mCastAppLayout.setVisibility(
mMediaOutputController.shouldShowLaunchSection()
? View.VISIBLE : View.GONE);
- if (appSourceDrawable != null) {
- mAppResourceIcon.setImageDrawable(appSourceDrawable);
- mAppButton.setCompoundDrawablesWithIntrinsicBounds(resizeDrawable(appSourceDrawable,
- mContext.getResources().getDimensionPixelSize(
- R.dimen.media_output_dialog_app_tier_icon_size
- )),
- null, null, null);
- } else {
- mAppResourceIcon.setVisibility(View.GONE);
- }
if (iconRes != 0) {
mHeaderIcon.setVisibility(View.VISIBLE);
mHeaderIcon.setImageResource(iconRes);
- } else if (iconCompat != null) {
- Icon icon = iconCompat.toIcon(mContext);
+ } else if (headerIcon != null) {
+ Icon icon = headerIcon.toIcon(mContext);
if (icon.getType() != Icon.TYPE_BITMAP && icon.getType() != Icon.TYPE_ADAPTIVE_BITMAP) {
// icon doesn't support getBitmap, use default value for color scheme
updateButtonBackgroundColorFilter();
@@ -336,6 +326,18 @@
} else {
mHeaderIcon.setVisibility(View.GONE);
}
+ if (appSourceIcon != null) {
+ Icon appIcon = appSourceIcon.toIcon(mContext);
+ mAppResourceIcon.setColorFilter(mMediaOutputController.getColorItemContent());
+ mAppResourceIcon.setImageIcon(appIcon);
+ } else {
+ Drawable appIconDrawable = mMediaOutputController.getAppSourceIconFromPackage();
+ if (appIconDrawable != null) {
+ mAppResourceIcon.setImageDrawable(appIconDrawable);
+ } else {
+ mAppResourceIcon.setVisibility(View.GONE);
+ }
+ }
if (mHeaderIcon.getVisibility() == View.VISIBLE) {
final int size = getHeaderIconSize();
final int padding = mContext.getResources().getDimensionPixelSize(
@@ -480,7 +482,7 @@
}
}
- abstract Drawable getAppSourceIcon();
+ abstract IconCompat getAppSourceIcon();
abstract int getHeaderIconRes();
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialog.java
index 310469d..35baf013 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialog.java
@@ -19,7 +19,6 @@
import android.app.AlertDialog;
import android.content.Context;
import android.graphics.Bitmap;
-import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.text.method.HideReturnsTransformationMethod;
import android.text.method.PasswordTransformationMethod;
@@ -116,8 +115,8 @@
}
@Override
- Drawable getAppSourceIcon() {
- return mMediaOutputController.getAppSourceIcon();
+ IconCompat getAppSourceIcon() {
+ return mMediaOutputController.getNotificationSmallIcon();
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogFactory.kt b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogFactory.kt
index 0fa3265..2b5d6fd 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogFactory.kt
@@ -16,6 +16,7 @@
package com.android.systemui.media.dialog
+import android.app.KeyguardManager
import android.content.Context
import android.media.AudioManager
import android.media.session.MediaSessionManager
@@ -45,7 +46,8 @@
private val dialogLaunchAnimator: DialogLaunchAnimator,
private val nearbyMediaDevicesManagerOptional: Optional<NearbyMediaDevicesManager>,
private val audioManager: AudioManager,
- private val powerExemptionManager: PowerExemptionManager
+ private val powerExemptionManager: PowerExemptionManager,
+ private val keyGuardManager: KeyguardManager
) {
var mediaOutputBroadcastDialog: MediaOutputBroadcastDialog? = null
@@ -57,7 +59,7 @@
val controller = MediaOutputController(context, packageName,
mediaSessionManager, lbm, starter, notifCollection,
dialogLaunchAnimator, nearbyMediaDevicesManagerOptional, audioManager,
- powerExemptionManager)
+ powerExemptionManager, keyGuardManager)
val dialog =
MediaOutputBroadcastDialog(context, aboveStatusBar, broadcastSender, controller)
mediaOutputBroadcastDialog = dialog
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
index 27095b3..f7d80e0 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
@@ -20,6 +20,7 @@
import android.annotation.CallbackExecutor;
import android.app.AlertDialog;
+import android.app.KeyguardManager;
import android.app.Notification;
import android.app.WallpaperColors;
import android.bluetooth.BluetoothLeBroadcast;
@@ -120,6 +121,7 @@
final List<MediaDevice> mCachedMediaDevices = new CopyOnWriteArrayList<>();
private final AudioManager mAudioManager;
private final PowerExemptionManager mPowerExemptionManager;
+ private final KeyguardManager mKeyGuardManager;
private final NearbyMediaDevicesManager mNearbyMediaDevicesManager;
private final Map<String, Integer> mNearbyDeviceInfoMap = new ConcurrentHashMap<>();
@@ -154,7 +156,8 @@
DialogLaunchAnimator dialogLaunchAnimator,
Optional<NearbyMediaDevicesManager> nearbyMediaDevicesManagerOptional,
AudioManager audioManager,
- PowerExemptionManager powerExemptionManager) {
+ PowerExemptionManager powerExemptionManager,
+ KeyguardManager keyGuardManager) {
mContext = context;
mPackageName = packageName;
mMediaSessionManager = mediaSessionManager;
@@ -163,6 +166,7 @@
mNotifCollection = notifCollection;
mAudioManager = audioManager;
mPowerExemptionManager = powerExemptionManager;
+ mKeyGuardManager = keyGuardManager;
InfoMediaManager imm = new InfoMediaManager(mContext, packageName, null, lbm);
mLocalMediaManager = new LocalMediaManager(mContext, lbm, imm, packageName);
mMetricLogger = new MediaOutputMetricLogger(mContext, mPackageName);
@@ -300,7 +304,7 @@
}
}
- Drawable getAppSourceIcon() {
+ Drawable getAppSourceIconFromPackage() {
if (mPackageName.isEmpty()) {
return null;
}
@@ -411,6 +415,24 @@
|| isSelectedDeviceInGroup;
}
+ IconCompat getNotificationSmallIcon() {
+ if (TextUtils.isEmpty(mPackageName)) {
+ return null;
+ }
+ for (NotificationEntry entry : mNotifCollection.getAllNotifs()) {
+ final Notification notification = entry.getSbn().getNotification();
+ if (notification.isMediaNotification()
+ && TextUtils.equals(entry.getSbn().getPackageName(), mPackageName)) {
+ final Icon icon = notification.getSmallIcon();
+ if (icon == null) {
+ break;
+ }
+ return IconCompat.createFromIcon(icon);
+ }
+ }
+ return null;
+ }
+
IconCompat getNotificationIcon() {
if (TextUtils.isEmpty(mPackageName)) {
return null;
@@ -701,7 +723,8 @@
ActivityLaunchAnimator.Controller controller =
mDialogLaunchAnimator.createActivityLaunchController(view);
- if (controller == null) {
+ if (controller == null || (mKeyGuardManager != null
+ && mKeyGuardManager.isKeyguardLocked())) {
mCallback.dismissDialog();
}
@@ -753,7 +776,7 @@
MediaOutputController controller = new MediaOutputController(mContext, mPackageName,
mMediaSessionManager, mLocalBluetoothManager, mActivityStarter,
mNotifCollection, mDialogLaunchAnimator, Optional.of(mNearbyMediaDevicesManager),
- mAudioManager, mPowerExemptionManager);
+ mAudioManager, mPowerExemptionManager, mKeyGuardManager);
MediaOutputBroadcastDialog dialog = new MediaOutputBroadcastDialog(mContext, true,
broadcastSender, controller);
mDialogLaunchAnimator.showFromView(dialog, mediaOutputDialog);
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java
index 9fb96b5..cb6f5a7 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java
@@ -17,7 +17,6 @@
package com.android.systemui.media.dialog;
import android.content.Context;
-import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.view.View;
import android.view.WindowManager;
@@ -81,8 +80,8 @@
}
@Override
- Drawable getAppSourceIcon() {
- return mMediaOutputController.getAppSourceIcon();
+ IconCompat getAppSourceIcon() {
+ return mMediaOutputController.getNotificationSmallIcon();
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt
index 8249a7c..543efed 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt
@@ -16,6 +16,7 @@
package com.android.systemui.media.dialog
+import android.app.KeyguardManager
import android.content.Context
import android.media.AudioManager
import android.media.session.MediaSessionManager
@@ -47,7 +48,8 @@
private val dialogLaunchAnimator: DialogLaunchAnimator,
private val nearbyMediaDevicesManagerOptional: Optional<NearbyMediaDevicesManager>,
private val audioManager: AudioManager,
- private val powerExemptionManager: PowerExemptionManager
+ private val powerExemptionManager: PowerExemptionManager,
+ private val keyGuardManager: KeyguardManager
) {
companion object {
private const val INTERACTION_JANK_TAG = "media_output"
@@ -63,7 +65,7 @@
context, packageName,
mediaSessionManager, lbm, starter, notifCollection,
dialogLaunchAnimator, nearbyMediaDevicesManagerOptional, audioManager,
- powerExemptionManager)
+ powerExemptionManager, keyGuardManager)
val dialog =
MediaOutputDialog(context, aboveStatusBar, broadcastSender, controller, uiEventLogger)
mediaOutputDialog = dialog
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java
index 833573d..be44202 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java
@@ -39,6 +39,7 @@
import javax.inject.Inject;
import javax.inject.Named;
+import javax.inject.Provider;
/** Controller for {@link QuickQSPanel}. */
@QSScope
@@ -52,20 +53,21 @@
}
};
- private final boolean mUsingCollapsedLandscapeMedia;
+ private final Provider<Boolean> mUsingCollapsedLandscapeMediaProvider;
@Inject
QuickQSPanelController(QuickQSPanel view, QSTileHost qsTileHost,
QSCustomizerController qsCustomizerController,
@Named(QS_USING_MEDIA_PLAYER) boolean usingMediaPlayer,
@Named(QUICK_QS_PANEL) MediaHost mediaHost,
- @Named(QS_USING_COLLAPSED_LANDSCAPE_MEDIA) boolean usingCollapsedLandscapeMedia,
+ @Named(QS_USING_COLLAPSED_LANDSCAPE_MEDIA)
+ Provider<Boolean> usingCollapsedLandscapeMediaProvider,
MetricsLogger metricsLogger, UiEventLogger uiEventLogger, QSLogger qsLogger,
DumpManager dumpManager
) {
super(view, qsTileHost, qsCustomizerController, usingMediaPlayer, mediaHost, metricsLogger,
uiEventLogger, qsLogger, dumpManager);
- mUsingCollapsedLandscapeMedia = usingCollapsedLandscapeMedia;
+ mUsingCollapsedLandscapeMediaProvider = usingCollapsedLandscapeMediaProvider;
}
@Override
@@ -80,7 +82,8 @@
int rotation = getRotation();
boolean isLandscape = rotation == RotationUtils.ROTATION_LANDSCAPE
|| rotation == RotationUtils.ROTATION_SEASCAPE;
- if (!mUsingCollapsedLandscapeMedia || !isLandscape) {
+ boolean usingCollapsedLandscapeMedia = mUsingCollapsedLandscapeMediaProvider.get();
+ if (!usingCollapsedLandscapeMedia || !isLandscape) {
mMediaHost.setExpansion(MediaHost.EXPANDED);
} else {
mMediaHost.setExpansion(MediaHost.COLLAPSED);
@@ -126,7 +129,6 @@
super.setTiles(tiles, /* collapsedView */ true);
}
- /** */
public void setContentMargins(int marginStart, int marginEnd) {
mView.setContentMargins(marginStart, marginEnd, mMediaHost.getHostView());
}
diff --git a/packages/SystemUI/src/com/android/systemui/ripple/RippleShader.kt b/packages/SystemUI/src/com/android/systemui/ripple/RippleShader.kt
index 56a1874..db7c1fd 100644
--- a/packages/SystemUI/src/com/android/systemui/ripple/RippleShader.kt
+++ b/packages/SystemUI/src/com/android/systemui/ripple/RippleShader.kt
@@ -67,7 +67,7 @@
float rippleInsideAlpha = (1.-inside) * in_fadeFill;
float rippleRingAlpha = (1.-sparkleRing) * in_fadeRing;
- float rippleAlpha = max(rippleInsideAlpha, rippleRingAlpha) * 0.45;
+ float rippleAlpha = max(rippleInsideAlpha, rippleRingAlpha) * in_color.a;
vec4 ripple = in_color * rippleAlpha;
return mix(ripple, vec4(sparkle), sparkle * in_sparkle_strength);
}
@@ -83,7 +83,7 @@
float rippleInsideAlpha = (1.-inside) * in_fadeFill;
float rippleRingAlpha = (1.-sparkleRing) * in_fadeRing;
- float rippleAlpha = max(rippleInsideAlpha, rippleRingAlpha) * 0.45;
+ float rippleAlpha = max(rippleInsideAlpha, rippleRingAlpha) * in_color.a;
vec4 ripple = in_color * rippleAlpha;
return mix(ripple, vec4(sparkle), sparkle * in_sparkle_strength);
}
@@ -99,7 +99,7 @@
float rippleInsideAlpha = (1.-inside) * in_fadeFill;
float rippleRingAlpha = (1.-sparkleRing) * in_fadeRing;
- float rippleAlpha = max(rippleInsideAlpha, rippleRingAlpha) * 0.45;
+ float rippleAlpha = max(rippleInsideAlpha, rippleRingAlpha) * in_color.a;
vec4 ripple = in_color * rippleAlpha;
return mix(ripple, vec4(sparkle), sparkle * in_sparkle_strength);
}
diff --git a/packages/SystemUI/src/com/android/systemui/ripple/RippleView.kt b/packages/SystemUI/src/com/android/systemui/ripple/RippleView.kt
index 8b01201..60c8f37 100644
--- a/packages/SystemUI/src/com/android/systemui/ripple/RippleView.kt
+++ b/packages/SystemUI/src/com/android/systemui/ripple/RippleView.kt
@@ -81,6 +81,7 @@
rippleShader.color = RIPPLE_DEFAULT_COLOR
rippleShader.progress = 0f
rippleShader.sparkleStrength = RIPPLE_SPARKLE_STRENGTH
+ rippleShader.pixelDensity = resources.displayMetrics.density
ripplePaint.shader = rippleShader
}
@@ -124,6 +125,13 @@
rippleShader.rippleFill = rippleFill
}
+ /**
+ * Set the intensity of the sparkles.
+ */
+ fun setSparkleStrength(strength: Float) {
+ rippleShader.sparkleStrength = strength
+ }
+
override fun onDraw(canvas: Canvas?) {
if (canvas == null || !canvas.isHardwareAccelerated) {
// Drawing with the ripple shader requires hardware acceleration, so skip
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotifPanelEvents.kt b/packages/SystemUI/src/com/android/systemui/shade/NotifPanelEvents.kt
index ce9d89f..4558061 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotifPanelEvents.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotifPanelEvents.kt
@@ -29,11 +29,25 @@
interface Listener {
/** Invoked when the notification panel starts or stops collapsing. */
- fun onPanelCollapsingChanged(isCollapsing: Boolean)
+ @JvmDefault
+ fun onPanelCollapsingChanged(isCollapsing: Boolean) {}
/**
* Invoked when the notification panel starts or stops launching an [android.app.Activity].
*/
- fun onLaunchingActivityChanged(isLaunchingActivity: Boolean)
+ @JvmDefault
+ fun onLaunchingActivityChanged(isLaunchingActivity: Boolean) {}
+
+ /**
+ * Invoked when the "expand immediate" attribute changes.
+ *
+ * An example of expanding immediately is when swiping down from the top with two fingers.
+ * Instead of going to QQS, we immediately expand to full QS.
+ *
+ * Another example is when full QS is showing, and we swipe up from the bottom. Instead of
+ * going to QQS, the panel fully collapses.
+ */
+ @JvmDefault
+ fun onExpandImmediateChanged(isExpandImmediateEnabled: Boolean) {}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 37a948d..e8ed00b 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -1690,12 +1690,17 @@
}
if (mQsExpanded) {
- mQsExpandImmediate = true;
+ setQsExpandImmediate(true);
setShowShelfOnly(true);
}
super.collapse(delayed, speedUpFactor);
}
+ private void setQsExpandImmediate(boolean expandImmediate) {
+ mQsExpandImmediate = expandImmediate;
+ mPanelEventsEmitter.notifyExpandImmediateChange(expandImmediate);
+ }
+
private void setShowShelfOnly(boolean shelfOnly) {
mNotificationStackScrollLayoutController.setShouldShowShelfOnly(
shelfOnly && !mSplitShadeEnabled);
@@ -1748,7 +1753,7 @@
public void expandWithQs() {
if (isQsExpansionEnabled()) {
- mQsExpandImmediate = true;
+ setQsExpandImmediate(true);
setShowShelfOnly(true);
}
if (mSplitShadeEnabled && isOnKeyguard()) {
@@ -2077,7 +2082,7 @@
if (mTwoFingerQsExpandPossible && isOpenQsEvent(event) && event.getY(event.getActionIndex())
< mStatusBarMinHeight) {
mMetricsLogger.count(COUNTER_PANEL_OPEN_QS, 1);
- mQsExpandImmediate = true;
+ setQsExpandImmediate(true);
setShowShelfOnly(true);
requestPanelHeightUpdate();
@@ -3279,7 +3284,7 @@
} else {
setListening(true);
}
- mQsExpandImmediate = false;
+ setQsExpandImmediate(false);
setShowShelfOnly(false);
mTwoFingerQsExpandPossible = false;
updateTrackingHeadsUp(null);
@@ -3337,7 +3342,7 @@
super.onTrackingStarted();
mScrimController.onTrackingStarted();
if (mQsFullyExpanded) {
- mQsExpandImmediate = true;
+ setQsExpandImmediate(true);
setShowShelfOnly(true);
}
mNotificationStackScrollLayoutController.onPanelTrackingStarted();
@@ -4898,7 +4903,7 @@
// to locked will trigger this event and we're not actually in the process of opening
// the shade, lockscreen is just always expanded
if (mSplitShadeEnabled && !isOnKeyguard()) {
- mQsExpandImmediate = true;
+ setQsExpandImmediate(true);
}
mCentralSurfaces.makeExpandedVisible(false);
}
@@ -4965,5 +4970,11 @@
cb.onPanelCollapsingChanged(isCollapsing);
}
}
+
+ private void notifyExpandImmediateChange(boolean expandImmediateEnabled) {
+ for (NotifPanelEvents.Listener cb : mListeners) {
+ cb.onExpandImmediateChanged(expandImmediateEnabled);
+ }
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
index d058b75..53e08ea 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
@@ -10,6 +10,7 @@
import android.provider.Settings
import android.view.Surface
import android.view.View
+import android.view.WindowManager.fixScale
import com.android.internal.jank.InteractionJankMonitor
import com.android.internal.jank.InteractionJankMonitor.CUJ_SCREEN_OFF
import com.android.internal.jank.InteractionJankMonitor.CUJ_SCREEN_OFF_SHOW_AOD
@@ -138,8 +139,8 @@
}
fun updateAnimatorDurationScale() {
- animatorDurationScale =
- globalSettings.getFloat(Settings.Global.ANIMATOR_DURATION_SCALE, 1f)
+ animatorDurationScale = fixScale(
+ globalSettings.getFloat(Settings.Global.ANIMATOR_DURATION_SCALE, 1f))
}
override fun shouldDelayKeyguardShow(): Boolean =
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt
index 8f2a432..fc20ac2 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt
@@ -138,7 +138,7 @@
ensureOverlayRemoved()
- val newRoot = SurfaceControlViewHost(context, context.display!!, wwm, false)
+ val newRoot = SurfaceControlViewHost(context, context.display!!, wwm)
val newView =
LightRevealScrim(context, null).apply {
revealEffect = createLightRevealEffect()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaHierarchyManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaHierarchyManagerTest.kt
index d65b6b3..369913d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaHierarchyManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaHierarchyManagerTest.kt
@@ -16,7 +16,6 @@
package com.android.systemui.media
-import org.mockito.Mockito.`when` as whenever
import android.graphics.Rect
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
@@ -30,6 +29,7 @@
import com.android.systemui.dreams.DreamOverlayStateController
import com.android.systemui.keyguard.WakefulnessLifecycle
import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.shade.testing.FakeNotifPanelEvents
import com.android.systemui.statusbar.NotificationLockscreenUserManager
import com.android.systemui.statusbar.StatusBarState
import com.android.systemui.statusbar.SysuiStatusBarStateController
@@ -50,10 +50,11 @@
import org.mockito.ArgumentMatchers.anyLong
import org.mockito.Captor
import org.mockito.Mock
-import org.mockito.Mockito.`when`
import org.mockito.Mockito.clearInvocations
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
+import org.mockito.Mockito.`when` as whenever
import org.mockito.junit.MockitoJUnit
@SmallTest
@@ -61,32 +62,19 @@
@TestableLooper.RunWithLooper
class MediaHierarchyManagerTest : SysuiTestCase() {
- @Mock
- private lateinit var lockHost: MediaHost
- @Mock
- private lateinit var qsHost: MediaHost
- @Mock
- private lateinit var qqsHost: MediaHost
- @Mock
- private lateinit var bypassController: KeyguardBypassController
- @Mock
- private lateinit var keyguardStateController: KeyguardStateController
- @Mock
- private lateinit var statusBarStateController: SysuiStatusBarStateController
- @Mock
- private lateinit var notificationLockscreenUserManager: NotificationLockscreenUserManager
- @Mock
- private lateinit var mediaCarouselController: MediaCarouselController
- @Mock
- private lateinit var mediaCarouselScrollHandler: MediaCarouselScrollHandler
- @Mock
- private lateinit var wakefulnessLifecycle: WakefulnessLifecycle
- @Mock
- private lateinit var keyguardViewController: KeyguardViewController
- @Mock
- private lateinit var uniqueObjectHostView: UniqueObjectHostView
- @Mock
- private lateinit var dreamOverlayStateController: DreamOverlayStateController
+ @Mock private lateinit var lockHost: MediaHost
+ @Mock private lateinit var qsHost: MediaHost
+ @Mock private lateinit var qqsHost: MediaHost
+ @Mock private lateinit var bypassController: KeyguardBypassController
+ @Mock private lateinit var keyguardStateController: KeyguardStateController
+ @Mock private lateinit var statusBarStateController: SysuiStatusBarStateController
+ @Mock private lateinit var notificationLockscreenUserManager: NotificationLockscreenUserManager
+ @Mock private lateinit var mediaCarouselController: MediaCarouselController
+ @Mock private lateinit var mediaCarouselScrollHandler: MediaCarouselScrollHandler
+ @Mock private lateinit var wakefulnessLifecycle: WakefulnessLifecycle
+ @Mock private lateinit var keyguardViewController: KeyguardViewController
+ @Mock private lateinit var uniqueObjectHostView: UniqueObjectHostView
+ @Mock private lateinit var dreamOverlayStateController: DreamOverlayStateController
@Captor
private lateinit var wakefullnessObserver: ArgumentCaptor<(WakefulnessLifecycle.Observer)>
@Captor
@@ -97,6 +85,7 @@
private lateinit var mediaHiearchyManager: MediaHierarchyManager
private lateinit var mediaFrame: ViewGroup
private val configurationController = FakeConfigurationController()
+ private val notifPanelEvents = FakeNotifPanelEvents()
@Before
fun setup() {
@@ -111,10 +100,12 @@
bypassController,
mediaCarouselController,
notificationLockscreenUserManager,
+ keyguardViewController,
+ dreamOverlayStateController,
configurationController,
wakefulnessLifecycle,
- keyguardViewController,
- dreamOverlayStateController)
+ notifPanelEvents,
+ )
verify(wakefulnessLifecycle).addObserver(wakefullnessObserver.capture())
verify(statusBarStateController).addCallback(statusBarCallback.capture())
setupHost(lockHost, MediaHierarchyManager.LOCATION_LOCKSCREEN, LOCKSCREEN_TOP)
@@ -212,6 +203,25 @@
}
@Test
+ fun calculateTransformationType_notOnLockscreen_returnsTransition() {
+ expandQS()
+
+ val transformType = mediaHiearchyManager.calculateTransformationType()
+
+ assertThat(transformType).isEqualTo(MediaHierarchyManager.TRANSFORMATION_TYPE_TRANSITION)
+ }
+
+ @Test
+ fun calculateTransformationType_onLockscreen_returnsTransition() {
+ goToLockscreen()
+ expandQS()
+
+ val transformType = mediaHiearchyManager.calculateTransformationType()
+
+ assertThat(transformType).isEqualTo(MediaHierarchyManager.TRANSFORMATION_TYPE_FADE)
+ }
+
+ @Test
fun calculateTransformationType_onLockShade_inSplitShade_goingToFullShade_returnsTransition() {
enableSplitShade()
goToLockscreen()
@@ -295,6 +305,18 @@
}
@Test
+ fun isCurrentlyInGuidedTransformation_hostsVisible_expandImmediateEnabled_returnsFalse() {
+ notifPanelEvents.changeExpandImmediate(expandImmediate = true)
+ goToLockscreen()
+ enterGuidedTransformation()
+ whenever(lockHost.visible).thenReturn(true)
+ whenever(qsHost.visible).thenReturn(true)
+ whenever(qqsHost.visible).thenReturn(true)
+
+ assertThat(mediaHiearchyManager.isCurrentlyInGuidedTransformation()).isFalse()
+ }
+
+ @Test
fun isCurrentlyInGuidedTransformation_hostNotVisible_returnsTrue() {
goToLockscreen()
enterGuidedTransformation()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java
index 314997d..6173692 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java
@@ -24,9 +24,9 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.app.KeyguardManager;
import android.content.Context;
import android.graphics.Bitmap;
-import android.graphics.drawable.Drawable;
import android.media.AudioManager;
import android.media.session.MediaController;
import android.media.session.MediaSessionManager;
@@ -88,6 +88,7 @@
private final DialogLaunchAnimator mDialogLaunchAnimator = mock(DialogLaunchAnimator.class);
private final AudioManager mAudioManager = mock(AudioManager.class);
private PowerExemptionManager mPowerExemptionManager = mock(PowerExemptionManager.class);
+ private KeyguardManager mKeyguardManager = mock(KeyguardManager.class);
private List<MediaController> mMediaControllers = new ArrayList<>();
private MediaOutputBaseDialogImpl mMediaOutputBaseDialogImpl;
@@ -119,7 +120,8 @@
mMediaOutputController = new MediaOutputController(mContext, TEST_PACKAGE,
mMediaSessionManager, mLocalBluetoothManager, mStarter,
mNotifCollection, mDialogLaunchAnimator,
- Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager);
+ Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager,
+ mKeyguardManager);
mMediaOutputBaseDialogImpl = new MediaOutputBaseDialogImpl(mContext, mBroadcastSender,
mMediaOutputController);
mMediaOutputBaseDialogImpl.onCreate(new Bundle());
@@ -276,7 +278,7 @@
}
@Override
- Drawable getAppSourceIcon() {
+ IconCompat getAppSourceIcon() {
return null;
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java
index 751c895..6dcf802 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java
@@ -31,6 +31,7 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.app.KeyguardManager;
import android.app.Notification;
import android.content.Context;
import android.graphics.drawable.Icon;
@@ -47,6 +48,7 @@
import android.service.notification.StatusBarNotification;
import android.testing.AndroidTestingRunner;
import android.text.TextUtils;
+import android.view.View;
import androidx.core.graphics.drawable.IconCompat;
import androidx.test.filters.SmallTest;
@@ -57,6 +59,7 @@
import com.android.settingslib.media.MediaDevice;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.animation.ActivityLaunchAnimator;
import com.android.systemui.animation.DialogLaunchAnimator;
import com.android.systemui.media.nearby.NearbyMediaDevicesManager;
import com.android.systemui.plugins.ActivityStarter;
@@ -102,11 +105,16 @@
private RoutingSessionInfo mRemoteSessionInfo = mock(RoutingSessionInfo.class);
private ActivityStarter mStarter = mock(ActivityStarter.class);
private AudioManager mAudioManager = mock(AudioManager.class);
+ private KeyguardManager mKeyguardManager = mock(KeyguardManager.class);
private PowerExemptionManager mPowerExemptionManager = mock(PowerExemptionManager.class);
private CommonNotifCollection mNotifCollection = mock(CommonNotifCollection.class);
private final DialogLaunchAnimator mDialogLaunchAnimator = mock(DialogLaunchAnimator.class);
+ private final ActivityLaunchAnimator.Controller mActivityLaunchAnimatorController = mock(
+ ActivityLaunchAnimator.Controller.class);
private final NearbyMediaDevicesManager mNearbyMediaDevicesManager = mock(
NearbyMediaDevicesManager.class);
+ private View mDialogLaunchView = mock(View.class);
+ private MediaOutputController.Callback mCallback = mock(MediaOutputController.Callback.class);
private Context mSpyContext;
private MediaOutputController mMediaOutputController;
@@ -131,7 +139,8 @@
mMediaOutputController = new MediaOutputController(mSpyContext, TEST_PACKAGE_NAME,
mMediaSessionManager, mLocalBluetoothManager, mStarter,
mNotifCollection, mDialogLaunchAnimator,
- Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager);
+ Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager,
+ mKeyguardManager);
mLocalMediaManager = spy(mMediaOutputController.mLocalMediaManager);
mMediaOutputController.mLocalMediaManager = mLocalMediaManager;
MediaDescription.Builder builder = new MediaDescription.Builder();
@@ -183,7 +192,8 @@
mMediaOutputController = new MediaOutputController(mSpyContext, null,
mMediaSessionManager, mLocalBluetoothManager, mStarter,
mNotifCollection, mDialogLaunchAnimator,
- Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager);
+ Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager,
+ mKeyguardManager);
mMediaOutputController.start(mCb);
@@ -212,7 +222,8 @@
mMediaOutputController = new MediaOutputController(mSpyContext, null,
mMediaSessionManager, mLocalBluetoothManager, mStarter,
mNotifCollection, mDialogLaunchAnimator,
- Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager);
+ Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager,
+ mKeyguardManager);
mMediaOutputController.start(mCb);
@@ -461,7 +472,8 @@
mMediaOutputController = new MediaOutputController(mSpyContext, null,
mMediaSessionManager, mLocalBluetoothManager, mStarter,
mNotifCollection, mDialogLaunchAnimator,
- Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager);
+ Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager,
+ mKeyguardManager);
assertThat(mMediaOutputController.getNotificationIcon()).isNull();
}
@@ -557,4 +569,16 @@
verify(mPowerExemptionManager).addToTemporaryAllowList(anyString(), anyInt(), anyString(),
anyLong());
}
+
+ @Test
+ public void launchBluetoothPairing_isKeyguardLocked_dismissDialog() {
+ when(mDialogLaunchAnimator.createActivityLaunchController(mDialogLaunchView)).thenReturn(
+ mActivityLaunchAnimatorController);
+ when(mKeyguardManager.isKeyguardLocked()).thenReturn(true);
+ mMediaOutputController.mCallback = this.mCallback;
+
+ mMediaOutputController.launchBluetoothPairing(mDialogLaunchView);
+
+ verify(mCallback).dismissDialog();
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java
index 4779d32..9557513 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java
@@ -24,6 +24,7 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.app.KeyguardManager;
import android.media.AudioManager;
import android.media.MediaRoute2Info;
import android.media.session.MediaController;
@@ -85,6 +86,7 @@
NearbyMediaDevicesManager.class);
private final AudioManager mAudioManager = mock(AudioManager.class);
private PowerExemptionManager mPowerExemptionManager = mock(PowerExemptionManager.class);
+ private KeyguardManager mKeyguardManager = mock(KeyguardManager.class);
private List<MediaController> mMediaControllers = new ArrayList<>();
private MediaOutputDialog mMediaOutputDialog;
@@ -104,7 +106,8 @@
mMediaOutputController = new MediaOutputController(mContext, TEST_PACKAGE,
mMediaSessionManager, mLocalBluetoothManager, mStarter,
mNotifCollection, mDialogLaunchAnimator,
- Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager);
+ Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager,
+ mKeyguardManager);
mMediaOutputController.mLocalMediaManager = mLocalMediaManager;
mMediaOutputDialog = new MediaOutputDialog(mContext, false, mBroadcastSender,
mMediaOutputController, mUiEventLogger);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt
index 1f28210..e4f47fd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt
@@ -17,8 +17,8 @@
package com.android.systemui.qs
import android.content.res.Configuration
-import android.test.suitebuilder.annotation.SmallTest
import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
import com.android.internal.logging.MetricsLogger
import com.android.internal.logging.testing.UiEventLoggerFake
import com.android.systemui.SysuiTestCase
@@ -38,38 +38,32 @@
import org.mockito.ArgumentMatchers.anyBoolean
import org.mockito.Captor
import org.mockito.Mock
-import org.mockito.Mockito.`when`
import org.mockito.Mockito.any
+import org.mockito.Mockito.reset
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations
@SmallTest
@RunWith(AndroidTestingRunner::class)
class QuickQSPanelControllerTest : SysuiTestCase() {
- @Mock
- private lateinit var quickQSPanel: QuickQSPanel
- @Mock
- private lateinit var qsTileHost: QSTileHost
- @Mock
- private lateinit var qsCustomizerController: QSCustomizerController
- @Mock
- private lateinit var mediaHost: MediaHost
- @Mock
- private lateinit var metricsLogger: MetricsLogger
+ @Mock private lateinit var quickQSPanel: QuickQSPanel
+ @Mock private lateinit var qsTileHost: QSTileHost
+ @Mock private lateinit var qsCustomizerController: QSCustomizerController
+ @Mock private lateinit var mediaHost: MediaHost
+ @Mock private lateinit var metricsLogger: MetricsLogger
+ @Mock private lateinit var qsLogger: QSLogger
+ @Mock private lateinit var tile: QSTile
+ @Mock private lateinit var tileLayout: TileLayout
+ @Mock private lateinit var tileView: QSTileView
+ @Captor private lateinit var captor: ArgumentCaptor<QSPanel.OnConfigurationChangedListener>
+
private val uiEventLogger = UiEventLoggerFake()
- @Mock
- private lateinit var qsLogger: QSLogger
private val dumpManager = DumpManager()
- @Mock
- private lateinit var tile: QSTile
- @Mock
- private lateinit var tileLayout: TileLayout
- @Mock
- private lateinit var tileView: QSTileView
- @Captor
- private lateinit var captor: ArgumentCaptor<QSPanel.OnConfigurationChangedListener>
+
+ private var usingCollapsedLandscapeMedia = true
private lateinit var controller: TestQuickQSPanelController
@@ -77,24 +71,24 @@
fun setUp() {
MockitoAnnotations.initMocks(this)
- `when`(quickQSPanel.tileLayout).thenReturn(tileLayout)
- `when`(quickQSPanel.isAttachedToWindow).thenReturn(true)
- `when`(quickQSPanel.dumpableTag).thenReturn("")
- `when`(quickQSPanel.resources).thenReturn(mContext.resources)
- `when`(qsTileHost.createTileView(any(), any(), anyBoolean())).thenReturn(tileView)
+ whenever(quickQSPanel.tileLayout).thenReturn(tileLayout)
+ whenever(quickQSPanel.isAttachedToWindow).thenReturn(true)
+ whenever(quickQSPanel.dumpableTag).thenReturn("")
+ whenever(quickQSPanel.resources).thenReturn(mContext.resources)
+ whenever(qsTileHost.createTileView(any(), any(), anyBoolean())).thenReturn(tileView)
- controller = TestQuickQSPanelController(
+ controller =
+ TestQuickQSPanelController(
quickQSPanel,
qsTileHost,
qsCustomizerController,
- false,
+ /* usingMediaPlayer = */ false,
mediaHost,
- true,
+ { usingCollapsedLandscapeMedia },
metricsLogger,
uiEventLogger,
qsLogger,
- dumpManager
- )
+ dumpManager)
controller.init()
}
@@ -106,9 +100,9 @@
@Test
fun testTileSublistWithFewerTiles_noCrash() {
- `when`(quickQSPanel.numQuickTiles).thenReturn(3)
+ whenever(quickQSPanel.numQuickTiles).thenReturn(3)
- `when`(qsTileHost.tiles).thenReturn(listOf(tile, tile))
+ whenever(qsTileHost.tiles).thenReturn(listOf(tile, tile))
controller.setTiles()
}
@@ -116,8 +110,8 @@
@Test
fun testTileSublistWithTooManyTiles() {
val limit = 3
- `when`(quickQSPanel.numQuickTiles).thenReturn(limit)
- `when`(qsTileHost.tiles).thenReturn(listOf(tile, tile, tile, tile))
+ whenever(quickQSPanel.numQuickTiles).thenReturn(limit)
+ whenever(qsTileHost.tiles).thenReturn(listOf(tile, tile, tile, tile))
controller.setTiles()
@@ -125,39 +119,61 @@
}
@Test
- fun testMediaExpansionUpdatedWhenConfigurationChanged() {
+ fun mediaExpansion_afterConfigChange_inLandscape_collapsedInLandscapeTrue_updatesToCollapsed() {
// times(2) because both controller and base controller are registering their listeners
verify(quickQSPanel, times(2)).addOnConfigurationChangedListener(captor.capture())
// verify that media starts in the expanded state by default
verify(mediaHost).expansion = MediaHostState.EXPANDED
- // Rotate device, verify media size updated
+ // Rotate device, verify media size updated to collapsed
+ usingCollapsedLandscapeMedia = true
controller.setRotation(RotationUtils.ROTATION_LANDSCAPE)
captor.allValues.forEach { it.onConfigurationChange(Configuration.EMPTY) }
verify(mediaHost).expansion = MediaHostState.COLLAPSED
}
+ @Test
+ fun mediaExpansion_afterConfigChange_landscape_collapsedInLandscapeFalse_remainsExpanded() {
+ // times(2) because both controller and base controller are registering their listeners
+ verify(quickQSPanel, times(2)).addOnConfigurationChangedListener(captor.capture())
+ reset(mediaHost)
+
+ usingCollapsedLandscapeMedia = false
+ controller.setRotation(RotationUtils.ROTATION_LANDSCAPE)
+ captor.allValues.forEach { it.onConfigurationChange(Configuration.EMPTY) }
+
+ verify(mediaHost).expansion = MediaHostState.EXPANDED
+ }
+
class TestQuickQSPanelController(
view: QuickQSPanel,
qsTileHost: QSTileHost,
qsCustomizerController: QSCustomizerController,
usingMediaPlayer: Boolean,
mediaHost: MediaHost,
- usingCollapsedLandscapeMedia: Boolean,
+ usingCollapsedLandscapeMedia: () -> Boolean,
metricsLogger: MetricsLogger,
uiEventLogger: UiEventLoggerFake,
qsLogger: QSLogger,
dumpManager: DumpManager
- ) : QuickQSPanelController(view, qsTileHost, qsCustomizerController, usingMediaPlayer,
- mediaHost, usingCollapsedLandscapeMedia, metricsLogger, uiEventLogger, qsLogger,
- dumpManager) {
+ ) :
+ QuickQSPanelController(
+ view,
+ qsTileHost,
+ qsCustomizerController,
+ usingMediaPlayer,
+ mediaHost,
+ usingCollapsedLandscapeMedia,
+ metricsLogger,
+ uiEventLogger,
+ qsLogger,
+ dumpManager) {
private var rotation = RotationUtils.ROTATION_NONE
- @Override
- override fun getRotation(): Int = rotation
+ @Override override fun getRotation(): Int = rotation
fun setRotation(newRotation: Int) {
rotation = newRotation
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/testing/FakeNotifPanelEvents.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/testing/FakeNotifPanelEvents.kt
new file mode 100644
index 0000000..d052138
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/testing/FakeNotifPanelEvents.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2022 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.shade.testing
+
+import com.android.systemui.shade.NotifPanelEvents
+
+/** Fake implementation of [NotifPanelEvents] for testing. */
+class FakeNotifPanelEvents : NotifPanelEvents {
+
+ private val listeners = mutableListOf<NotifPanelEvents.Listener>()
+
+ override fun registerListener(listener: NotifPanelEvents.Listener) {
+ listeners.add(listener)
+ }
+
+ override fun unregisterListener(listener: NotifPanelEvents.Listener) {
+ listeners.remove(listener)
+ }
+
+ fun changeExpandImmediate(expandImmediate: Boolean) {
+ listeners.forEach { it.onExpandImmediateChanged(expandImmediate) }
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/mockito/KotlinMockitoHelpers.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/util/mockito/KotlinMockitoHelpers.kt
index 309acdf..f539dbd 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/mockito/KotlinMockitoHelpers.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/mockito/KotlinMockitoHelpers.kt
@@ -91,6 +91,8 @@
fun capture(): T = wrapped.capture()
val value: T
get() = wrapped.value
+ val allValues: List<T>
+ get() = wrapped.allValues
}
/**
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 297e6a2..c8519c1 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -18414,11 +18414,11 @@
attributionSource.getUid()), Process.SHELL_UID);
final long identity = Binder.clearCallingIdentity();
try {
- return superImpl.apply(code, new AttributionSource(shellUid,
+ return superImpl.apply(code, new AttributionSource(shellUid, /*pid*/ -1,
"com.android.shell", attributionSource.getAttributionTag(),
- attributionSource.getToken(), attributionSource.getNext()),
- shouldCollectAsyncNotedOp, message, shouldCollectMessage,
- skiProxyOperation);
+ attributionSource.getToken(), /*renouncedPermissions*/ null,
+ attributionSource.getNext()), shouldCollectAsyncNotedOp, message,
+ shouldCollectMessage, skiProxyOperation);
} finally {
Binder.restoreCallingIdentity(identity);
}
@@ -18465,12 +18465,13 @@
attributionSource.getUid()), Process.SHELL_UID);
final long identity = Binder.clearCallingIdentity();
try {
- return superImpl.apply(code, new AttributionSource(shellUid,
+ return superImpl.apply(code, new AttributionSource(shellUid, /*pid*/ -1,
"com.android.shell", attributionSource.getAttributionTag(),
- attributionSource.getToken(), attributionSource.getNext()),
- startIfModeDefault, shouldCollectAsyncNotedOp, message,
- shouldCollectMessage, skipProxyOperation, proxyAttributionFlags,
- proxiedAttributionFlags, attributionChainId);
+ attributionSource.getToken(), /*renouncedPermissions*/ null,
+ attributionSource.getNext()), startIfModeDefault,
+ shouldCollectAsyncNotedOp, message, shouldCollectMessage,
+ skipProxyOperation, proxyAttributionFlags, proxiedAttributionFlags,
+ attributionChainId);
} finally {
Binder.restoreCallingIdentity(identity);
}
@@ -18489,10 +18490,10 @@
attributionSource.getUid()), Process.SHELL_UID);
final long identity = Binder.clearCallingIdentity();
try {
- superImpl.apply(code, new AttributionSource(shellUid,
+ superImpl.apply(code, new AttributionSource(shellUid, /*pid*/ -1,
"com.android.shell", attributionSource.getAttributionTag(),
- attributionSource.getToken(), attributionSource.getNext()),
- skipProxyOperation);
+ attributionSource.getToken(), /*renouncedPermissions*/ null,
+ attributionSource.getNext()), skipProxyOperation);
} finally {
Binder.restoreCallingIdentity(identity);
}
diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java
index 725d205..31d9f96 100644
--- a/services/core/java/com/android/server/am/BroadcastQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastQueue.java
@@ -1969,8 +1969,7 @@
info.activityInfo.applicationInfo, true,
r.intent.getFlags() | Intent.FLAG_FROM_BACKGROUND,
new HostingRecord(HostingRecord.HOSTING_TYPE_BROADCAST, r.curComponent,
- r.intent.getAction(), (r.alarm ? HostingRecord.TRIGGER_TYPE_ALARM
- : HostingRecord.TRIGGER_TYPE_UNKNOWN)),
+ r.intent.getAction(), getHostingRecordTriggerType(r)),
isActivityCapable ? ZYGOTE_POLICY_FLAG_LATENCY_SENSITIVE : ZYGOTE_POLICY_FLAG_EMPTY,
(r.intent.getFlags() & Intent.FLAG_RECEIVER_BOOT_UPGRADE) != 0, false);
if (r.curApp == null) {
@@ -1993,6 +1992,16 @@
mPendingBroadcastRecvIndex = recIdx;
}
+ private String getHostingRecordTriggerType(BroadcastRecord r) {
+ if (r.alarm) {
+ return HostingRecord.TRIGGER_TYPE_ALARM;
+ } else if (r.pushMessage) {
+ return HostingRecord.TRIGGER_TYPE_PUSH_MESSAGE;
+ } else if (r.pushMessageOverQuota) {
+ return HostingRecord.TRIGGER_TYPE_PUSH_MESSAGE_OVER_QUOTA;
+ }
+ return HostingRecord.TRIGGER_TYPE_UNKNOWN;
+ }
@Nullable
private String getTargetPackage(BroadcastRecord r) {
diff --git a/services/core/java/com/android/server/am/BroadcastRecord.java b/services/core/java/com/android/server/am/BroadcastRecord.java
index 12bab273..18fbfde 100644
--- a/services/core/java/com/android/server/am/BroadcastRecord.java
+++ b/services/core/java/com/android/server/am/BroadcastRecord.java
@@ -72,6 +72,8 @@
final boolean ordered; // serialize the send to receivers?
final boolean sticky; // originated from existing sticky data?
final boolean alarm; // originated from an alarm triggering?
+ final boolean pushMessage; // originated from a push message?
+ final boolean pushMessageOverQuota; // originated from a push message which was over quota?
final boolean initialSticky; // initial broadcast from register to sticky?
final int userId; // user id this broadcast was for
final String resolvedType; // the resolved data type
@@ -320,6 +322,8 @@
mBackgroundActivityStartsToken = backgroundActivityStartsToken;
this.timeoutExempt = timeoutExempt;
alarm = options != null && options.isAlarmBroadcast();
+ pushMessage = options != null && options.isPushMessagingBroadcast();
+ pushMessageOverQuota = options != null && options.isPushMessagingOverQuotaBroadcast();
this.filterExtrasForReceiver = filterExtrasForReceiver;
}
@@ -374,6 +378,8 @@
mBackgroundActivityStartsToken = from.mBackgroundActivityStartsToken;
timeoutExempt = from.timeoutExempt;
alarm = from.alarm;
+ pushMessage = from.pushMessage;
+ pushMessageOverQuota = from.pushMessageOverQuota;
filterExtrasForReceiver = from.filterExtrasForReceiver;
}
diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java
index b76bad4..653b602 100644
--- a/services/core/java/com/android/server/am/CachedAppOptimizer.java
+++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java
@@ -110,8 +110,6 @@
private static final String ATRACE_COMPACTION_TRACK = "Compaction";
- private static final int FREEZE_BINDER_TIMEOUT_MS = 100;
-
// Defaults for phenotype flags.
@VisibleForTesting static final Boolean DEFAULT_USE_COMPACTION = false;
@VisibleForTesting static final Boolean DEFAULT_USE_FREEZER = true;
@@ -929,13 +927,11 @@
* @param pid the target pid for which binder transactions are to be frozen
* @param freeze specifies whether to flush transactions and then freeze (true) or unfreeze
* binder for the specificed pid.
- * @param timeoutMs the timeout in milliseconds to wait for the binder interface to freeze
- * before giving up.
*
* @throws RuntimeException in case a flush/freeze operation could not complete successfully.
* @return 0 if success, or -EAGAIN indicating there's pending transaction.
*/
- public static native int freezeBinder(int pid, boolean freeze, int timeoutMs);
+ private static native int freezeBinder(int pid, boolean freeze);
/**
* Retrieves binder freeze info about a process.
@@ -1302,7 +1298,7 @@
long freezeTime = opt.getFreezeUnfreezeTime();
try {
- freezeBinder(pid, false, FREEZE_BINDER_TIMEOUT_MS);
+ freezeBinder(pid, false);
} catch (RuntimeException e) {
Slog.e(TAG_AM, "Unable to unfreeze binder for " + pid + " " + app.processName
+ ". Killing it");
@@ -1934,7 +1930,7 @@
// Freeze binder interface before the process, to flush any
// transactions that might be pending.
try {
- if (freezeBinder(pid, true, FREEZE_BINDER_TIMEOUT_MS) != 0) {
+ if (freezeBinder(pid, true) != 0) {
rescheduleFreeze(proc, "outstanding txns");
return;
}
diff --git a/services/core/java/com/android/server/am/HostingRecord.java b/services/core/java/com/android/server/am/HostingRecord.java
index efc2a27..2498f76 100644
--- a/services/core/java/com/android/server/am/HostingRecord.java
+++ b/services/core/java/com/android/server/am/HostingRecord.java
@@ -30,6 +30,8 @@
import static com.android.internal.util.FrameworkStatsLog.PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_SERVICE;
import static com.android.internal.util.FrameworkStatsLog.PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_SYSTEM;
import static com.android.internal.util.FrameworkStatsLog.PROCESS_START_TIME__HOSTING_TYPE_ID__HOSTING_TYPE_TOP_ACTIVITY;
+import static com.android.internal.util.FrameworkStatsLog.PROCESS_START_TIME__TRIGGER_TYPE__TRIGGER_PUSH_MESSAGE;
+import static com.android.internal.util.FrameworkStatsLog.PROCESS_START_TIME__TRIGGER_TYPE__TRIGGER_PUSH_MESSAGE_OVER_QUOTA;
import static com.android.internal.util.FrameworkStatsLog.PROCESS_START_TIME__TRIGGER_TYPE__TRIGGER_TYPE_ALARM;
import static com.android.internal.util.FrameworkStatsLog.PROCESS_START_TIME__TRIGGER_TYPE__TRIGGER_TYPE_UNKNOWN;
import static com.android.internal.util.FrameworkStatsLog.PROCESS_START_TIME__TYPE__UNKNOWN;
@@ -93,6 +95,8 @@
public static final String TRIGGER_TYPE_UNKNOWN = "unknown";
public static final String TRIGGER_TYPE_ALARM = "alarm";
+ public static final String TRIGGER_TYPE_PUSH_MESSAGE = "push_message";
+ public static final String TRIGGER_TYPE_PUSH_MESSAGE_OVER_QUOTA = "push_message_over_quota";
@NonNull private final String mHostingType;
private final String mHostingName;
@@ -308,6 +312,10 @@
switch(triggerType) {
case TRIGGER_TYPE_ALARM:
return PROCESS_START_TIME__TRIGGER_TYPE__TRIGGER_TYPE_ALARM;
+ case TRIGGER_TYPE_PUSH_MESSAGE:
+ return PROCESS_START_TIME__TRIGGER_TYPE__TRIGGER_PUSH_MESSAGE;
+ case TRIGGER_TYPE_PUSH_MESSAGE_OVER_QUOTA:
+ return PROCESS_START_TIME__TRIGGER_TYPE__TRIGGER_PUSH_MESSAGE_OVER_QUOTA;
default:
return PROCESS_START_TIME__TRIGGER_TYPE__TRIGGER_TYPE_UNKNOWN;
}
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index ee60565..ccbca76 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -31,7 +31,6 @@
import static android.os.Process.getTotalMemory;
import static android.os.Process.killProcessQuiet;
import static android.os.Process.startWebView;
-import static android.system.OsConstants.*;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_LRU;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_NETWORK;
@@ -2706,50 +2705,6 @@
}
}
- private static boolean freezePackageCgroup(int packageUID, boolean freeze) {
- try {
- Process.freezeCgroupUid(packageUID, freeze);
- } catch (RuntimeException e) {
- final String logtxt = freeze ? "freeze" : "unfreeze";
- Slog.e(TAG, "Unable to " + logtxt + " cgroup uid: " + packageUID + ": " + e);
- return false;
- }
- return true;
- }
-
- private static void freezeBinderAndPackageCgroup(ArrayList<Pair<ProcessRecord, Boolean>> procs,
- int packageUID) {
- // Freeze all binder processes under the target UID (whose cgroup is about to be frozen).
- // Since we're going to kill these, we don't need to unfreze them later.
- // The procs list may not include all processes under the UID cgroup, but unincluded
- // processes (forks) should not be Binder users.
- int N = procs.size();
- for (int i = 0; i < N; i++) {
- final int uid = procs.get(i).first.uid;
- final int pid = procs.get(i).first.getPid();
- int nRetries = 0;
- // We only freeze the cgroup of the target package, so we do not need to freeze the
- // Binder interfaces of dependant processes in other UIDs.
- if (pid > 0 && uid == packageUID) {
- try {
- int rc;
- do {
- rc = CachedAppOptimizer.freezeBinder(pid, true, 10 /* timeout_ms */);
- } while (rc == -EAGAIN && nRetries++ < 1);
- if (rc != 0) Slog.e(TAG, "Unable to freeze binder for " + pid + ": " + rc);
- } catch (RuntimeException e) {
- Slog.e(TAG, "Unable to freeze binder for " + pid + ": " + e);
- }
- }
- }
-
- // We freeze the entire UID (parent) cgroup so that newly-specialized processes also freeze
- // despite being added to a new child cgroup. The cgroups of package dependant processes are
- // not frozen, since it's possible this would freeze processes with no dependency on the
- // package being killed here.
- freezePackageCgroup(packageUID, true);
- }
-
@GuardedBy({"mService", "mProcLock"})
boolean killPackageProcessesLSP(String packageName, int appId,
int userId, int minOomAdj, boolean callerWillRestart, boolean allowRestart,
@@ -2802,7 +2757,7 @@
boolean shouldAllowRestart = false;
// If no package is specified, we call all processes under the
- // given user id.
+ // give user id.
if (packageName == null) {
if (userId != UserHandle.USER_ALL && app.userId != userId) {
continue;
@@ -2845,18 +2800,14 @@
}
}
- final int packageUID = UserHandle.getUid(userId, appId);
- freezeBinderAndPackageCgroup(procs, packageUID);
-
int N = procs.size();
for (int i=0; i<N; i++) {
final Pair<ProcessRecord, Boolean> proc = procs.get(i);
removeProcessLocked(proc.first, callerWillRestart, allowRestart || proc.second,
- reasonCode, subReason, reason, false /* async */);
+ reasonCode, subReason, reason);
}
killAppZygotesLocked(packageName, appId, userId, false /* force */);
mService.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_PROCESS_END);
- freezePackageCgroup(packageUID, false);
return N > 0;
}
@@ -2864,19 +2815,12 @@
boolean removeProcessLocked(ProcessRecord app,
boolean callerWillRestart, boolean allowRestart, int reasonCode, String reason) {
return removeProcessLocked(app, callerWillRestart, allowRestart, reasonCode,
- ApplicationExitInfo.SUBREASON_UNKNOWN, reason, true);
+ ApplicationExitInfo.SUBREASON_UNKNOWN, reason);
}
@GuardedBy("mService")
boolean removeProcessLocked(ProcessRecord app, boolean callerWillRestart,
boolean allowRestart, int reasonCode, int subReason, String reason) {
- return removeProcessLocked(app, callerWillRestart, allowRestart, reasonCode, subReason,
- reason, true);
- }
-
- @GuardedBy("mService")
- boolean removeProcessLocked(ProcessRecord app, boolean callerWillRestart,
- boolean allowRestart, int reasonCode, int subReason, String reason, boolean async) {
final String name = app.processName;
final int uid = app.uid;
if (DEBUG_PROCESSES) Slog.d(TAG_PROCESSES,
@@ -2913,7 +2857,7 @@
needRestart = true;
}
}
- app.killLocked(reason, reasonCode, subReason, true, async);
+ app.killLocked(reason, reasonCode, subReason, true);
mService.handleAppDiedLocked(app, pid, willRestart, allowRestart,
false /* fromBinderDied */);
if (willRestart) {
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index 2f1c5f6..07b6fcd 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -1057,30 +1057,18 @@
@GuardedBy("mService")
void killLocked(String reason, @Reason int reasonCode, boolean noisy) {
- killLocked(reason, reasonCode, ApplicationExitInfo.SUBREASON_UNKNOWN, noisy, true);
+ killLocked(reason, reasonCode, ApplicationExitInfo.SUBREASON_UNKNOWN, noisy);
}
@GuardedBy("mService")
void killLocked(String reason, @Reason int reasonCode, @SubReason int subReason,
boolean noisy) {
- killLocked(reason, reason, reasonCode, subReason, noisy, true);
+ killLocked(reason, reason, reasonCode, subReason, noisy);
}
@GuardedBy("mService")
void killLocked(String reason, String description, @Reason int reasonCode,
@SubReason int subReason, boolean noisy) {
- killLocked(reason, description, reasonCode, subReason, noisy, true);
- }
-
- @GuardedBy("mService")
- void killLocked(String reason, @Reason int reasonCode, @SubReason int subReason,
- boolean noisy, boolean asyncKPG) {
- killLocked(reason, reason, reasonCode, subReason, noisy, asyncKPG);
- }
-
- @GuardedBy("mService")
- void killLocked(String reason, String description, @Reason int reasonCode,
- @SubReason int subReason, boolean noisy, boolean asyncKPG) {
if (!mKilledByAm) {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "kill");
if (reasonCode == ApplicationExitInfo.REASON_ANR
@@ -1097,8 +1085,7 @@
EventLog.writeEvent(EventLogTags.AM_KILL,
userId, mPid, processName, mState.getSetAdj(), reason);
Process.killProcessQuiet(mPid);
- if (asyncKPG) ProcessList.killProcessGroup(uid, mPid);
- else Process.killProcessGroup(uid, mPid);
+ ProcessList.killProcessGroup(uid, mPid);
} else {
mPendingStart = false;
}
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index 1edfabe..a1f3dae 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -71,6 +71,7 @@
import android.content.pm.PackageManager;
import android.content.pm.PackagePartitions;
import android.content.pm.UserInfo;
+import android.content.pm.UserProperties;
import android.os.BatteryStats;
import android.os.Binder;
import android.os.Bundle;
@@ -1367,7 +1368,8 @@
List<UserInfo> profilesToStart = new ArrayList<>(profiles.size());
for (UserInfo user : profiles) {
if ((user.flags & UserInfo.FLAG_INITIALIZED) == UserInfo.FLAG_INITIALIZED
- && user.id != currentUserId && !user.isQuietModeEnabled()) {
+ && user.id != currentUserId
+ && shouldStartWithParent(user)) {
profilesToStart.add(user);
}
}
@@ -1382,6 +1384,13 @@
}
}
+ private boolean shouldStartWithParent(UserInfo user) {
+ final UserProperties properties = mInjector.getUserManagerInternal()
+ .getUserProperties(user.id);
+ return (properties != null && properties.getStartWithParent())
+ && !user.isQuietModeEnabled();
+ }
+
// TODO(b/239982558): might need to infer the display id based on parent user
/**
* Starts a user only if it's a profile, with a more relaxed permission requirement:
@@ -1468,7 +1477,16 @@
checkCallingHasOneOfThosePermissions("startUserOnSecondaryDisplay",
MANAGE_USERS, CREATE_USERS);
- return startUserNoChecks(userId, displayId, /* foreground= */ false, unlockListener);
+ // DEFAULT_DISPLAY is used for "regular" start user operations
+ Preconditions.checkArgument(displayId != Display.DEFAULT_DISPLAY,
+ "Cannot use DEFAULT_DISPLAY");
+
+ try {
+ return startUserNoChecks(userId, displayId, /* foreground= */ false, unlockListener);
+ } catch (RuntimeException e) {
+ Slogf.w(TAG, "startUserOnSecondaryDisplay(%d, %d) failed: %s", userId, displayId, e);
+ return false;
+ }
}
private boolean startUserNoChecks(@UserIdInt int userId, int displayId, boolean foreground,
@@ -1492,13 +1510,15 @@
foreground ? " in foreground" : "");
}
+ // TODO(b/239982558): move logic below to a different class (like DisplayAssignmentManager)
if (displayId != Display.DEFAULT_DISPLAY) {
+ // This is called by startUserOnSecondaryDisplay()
if (!UserManager.isUsersOnSecondaryDisplaysEnabled()) {
- // TODO(b/239824814): add CTS test and/or unit test
+ // TODO(b/239824814): add CTS test and/or unit test for all exceptional cases
throw new UnsupportedOperationException("Not supported by device");
}
- // TODO(b/239982558): add unit test for the exceptional cases below
+ // TODO(b/239982558): call DisplayManagerInternal to check if display is valid instead
Preconditions.checkArgument(displayId > 0, "Invalid display id (%d)", displayId);
Preconditions.checkArgument(userId != UserHandle.USER_SYSTEM, "Cannot start system user"
+ " on secondary display (%d)", displayId);
@@ -1508,7 +1528,6 @@
// TODO(b/239982558): for now we're just updating the user's visibility, but most likely
// we'll need to remove this call and handle that as part of the user state workflow
// instead.
- // TODO(b/239982558): check if display is valid
mInjector.getUserManagerInternal().assignUserToDisplay(userId, displayId);
}
diff --git a/services/core/java/com/android/server/attention/TEST_MAPPING b/services/core/java/com/android/server/attention/TEST_MAPPING
new file mode 100644
index 0000000..35b8165
--- /dev/null
+++ b/services/core/java/com/android/server/attention/TEST_MAPPING
@@ -0,0 +1,24 @@
+{
+ "presubmit": [
+ {
+ "name": "CtsVoiceInteractionTestCases",
+ "options": [
+ {
+ "include-filter": "android.voiceinteraction.cts.AlwaysOnHotwordDetectorTest"
+ },
+ {
+ "include-filter": "android.voiceinteraction.cts.HotwordDetectedResultTest"
+ },
+ {
+ "include-filter": "android.voiceinteraction.cts.HotwordDetectionServiceBasicTest"
+ },
+ {
+ "include-filter": "android.voiceinteraction.cts.HotwordDetectionServiceProximityTest"
+ },
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ }
+ ]
+ }
+ ]
+}
diff --git a/services/core/java/com/android/server/audio/SpatializerHelper.java b/services/core/java/com/android/server/audio/SpatializerHelper.java
index 8356134..aedbe4e 100644
--- a/services/core/java/com/android/server/audio/SpatializerHelper.java
+++ b/services/core/java/com/android/server/audio/SpatializerHelper.java
@@ -571,7 +571,9 @@
// There may be different devices with the same device type (aliasing).
// We always send the full device state info on each change.
private void logDeviceState(SADeviceState deviceState, String event) {
- final String deviceName = AudioSystem.getDeviceName(deviceState.mDeviceType);
+ final int deviceType = AudioDeviceInfo.convertDeviceTypeToInternalDevice(
+ deviceState.mDeviceType);
+ final String deviceName = AudioSystem.getDeviceName(deviceType);
new MediaMetrics.Item(METRICS_DEVICE_PREFIX + deviceName)
.set(MediaMetrics.Property.ADDRESS, deviceState.mDeviceAddress)
.set(MediaMetrics.Property.ENABLED, deviceState.mEnabled ? "true" : "false")
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index 16a060a..931c692 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -752,7 +752,7 @@
return true;
}
- private boolean sendEventToVpnManagerApp(@NonNull String category, int errorClass,
+ private Intent buildVpnManagerEventIntent(@NonNull String category, int errorClass,
int errorCode, @NonNull final String packageName, @Nullable final String sessionKey,
@NonNull final VpnProfileState profileState, @Nullable final Network underlyingNetwork,
@Nullable final NetworkCapabilities nc, @Nullable final LinkProperties lp) {
@@ -771,6 +771,20 @@
intent.putExtra(VpnManager.EXTRA_ERROR_CODE, errorCode);
}
+ return intent;
+ }
+
+ private boolean sendEventToVpnManagerApp(@NonNull String category, int errorClass,
+ int errorCode, @NonNull final String packageName, @Nullable final String sessionKey,
+ @NonNull final VpnProfileState profileState, @Nullable final Network underlyingNetwork,
+ @Nullable final NetworkCapabilities nc, @Nullable final LinkProperties lp) {
+ final Intent intent = buildVpnManagerEventIntent(category, errorClass, errorCode,
+ packageName, sessionKey, profileState, underlyingNetwork, nc, lp);
+ return sendEventToVpnManagerApp(intent, packageName);
+ }
+
+ private boolean sendEventToVpnManagerApp(@NonNull final Intent intent,
+ @NonNull final String packageName) {
// Allow VpnManager app to temporarily run background services to handle this error.
// If an app requires anything beyond this grace period, they MUST either declare
// themselves as a foreground service, or schedule a job/workitem.
@@ -1182,12 +1196,25 @@
mContext.unbindService(mConnection);
cleanupVpnStateLocked();
} else if (mVpnRunner != null) {
- if (!VpnConfig.LEGACY_VPN.equals(mPackage)) {
- notifyVpnManagerVpnStopped(mPackage, mOwnerUID);
+ // Build intent first because the sessionKey will be reset after performing
+ // VpnRunner.exit(). Also, cache mOwnerUID even if ownerUID will not be changed in
+ // VpnRunner.exit() to prevent design being changed in the future.
+ // TODO(b/230548427): Remove SDK check once VPN related stuff are decoupled from
+ // ConnectivityServiceTest.
+ final int ownerUid = mOwnerUID;
+ Intent intent = null;
+ if (SdkLevel.isAtLeastT() && isVpnApp(mPackage)) {
+ intent = buildVpnManagerEventIntent(
+ VpnManager.CATEGORY_EVENT_DEACTIVATED_BY_USER,
+ -1 /* errorClass */, -1 /* errorCode*/, mPackage,
+ getSessionKeyLocked(), makeVpnProfileStateLocked(),
+ null /* underlyingNetwork */, null /* nc */, null /* lp */);
}
-
// cleanupVpnStateLocked() is called from mVpnRunner.exit()
mVpnRunner.exit();
+ if (intent != null && isVpnApp(mPackage)) {
+ notifyVpnManagerVpnStopped(mPackage, ownerUid, intent);
+ }
}
try {
@@ -2886,6 +2913,9 @@
final LinkProperties lp;
synchronized (Vpn.this) {
+ // Ignore stale runner.
+ if (mVpnRunner != this) return;
+
mInterface = interfaceName;
mConfig.mtu = maxMtu;
mConfig.interfaze = mInterface;
@@ -2987,6 +3017,9 @@
try {
synchronized (Vpn.this) {
+ // Ignore stale runner.
+ if (mVpnRunner != this) return;
+
mConfig.underlyingNetworks = new Network[] {network};
mNetworkCapabilities =
new NetworkCapabilities.Builder(mNetworkCapabilities)
@@ -3076,7 +3109,12 @@
// Clear mInterface to prevent Ikev2VpnRunner being cleared when
// interfaceRemoved() is called.
- mInterface = null;
+ synchronized (Vpn.this) {
+ // Ignore stale runner.
+ if (mVpnRunner != this) return;
+
+ mInterface = null;
+ }
// Without MOBIKE, we have no way to seamlessly migrate. Close on old
// (non-default) network, and start the new one.
resetIkeState();
@@ -3261,6 +3299,9 @@
/** Marks the state as FAILED, and disconnects. */
private void markFailedAndDisconnect(Exception exception) {
synchronized (Vpn.this) {
+ // Ignore stale runner.
+ if (mVpnRunner != this) return;
+
updateState(DetailedState.FAILED, exception.getMessage());
}
@@ -3345,6 +3386,9 @@
}
synchronized (Vpn.this) {
+ // Ignore stale runner.
+ if (mVpnRunner != this) return;
+
// TODO(b/230548427): Remove SDK check once VPN related stuff are
// decoupled from ConnectivityServiceTest.
if (SdkLevel.isAtLeastT() && category != null && isVpnApp(mPackage)) {
@@ -3371,6 +3415,9 @@
Log.d(TAG, "Resetting state for token: " + mCurrentToken);
synchronized (Vpn.this) {
+ // Ignore stale runner.
+ if (mVpnRunner != this) return;
+
// Since this method handles non-fatal errors only, set mInterface to null to
// prevent the NetworkManagementEventObserver from killing this VPN based on the
// interface going down (which we expect).
@@ -3993,6 +4040,7 @@
mConfig.proxyInfo = profile.proxy;
mConfig.requiresInternetValidation = profile.requiresInternetValidation;
mConfig.excludeLocalRoutes = profile.excludeLocalRoutes;
+ mConfig.allowBypass = profile.isBypassable;
switch (profile.type) {
case VpnProfile.TYPE_IKEV2_IPSEC_USER_PASS:
@@ -4042,13 +4090,23 @@
// To stop the VPN profile, the caller must be the current prepared package and must be
// running an Ikev2VpnProfile.
if (isCurrentIkev2VpnLocked(packageName)) {
- notifyVpnManagerVpnStopped(packageName, mOwnerUID);
+ // Build intent first because the sessionKey will be reset after performing
+ // VpnRunner.exit(). Also, cache mOwnerUID even if ownerUID will not be changed in
+ // VpnRunner.exit() to prevent design being changed in the future.
+ final int ownerUid = mOwnerUID;
+ final Intent intent = buildVpnManagerEventIntent(
+ VpnManager.CATEGORY_EVENT_DEACTIVATED_BY_USER,
+ -1 /* errorClass */, -1 /* errorCode*/, packageName,
+ getSessionKeyLocked(), makeVpnProfileStateLocked(),
+ null /* underlyingNetwork */, null /* nc */, null /* lp */);
mVpnRunner.exit();
+ notifyVpnManagerVpnStopped(packageName, ownerUid, intent);
}
}
- private synchronized void notifyVpnManagerVpnStopped(String packageName, int ownerUID) {
+ private synchronized void notifyVpnManagerVpnStopped(String packageName, int ownerUID,
+ Intent intent) {
mAppOpsManager.finishOp(
AppOpsManager.OPSTR_ESTABLISH_VPN_MANAGER, ownerUID, packageName, null);
// The underlying network, NetworkCapabilities and LinkProperties are not
@@ -4057,10 +4115,7 @@
// TODO(b/230548427): Remove SDK check once VPN related stuff are decoupled from
// ConnectivityServiceTest.
if (SdkLevel.isAtLeastT()) {
- sendEventToVpnManagerApp(VpnManager.CATEGORY_EVENT_DEACTIVATED_BY_USER,
- -1 /* errorClass */, -1 /* errorCode*/, packageName,
- getSessionKeyLocked(), makeVpnProfileStateLocked(),
- null /* underlyingNetwork */, null /* nc */, null /* lp */);
+ sendEventToVpnManagerApp(intent, packageName);
}
}
diff --git a/services/core/java/com/android/server/location/LocationPermissions.java b/services/core/java/com/android/server/location/LocationPermissions.java
index ca2ff60..f7da0d8 100644
--- a/services/core/java/com/android/server/location/LocationPermissions.java
+++ b/services/core/java/com/android/server/location/LocationPermissions.java
@@ -26,8 +26,10 @@
import android.content.Context;
import android.os.Binder;
+import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
/** Utility class for dealing with location permissions. */
public final class LocationPermissions {
@@ -49,6 +51,7 @@
*/
public static final int PERMISSION_FINE = 2;
+ @Target(ElementType.TYPE_USE)
@IntDef({PERMISSION_NONE, PERMISSION_COARSE, PERMISSION_FINE})
@Retention(RetentionPolicy.SOURCE)
public @interface PermissionLevel {}
diff --git a/services/core/java/com/android/server/location/geofence/GeofenceKey.java b/services/core/java/com/android/server/location/geofence/GeofenceKey.java
deleted file mode 100644
index bbfa68f..0000000
--- a/services/core/java/com/android/server/location/geofence/GeofenceKey.java
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Copyright (C) 2020 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.location.geofence;
-
-import android.app.PendingIntent;
-import android.location.Geofence;
-
-import com.android.server.location.listeners.PendingIntentListenerRegistration;
-
-import java.util.Objects;
-
-// geofencing unfortunately allows multiple geofences under the same pending intent, even though
-// this makes no real sense. therefore we manufacture an artificial key to use (pendingintent +
-// geofence) instead of (pendingintent).
-final class GeofenceKey implements PendingIntentListenerRegistration.PendingIntentKey {
-
- private final PendingIntent mPendingIntent;
- private final Geofence mGeofence;
-
- GeofenceKey(PendingIntent pendingIntent, Geofence geofence) {
- mPendingIntent = Objects.requireNonNull(pendingIntent);
- mGeofence = Objects.requireNonNull(geofence);
- }
-
- @Override
- public PendingIntent getPendingIntent() {
- return mPendingIntent;
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) {
- return true;
- }
- if (!(o instanceof GeofenceKey)) {
- return false;
- }
- GeofenceKey that = (GeofenceKey) o;
- return mPendingIntent.equals(that.mPendingIntent) && mGeofence.equals(that.mGeofence);
- }
-
- @Override
- public int hashCode() {
- return mPendingIntent.hashCode();
- }
-}
diff --git a/services/core/java/com/android/server/location/geofence/GeofenceManager.java b/services/core/java/com/android/server/location/geofence/GeofenceManager.java
index b6342a4..0f5e3d4 100644
--- a/services/core/java/com/android/server/location/geofence/GeofenceManager.java
+++ b/services/core/java/com/android/server/location/geofence/GeofenceManager.java
@@ -59,8 +59,8 @@
* Manages all geofences.
*/
public class GeofenceManager extends
- ListenerMultiplexer<GeofenceKey, PendingIntent, GeofenceManager.GeofenceRegistration,
- LocationRequest> implements
+ ListenerMultiplexer<GeofenceManager.GeofenceKey, PendingIntent,
+ GeofenceManager.GeofenceRegistration, LocationRequest> implements
LocationListener {
private static final String TAG = "GeofenceManager";
@@ -73,13 +73,49 @@
private static final long MAX_LOCATION_AGE_MS = 5 * 60 * 1000L; // five minutes
private static final long MAX_LOCATION_INTERVAL_MS = 2 * 60 * 60 * 1000; // two hours
- protected final class GeofenceRegistration extends
- PendingIntentListenerRegistration<Geofence, PendingIntent> {
+ // geofencing unfortunately allows multiple geofences under the same pending intent, even though
+ // this makes no real sense. therefore we manufacture an artificial key to use (pendingintent +
+ // geofence) instead of (pendingintent).
+ static class GeofenceKey {
+
+ private final PendingIntent mPendingIntent;
+ private final Geofence mGeofence;
+
+ GeofenceKey(PendingIntent pendingIntent, Geofence geofence) {
+ mPendingIntent = Objects.requireNonNull(pendingIntent);
+ mGeofence = Objects.requireNonNull(geofence);
+ }
+
+ public PendingIntent getPendingIntent() {
+ return mPendingIntent;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o instanceof GeofenceKey) {
+ GeofenceKey that = (GeofenceKey) o;
+ return mPendingIntent.equals(that.mPendingIntent) && mGeofence.equals(
+ that.mGeofence);
+ }
+
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return mPendingIntent.hashCode();
+ }
+ }
+
+ protected class GeofenceRegistration extends
+ PendingIntentListenerRegistration<GeofenceKey, PendingIntent> {
private static final int STATE_UNKNOWN = 0;
private static final int STATE_INSIDE = 1;
private static final int STATE_OUTSIDE = 2;
+ private final Geofence mGeofence;
+ private final CallerIdentity mIdentity;
private final Location mCenter;
private final PowerManager.WakeLock mWakeLock;
@@ -89,13 +125,15 @@
// spam us, and because checking the values may be more expensive
private boolean mPermitted;
- private @Nullable Location mCachedLocation;
+ @Nullable private Location mCachedLocation;
private float mCachedLocationDistanceM;
- protected GeofenceRegistration(Geofence geofence, CallerIdentity identity,
+ GeofenceRegistration(Geofence geofence, CallerIdentity identity,
PendingIntent pendingIntent) {
- super(geofence, identity, pendingIntent);
+ super(pendingIntent);
+ mGeofence = geofence;
+ mIdentity = identity;
mCenter = new Location("");
mCenter.setLatitude(geofence.getLatitude());
mCenter.setLongitude(geofence.getLongitude());
@@ -107,16 +145,36 @@
mWakeLock.setWorkSource(identity.addToWorkSource(null));
}
+ public Geofence getGeofence() {
+ return mGeofence;
+ }
+
+ public CallerIdentity getIdentity() {
+ return mIdentity;
+ }
+
+ @Override
+ public String getTag() {
+ return TAG;
+ }
+
+ @Override
+ protected PendingIntent getPendingIntentFromKey(GeofenceKey geofenceKey) {
+ return geofenceKey.getPendingIntent();
+ }
+
@Override
protected GeofenceManager getOwner() {
return GeofenceManager.this;
}
@Override
- protected void onPendingIntentListenerRegister() {
+ protected void onRegister() {
+ super.onRegister();
+
mGeofenceState = STATE_UNKNOWN;
mPermitted = mLocationPermissionsHelper.hasLocationPermissions(PERMISSION_FINE,
- getIdentity());
+ mIdentity);
}
@Override
@@ -132,7 +190,7 @@
}
boolean onLocationPermissionsChanged(@Nullable String packageName) {
- if (packageName == null || getIdentity().getPackageName().equals(packageName)) {
+ if (packageName == null || mIdentity.getPackageName().equals(packageName)) {
return onLocationPermissionsChanged();
}
@@ -140,7 +198,7 @@
}
boolean onLocationPermissionsChanged(int uid) {
- if (getIdentity().getUid() == uid) {
+ if (mIdentity.getUid() == uid) {
return onLocationPermissionsChanged();
}
@@ -149,7 +207,7 @@
private boolean onLocationPermissionsChanged() {
boolean permitted = mLocationPermissionsHelper.hasLocationPermissions(PERMISSION_FINE,
- getIdentity());
+ mIdentity);
if (permitted != mPermitted) {
mPermitted = permitted;
return true;
@@ -164,12 +222,12 @@
mCachedLocationDistanceM = mCenter.distanceTo(mCachedLocation);
}
- return Math.abs(getRequest().getRadius() - mCachedLocationDistanceM);
+ return Math.abs(mGeofence.getRadius() - mCachedLocationDistanceM);
}
ListenerOperation<PendingIntent> onLocationChanged(Location location) {
// remove expired fences
- if (getRequest().isExpired()) {
+ if (mGeofence.isExpired()) {
remove();
return null;
}
@@ -178,7 +236,7 @@
mCachedLocationDistanceM = mCenter.distanceTo(mCachedLocation);
int oldState = mGeofenceState;
- float radius = Math.max(getRequest().getRadius(), location.getAccuracy());
+ float radius = Math.max(mGeofence.getRadius(), location.getAccuracy());
if (mCachedLocationDistanceM <= radius) {
mGeofenceState = STATE_INSIDE;
if (oldState != STATE_INSIDE) {
@@ -206,14 +264,14 @@
null, null, PendingIntentUtils.createDontSendToRestrictedAppsBundle(null));
} catch (PendingIntent.CanceledException e) {
mWakeLock.release();
- removeRegistration(new GeofenceKey(pendingIntent, getRequest()), this);
+ removeRegistration(new GeofenceKey(pendingIntent, mGeofence), this);
}
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
- builder.append(getIdentity());
+ builder.append(mIdentity);
ArraySet<String> flags = new ArraySet<>(1);
if (!mPermitted) {
@@ -223,7 +281,7 @@
builder.append(" ").append(flags);
}
- builder.append(" ").append(getRequest());
+ builder.append(" ").append(mGeofence);
return builder.toString();
}
}
@@ -258,10 +316,10 @@
protected final LocationUsageLogger mLocationUsageLogger;
@GuardedBy("mLock")
- private @Nullable LocationManager mLocationManager;
+ @Nullable private LocationManager mLocationManager;
@GuardedBy("mLock")
- private @Nullable Location mLastLocation;
+ @Nullable private Location mLastLocation;
public GeofenceManager(Context context, Injector injector) {
mContext = context.createAttributionContext(ATTRIBUTION_TAG);
@@ -271,11 +329,6 @@
mLocationUsageLogger = injector.getLocationUsageLogger();
}
- @Override
- public String getTag() {
- return TAG;
- }
-
private LocationManager getLocationManager() {
synchronized (mLock) {
if (mLocationManager == null) {
@@ -375,7 +428,7 @@
/* LocationRequest= */ null,
/* hasListener= */ false,
true,
- registration.getRequest(), true);
+ registration.getGeofence(), true);
}
@Override
@@ -389,7 +442,7 @@
/* LocationRequest= */ null,
/* hasListener= */ false,
true,
- registration.getRequest(), true);
+ registration.getGeofence(), true);
}
@Override
@@ -417,7 +470,7 @@
WorkSource workSource = null;
double minFenceDistanceM = Double.MAX_VALUE;
for (GeofenceRegistration registration : registrations) {
- if (registration.getRequest().isExpired(realtimeMs)) {
+ if (registration.getGeofence().isExpired(realtimeMs)) {
continue;
}
diff --git a/services/core/java/com/android/server/location/gnss/GnssAntennaInfoProvider.java b/services/core/java/com/android/server/location/gnss/GnssAntennaInfoProvider.java
index e375007..62ab22a 100644
--- a/services/core/java/com/android/server/location/gnss/GnssAntennaInfoProvider.java
+++ b/services/core/java/com/android/server/location/gnss/GnssAntennaInfoProvider.java
@@ -16,6 +16,7 @@
package com.android.server.location.gnss;
+import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR;
import static com.android.server.location.gnss.GnssManagerService.TAG;
import android.annotation.Nullable;
@@ -25,6 +26,7 @@
import android.os.Binder;
import android.os.IBinder;
+import com.android.server.FgThread;
import com.android.server.location.gnss.hal.GnssNative;
import com.android.server.location.listeners.BinderListenerRegistration;
import com.android.server.location.listeners.ListenerMultiplexer;
@@ -45,17 +47,35 @@
* Registration object for GNSS listeners.
*/
protected class AntennaInfoListenerRegistration extends
- BinderListenerRegistration<Void, IGnssAntennaInfoListener> {
+ BinderListenerRegistration<IBinder, IGnssAntennaInfoListener> {
- protected AntennaInfoListenerRegistration(CallerIdentity callerIdentity,
+ private final CallerIdentity mIdentity;
+
+ protected AntennaInfoListenerRegistration(CallerIdentity identity,
IGnssAntennaInfoListener listener) {
- super(null, callerIdentity, listener);
+ super(identity.isMyProcess() ? FgThread.getExecutor() : DIRECT_EXECUTOR, listener);
+ mIdentity = identity;
+ }
+
+ @Override
+ protected String getTag() {
+ return TAG;
}
@Override
protected GnssAntennaInfoProvider getOwner() {
return GnssAntennaInfoProvider.this;
}
+
+ @Override
+ protected IBinder getBinderFromKey(IBinder key) {
+ return key;
+ }
+
+ @Override
+ public String toString() {
+ return mIdentity.toString();
+ }
}
private final GnssNative mGnssNative;
@@ -72,11 +92,6 @@
return mAntennaInfos;
}
- @Override
- public String getTag() {
- return TAG;
- }
-
public boolean isSupported() {
return mGnssNative.isAntennaInfoSupported();
}
diff --git a/services/core/java/com/android/server/location/gnss/GnssListenerMultiplexer.java b/services/core/java/com/android/server/location/gnss/GnssListenerMultiplexer.java
index a540476..82bcca2 100644
--- a/services/core/java/com/android/server/location/gnss/GnssListenerMultiplexer.java
+++ b/services/core/java/com/android/server/location/gnss/GnssListenerMultiplexer.java
@@ -18,6 +18,7 @@
import static android.location.LocationManager.GPS_PROVIDER;
+import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR;
import static com.android.server.location.LocationPermissions.PERMISSION_FINE;
import static com.android.server.location.gnss.GnssManagerService.TAG;
@@ -33,6 +34,7 @@
import android.util.ArraySet;
import com.android.internal.util.Preconditions;
+import com.android.server.FgThread;
import com.android.server.LocalServices;
import com.android.server.location.injector.AppForegroundHelper;
import com.android.server.location.injector.Injector;
@@ -67,16 +69,34 @@
* Registration object for GNSS listeners.
*/
protected class GnssListenerRegistration extends
- BinderListenerRegistration<TRequest, TListener> {
+ BinderListenerRegistration<IBinder, TListener> {
+
+ private final TRequest mRequest;
+ private final CallerIdentity mIdentity;
// we store these values because we don't trust the listeners not to give us dupes, not to
// spam us, and because checking the values may be more expensive
private boolean mForeground;
private boolean mPermitted;
- protected GnssListenerRegistration(@Nullable TRequest request,
- CallerIdentity callerIdentity, TListener listener) {
- super(request, callerIdentity, listener);
+ protected GnssListenerRegistration(TRequest request, CallerIdentity identity,
+ TListener listener) {
+ super(identity.isMyProcess() ? FgThread.getExecutor() : DIRECT_EXECUTOR, listener);
+ mRequest = request;
+ mIdentity = identity;
+ }
+
+ public final TRequest getRequest() {
+ return mRequest;
+ }
+
+ public final CallerIdentity getIdentity() {
+ return mIdentity;
+ }
+
+ @Override
+ public String getTag() {
+ return TAG;
}
@Override
@@ -84,6 +104,11 @@
return GnssListenerMultiplexer.this;
}
+ @Override
+ protected IBinder getBinderFromKey(IBinder key) {
+ return key;
+ }
+
/**
* Returns true if this registration is currently in the foreground.
*/
@@ -96,31 +121,16 @@
}
@Override
- protected final void onBinderListenerRegister() {
+ protected void onRegister() {
+ super.onRegister();
+
mPermitted = mLocationPermissionsHelper.hasLocationPermissions(PERMISSION_FINE,
- getIdentity());
- mForeground = mAppForegroundHelper.isAppForeground(getIdentity().getUid());
-
- onGnssListenerRegister();
+ mIdentity);
+ mForeground = mAppForegroundHelper.isAppForeground(mIdentity.getUid());
}
- @Override
- protected final void onBinderListenerUnregister() {
- onGnssListenerUnregister();
- }
-
- /**
- * May be overridden in place of {@link #onBinderListenerRegister()}.
- */
- protected void onGnssListenerRegister() {}
-
- /**
- * May be overridden in place of {@link #onBinderListenerUnregister()}.
- */
- protected void onGnssListenerUnregister() {}
-
boolean onLocationPermissionsChanged(@Nullable String packageName) {
- if (packageName == null || getIdentity().getPackageName().equals(packageName)) {
+ if (packageName == null || mIdentity.getPackageName().equals(packageName)) {
return onLocationPermissionsChanged();
}
@@ -128,7 +138,7 @@
}
boolean onLocationPermissionsChanged(int uid) {
- if (getIdentity().getUid() == uid) {
+ if (mIdentity.getUid() == uid) {
return onLocationPermissionsChanged();
}
@@ -137,7 +147,7 @@
private boolean onLocationPermissionsChanged() {
boolean permitted = mLocationPermissionsHelper.hasLocationPermissions(PERMISSION_FINE,
- getIdentity());
+ mIdentity);
if (permitted != mPermitted) {
mPermitted = permitted;
return true;
@@ -147,7 +157,7 @@
}
boolean onForegroundChanged(int uid, boolean foreground) {
- if (getIdentity().getUid() == uid && foreground != mForeground) {
+ if (mIdentity.getUid() == uid && foreground != mForeground) {
mForeground = foreground;
return true;
}
@@ -158,7 +168,7 @@
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
- builder.append(getIdentity());
+ builder.append(mIdentity);
ArraySet<String> flags = new ArraySet<>(2);
if (!mForeground) {
@@ -171,8 +181,8 @@
builder.append(" ").append(flags);
}
- if (getRequest() != null) {
- builder.append(" ").append(getRequest());
+ if (mRequest != null) {
+ builder.append(" ").append(mRequest);
}
return builder.toString();
}
@@ -218,11 +228,6 @@
LocalServices.getService(LocationManagerInternal.class));
}
- @Override
- public String getTag() {
- return TAG;
- }
-
/**
* May be overridden by subclasses to return whether the service is supported or not. This value
* should never change for the lifetime of the multiplexer. If the service is unsupported, all
diff --git a/services/core/java/com/android/server/location/gnss/GnssMeasurementsProvider.java b/services/core/java/com/android/server/location/gnss/GnssMeasurementsProvider.java
index e4e9d01..9f2a9cf 100644
--- a/services/core/java/com/android/server/location/gnss/GnssMeasurementsProvider.java
+++ b/services/core/java/com/android/server/location/gnss/GnssMeasurementsProvider.java
@@ -40,10 +40,7 @@
import java.util.Collection;
/**
- * An base implementation for GNSS measurements provider. It abstracts out the responsibility of
- * handling listeners, while still allowing technology specific implementations to be built.
- *
- * @hide
+ * GNSS measurements HAL module and listener multiplexer.
*/
public final class GnssMeasurementsProvider extends
GnssListenerMultiplexer<GnssMeasurementRequest, IGnssMeasurementsListener,
@@ -61,7 +58,9 @@
}
@Override
- protected void onGnssListenerRegister() {
+ protected void onRegister() {
+ super.onRegister();
+
executeOperation(listener -> listener.onStatusChanged(
GnssMeasurementsEvent.Callback.STATUS_READY));
}
diff --git a/services/core/java/com/android/server/location/gnss/GnssNavigationMessageProvider.java b/services/core/java/com/android/server/location/gnss/GnssNavigationMessageProvider.java
index e9fce05..63134bb 100644
--- a/services/core/java/com/android/server/location/gnss/GnssNavigationMessageProvider.java
+++ b/services/core/java/com/android/server/location/gnss/GnssNavigationMessageProvider.java
@@ -32,11 +32,7 @@
import java.util.Collection;
/**
- * An base implementation for GPS navigation messages provider.
- * It abstracts out the responsibility of handling listeners, while still allowing technology
- * specific implementations to be built.
- *
- * @hide
+ * GNSS navigation message HAL module and listener multiplexer.
*/
public class GnssNavigationMessageProvider extends
GnssListenerMultiplexer<Void, IGnssNavigationMessageListener, Void> implements
@@ -51,7 +47,9 @@
}
@Override
- protected void onGnssListenerRegister() {
+ protected void onRegister() {
+ super.onRegister();
+
executeOperation(listener -> listener.onStatusChanged(
GnssNavigationMessage.Callback.STATUS_READY));
}
diff --git a/services/core/java/com/android/server/location/gnss/GnssNmeaProvider.java b/services/core/java/com/android/server/location/gnss/GnssNmeaProvider.java
index bfef978..d4e38b6a 100644
--- a/services/core/java/com/android/server/location/gnss/GnssNmeaProvider.java
+++ b/services/core/java/com/android/server/location/gnss/GnssNmeaProvider.java
@@ -34,7 +34,7 @@
import java.util.function.Function;
/**
- * Implementation of a handler for {@link IGnssNmeaListener}.
+ * GNSS NMEA HAL module and listener multiplexer.
*/
class GnssNmeaProvider extends GnssListenerMultiplexer<Void, IGnssNmeaListener, Void> implements
GnssNative.BaseCallbacks, GnssNative.NmeaCallbacks {
@@ -97,7 +97,7 @@
ListenerExecutor.ListenerOperation<IGnssNmeaListener>>() {
// only read in the nmea string if we need to
- private @Nullable String mNmea;
+ @Nullable private String mNmea;
@Override
public ListenerExecutor.ListenerOperation<IGnssNmeaListener> apply(
diff --git a/services/core/java/com/android/server/location/gnss/GnssStatusProvider.java b/services/core/java/com/android/server/location/gnss/GnssStatusProvider.java
index 0ce36d6..41fa7a1 100644
--- a/services/core/java/com/android/server/location/gnss/GnssStatusProvider.java
+++ b/services/core/java/com/android/server/location/gnss/GnssStatusProvider.java
@@ -35,7 +35,7 @@
import java.util.Collection;
/**
- * Implementation of a handler for {@link IGnssStatusListener}.
+ * GNSS status HAL module and listener multiplexer.
*/
public class GnssStatusProvider extends
GnssListenerMultiplexer<Void, IGnssStatusListener, Void> implements
diff --git a/services/core/java/com/android/server/location/listeners/BinderListenerRegistration.java b/services/core/java/com/android/server/location/listeners/BinderListenerRegistration.java
index 709e236..5555aeb 100644
--- a/services/core/java/com/android/server/location/listeners/BinderListenerRegistration.java
+++ b/services/core/java/com/android/server/location/listeners/BinderListenerRegistration.java
@@ -16,71 +16,59 @@
package com.android.server.location.listeners;
-import android.annotation.Nullable;
-import android.location.util.identity.CallerIdentity;
-import android.os.Binder;
import android.os.IBinder;
+import android.os.IBinder.DeathRecipient;
import android.os.RemoteException;
import android.util.Log;
+import java.util.NoSuchElementException;
+import java.util.concurrent.Executor;
+
/**
* A registration that works with IBinder keys, and registers a DeathListener to automatically
- * remove the registration if the binder dies. The key for this registration must either be an
- * {@link IBinder} or a {@link BinderKey}.
+ * remove the registration if the binder dies.
*
- * @param <TRequest> request type
+ * @param <TKey> key type
* @param <TListener> listener type
*/
-public abstract class BinderListenerRegistration<TRequest, TListener> extends
- RemoteListenerRegistration<TRequest, TListener> implements Binder.DeathRecipient {
+public abstract class BinderListenerRegistration<TKey, TListener> extends
+ RemovableListenerRegistration<TKey, TListener> implements DeathRecipient {
- /**
- * Interface to allow binder retrieval when keys are not themselves IBinders.
- */
- public interface BinderKey {
- /**
- * Returns the binder associated with this key.
- */
- IBinder getBinder();
+ protected BinderListenerRegistration(Executor executor, TListener listener) {
+ super(executor, listener);
}
- protected BinderListenerRegistration(@Nullable TRequest request, CallerIdentity callerIdentity,
- TListener listener) {
- super(request, callerIdentity, listener);
- }
+ protected abstract IBinder getBinderFromKey(TKey key);
@Override
- protected final void onRemovableListenerRegister() {
- IBinder binder = getBinderFromKey(getKey());
+ protected void onRegister() {
+ super.onRegister();
+
try {
- binder.linkToDeath(this, 0);
+ getBinderFromKey(getKey()).linkToDeath(this, 0);
} catch (RemoteException e) {
remove();
}
-
- onBinderListenerRegister();
}
@Override
- protected final void onRemovableListenerUnregister() {
- onBinderListenerUnregister();
- getBinderFromKey(getKey()).unlinkToDeath(this, 0);
+ protected void onUnregister() {
+ try {
+ getBinderFromKey(getKey()).unlinkToDeath(this, 0);
+ } catch (NoSuchElementException e) {
+ // the only way this exception can occur should be if another exception has been thrown
+ // prior to registration completing, and that exception is currently unwinding the call
+ // stack and causing this cleanup. since that exception should crash us anyways, drop
+ // this exception so we're not hiding the original exception.
+ Log.w(getTag(), "failed to unregister binder death listener", e);
+ }
+
+ super.onUnregister();
}
- /**
- * May be overridden in place of {@link #onRemovableListenerRegister()}.
- */
- protected void onBinderListenerRegister() {}
-
- /**
- * May be overridden in place of {@link #onRemovableListenerUnregister()}.
- */
- protected void onBinderListenerUnregister() {}
-
- @Override
public void onOperationFailure(ListenerOperation<TListener> operation, Exception e) {
if (e instanceof RemoteException) {
- Log.w(getOwner().getTag(), "registration " + this + " removed", e);
+ Log.w(getTag(), "registration " + this + " removed", e);
remove();
} else {
super.onOperationFailure(operation, e);
@@ -90,9 +78,10 @@
@Override
public void binderDied() {
try {
- if (Log.isLoggable(getOwner().getTag(), Log.DEBUG)) {
- Log.d(getOwner().getTag(), "binder registration " + getIdentity() + " died");
+ if (Log.isLoggable(getTag(), Log.DEBUG)) {
+ Log.d(getTag(), "binder registration " + this + " died");
}
+
remove();
} catch (RuntimeException e) {
// the caller may swallow runtime exceptions, so we rethrow as assertion errors to
@@ -100,14 +89,4 @@
throw new AssertionError(e);
}
}
-
- private static IBinder getBinderFromKey(Object key) {
- if (key instanceof IBinder) {
- return (IBinder) key;
- } else if (key instanceof BinderKey) {
- return ((BinderKey) key).getBinder();
- } else {
- throw new IllegalArgumentException("key must be IBinder or BinderKey");
- }
- }
}
diff --git a/services/core/java/com/android/server/location/listeners/ListenerMultiplexer.java b/services/core/java/com/android/server/location/listeners/ListenerMultiplexer.java
index 33b08d4..67ae265 100644
--- a/services/core/java/com/android/server/location/listeners/ListenerMultiplexer.java
+++ b/services/core/java/com/android/server/location/listeners/ListenerMultiplexer.java
@@ -18,7 +18,6 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.os.Build;
import android.util.ArrayMap;
import android.util.ArraySet;
@@ -37,40 +36,48 @@
import java.util.function.Predicate;
/**
- * A base class to multiplex client listener registrations within system server. Every listener is
+ * A base class to multiplex some event source to multiple listener registrations. Every listener is
* represented by a registration object which stores all required state for a listener. Keys are
* used to uniquely identify every registration. Listener operations may be executed on
* registrations in order to invoke the represented listener.
*
- * Registrations are divided into two categories, active registrations and inactive registrations,
- * as defined by {@link #isActive(ListenerRegistration)}. If a registration's active state changes,
- * {@link #updateRegistrations(Predicate)} must be invoked and return true for any registration
- * whose active state may have changed. Listeners will only be invoked for active registrations.
+ * <p>Registrations are divided into two categories, active registrations and inactive
+ * registrations, as defined by {@link #isActive(ListenerRegistration)}. The set of active
+ * registrations is combined into a single merged registration, which is submitted to the backing
+ * event source when necessary in order to register with the event source. The merged registration
+ * is updated whenever the set of active registration changes. Listeners will only be invoked for
+ * active registrations.
*
- * The set of active registrations is combined into a single merged registration, which is submitted
- * to the backing service when necessary in order to register the service. The merged registration
- * is updated whenever the set of active registration changes.
+ * <p>In order to inform the multiplexer of state changes, if a registration's active state changes,
+ * or if the merged registration changes, {@link #updateRegistrations(Predicate)} or {@link
+ * #updateRegistration(Object, Predicate)} must be invoked and return true for any registration
+ * whose state may have changed in such a way that the active state or merged registration state has
+ * changed. It is acceptable to return true from a predicate even if nothing has changed, though
+ * this may result in extra pointless work.
*
- * Callbacks invoked for various changes will always be ordered according to this lifecycle list:
+ * <p>Callbacks invoked for various changes will always be ordered according to this lifecycle list:
*
* <ul>
- * <li>{@link #onRegister()}</li>
- * <li>{@link ListenerRegistration#onRegister(Object)}</li>
- * <li>{@link #onRegistrationAdded(Object, ListenerRegistration)}</li>
- * <li>{@link #onRegistrationReplaced(Object, ListenerRegistration, ListenerRegistration)} (only
- * invoked if this registration is replacing a prior registration)</li>
- * <li>{@link #onActive()}</li>
- * <li>{@link ListenerRegistration#onActive()}</li>
- * <li>{@link ListenerRegistration#onInactive()}</li>
- * <li>{@link #onInactive()}</li>
- * <li>{@link #onRegistrationRemoved(Object, ListenerRegistration)}</li>
- * <li>{@link ListenerRegistration#onUnregister()}</li>
- * <li>{@link #onUnregister()}</li>
+ * <li>{@link #onRegister()}
+ * <li>{@link ListenerRegistration#onRegister(Object)}
+ * <li>{@link #onRegistrationAdded(Object, ListenerRegistration)}
+ * <li>{@link #onActive()}
+ * <li>{@link ListenerRegistration#onActive()}
+ * <li>{@link ListenerRegistration#onInactive()}
+ * <li>{@link #onInactive()}
+ * <li>{@link #onRegistrationRemoved(Object, ListenerRegistration)}
+ * <li>{@link ListenerRegistration#onUnregister()}
+ * <li>{@link #onUnregister()}
* </ul>
*
- * Adding registrations is not allowed to be called re-entrantly (ie, while in the middle of some
- * other operation or callback. Removal is allowed re-entrantly, however only via
- * {@link #removeRegistration(Object, ListenerRegistration)}, not via any other removal method. This
+ * <p>If one registration replaces another, then {@link #onRegistrationReplaced(Object,
+ * ListenerRegistration, Object, ListenerRegistration)} is invoked instead of {@link
+ * #onRegistrationRemoved(Object, ListenerRegistration)} and {@link #onRegistrationAdded(Object,
+ * ListenerRegistration)}.
+ *
+ * <p>Adding registrations is not allowed to be called re-entrantly (ie, while in the middle of some
+ * other operation or callback). Removal is allowed re-entrantly, however only via {@link
+ * #removeRegistration(Object, ListenerRegistration)}, not via any other removal method. This
* ensures re-entrant removal does not accidentally remove the incorrect registration.
*
* @param <TKey> key type
@@ -81,30 +88,31 @@
public abstract class ListenerMultiplexer<TKey, TListener,
TRegistration extends ListenerRegistration<TListener>, TMergedRegistration> {
- @GuardedBy("mRegistrations")
+ /**
+ * The lock object used by the multiplexer. Acquiring this lock allows for multiple operations
+ * on the multiplexer to be completed atomically. Otherwise, it is not required to hold this
+ * lock. This lock is held while invoking all lifecycle callbacks on both the multiplexer and
+ * any registrations.
+ */
+ protected final Object mMultiplexerLock = new Object();
+
+ @GuardedBy("mMultiplexerLock")
private final ArrayMap<TKey, TRegistration> mRegistrations = new ArrayMap<>();
- @GuardedBy("mRegistrations")
private final UpdateServiceBuffer mUpdateServiceBuffer = new UpdateServiceBuffer();
- @GuardedBy("mRegistrations")
private final ReentrancyGuard mReentrancyGuard = new ReentrancyGuard();
- @GuardedBy("mRegistrations")
+ @GuardedBy("mMultiplexerLock")
private int mActiveRegistrationsCount = 0;
- @GuardedBy("mRegistrations")
+ @GuardedBy("mMultiplexerLock")
private boolean mServiceRegistered = false;
- @GuardedBy("mRegistrations")
+ @GuardedBy("mMultiplexerLock")
@Nullable private TMergedRegistration mMerged;
/**
- * Should be implemented to return a unique identifying tag that may be used for logging, etc...
- */
- public abstract @NonNull String getTag();
-
- /**
* Should be implemented to register with the backing service with the given merged
* registration, and should return true if a matching call to {@link #unregisterWithService()}
* is required to unregister (ie, if registration succeeds). The set of registrations passed in
@@ -120,6 +128,7 @@
* @see #mergeRegistrations(Collection)
* @see #reregisterWithService(Object, Object, Collection)
*/
+ @GuardedBy("mMultiplexerLock")
protected abstract boolean registerWithService(TMergedRegistration merged,
@NonNull Collection<TRegistration> registrations);
@@ -130,6 +139,7 @@
*
* @see #registerWithService(Object, Collection)
*/
+ @GuardedBy("mMultiplexerLock")
protected boolean reregisterWithService(TMergedRegistration oldMerged,
TMergedRegistration newMerged, @NonNull Collection<TRegistration> registrations) {
return registerWithService(newMerged, registrations);
@@ -138,6 +148,7 @@
/**
* Should be implemented to unregister from the backing service.
*/
+ @GuardedBy("mMultiplexerLock")
protected abstract void unregisterWithService();
/**
@@ -147,6 +158,7 @@
* {@link #updateRegistrations(Predicate)} must be invoked with a function that returns true for
* any registrations that may have changed their active state.
*/
+ @GuardedBy("mMultiplexerLock")
protected abstract boolean isActive(@NonNull TRegistration registration);
/**
@@ -157,7 +169,8 @@
* {@link #reregisterWithService(Object, Object, Collection)} will be invoked with the new
* merged registration so that the backing service can be updated.
*/
- protected abstract @Nullable TMergedRegistration mergeRegistrations(
+ @GuardedBy("mMultiplexerLock")
+ protected abstract TMergedRegistration mergeRegistrations(
@NonNull Collection<TRegistration> registrations);
/**
@@ -166,6 +179,7 @@
* present while there are any registrations. Invoked while holding the multiplexer's internal
* lock.
*/
+ @GuardedBy("mMultiplexerLock")
protected void onRegister() {}
/**
@@ -174,28 +188,38 @@
* present while there are any registrations. Invoked while holding the multiplexer's internal
* lock.
*/
+ @GuardedBy("mMultiplexerLock")
protected void onUnregister() {}
/**
* Invoked when a registration is added. Invoked while holding the multiplexer's internal lock.
*/
+ @GuardedBy("mMultiplexerLock")
protected void onRegistrationAdded(@NonNull TKey key, @NonNull TRegistration registration) {}
/**
- * Invoked instead of {@link #onRegistrationAdded(Object, ListenerRegistration)} if a
- * registration is replacing an old registration. The old registration will have already been
- * unregistered. Invoked while holding the multiplexer's internal lock. The default behavior is
- * simply to call into {@link #onRegistrationAdded(Object, ListenerRegistration)}.
+ * Invoked when one registration replaces another (through {@link #replaceRegistration(Object,
+ * Object, ListenerRegistration)}). The old registration has already been unregistered at this
+ * point. Invoked while holding the multiplexer's internal lock.
+ *
+ * <p>The default behavior is simply to call first {@link #onRegistrationRemoved(Object,
+ * ListenerRegistration)} and then {@link #onRegistrationAdded(Object, ListenerRegistration)}.
*/
- protected void onRegistrationReplaced(@NonNull TKey key, @NonNull TRegistration oldRegistration,
+ @GuardedBy("mMultiplexerLock")
+ protected void onRegistrationReplaced(
+ @NonNull TKey oldKey,
+ @NonNull TRegistration oldRegistration,
+ @NonNull TKey newKey,
@NonNull TRegistration newRegistration) {
- onRegistrationAdded(key, newRegistration);
+ onRegistrationRemoved(oldKey, oldRegistration);
+ onRegistrationAdded(newKey, newRegistration);
}
/**
* Invoked when a registration is removed. Invoked while holding the multiplexer's internal
* lock.
*/
+ @GuardedBy("mMultiplexerLock")
protected void onRegistrationRemoved(@NonNull TKey key, @NonNull TRegistration registration) {}
/**
@@ -204,6 +228,7 @@
* need to be present while there are active registrations. Invoked while holding the
* multiplexer's internal lock.
*/
+ @GuardedBy("mMultiplexerLock")
protected void onActive() {}
/**
@@ -212,6 +237,7 @@
* need to be present while there are active registrations. Invoked while holding the
* multiplexer's internal lock.
*/
+ @GuardedBy("mMultiplexerLock")
protected void onInactive() {}
/**
@@ -224,13 +250,12 @@
/**
* Atomically removes the registration with the old key and adds a new registration with the
- * given key. If there was a registration for the old key,
- * {@link #onRegistrationReplaced(Object, ListenerRegistration, ListenerRegistration)} will be
- * invoked for the new registration and key instead of
- * {@link #onRegistrationAdded(Object, ListenerRegistration)}, even though they may not share
- * the same key. The old key may be the same value as the new key, in which case this function
- * is equivalent to {@link #putRegistration(Object, ListenerRegistration)}. This method cannot
- * be called to add a registration re-entrantly.
+ * given key. If there was a registration for the old key, {@link
+ * #onRegistrationReplaced(Object, ListenerRegistration, Object, ListenerRegistration)} will be
+ * invoked instead of {@link #onRegistrationAdded(Object, ListenerRegistration)}, even if they
+ * share the same key. The old key may be the same value as the new key, in which case this
+ * function is equivalent to {@link #putRegistration(Object, ListenerRegistration)}. This method
+ * cannot be called to add a registration re-entrantly.
*/
protected final void replaceRegistration(@NonNull TKey oldKey, @NonNull TKey key,
@NonNull TRegistration registration) {
@@ -238,7 +263,7 @@
Objects.requireNonNull(key);
Objects.requireNonNull(registration);
- synchronized (mRegistrations) {
+ synchronized (mMultiplexerLock) {
// adding listeners reentrantly is not supported
Preconditions.checkState(!mReentrancyGuard.isReentrant());
@@ -257,12 +282,18 @@
boolean wasEmpty = mRegistrations.isEmpty();
TRegistration oldRegistration = null;
- int index = mRegistrations.indexOfKey(oldKey);
- if (index >= 0) {
- oldRegistration = removeRegistration(index, oldKey != key);
+ int oldIndex = mRegistrations.indexOfKey(oldKey);
+ if (oldIndex >= 0) {
+ // remove ourselves instead of using remove(), to balance registration callbacks
+ oldRegistration = mRegistrations.valueAt(oldIndex);
+ unregister(oldRegistration);
+ oldRegistration.onUnregister();
+ if (oldKey != key) {
+ mRegistrations.removeAt(oldIndex);
+ }
}
- if (oldKey == key && index >= 0) {
- mRegistrations.setValueAt(index, registration);
+ if (oldKey == key && oldIndex >= 0) {
+ mRegistrations.setValueAt(oldIndex, registration);
} else {
mRegistrations.put(key, registration);
}
@@ -274,7 +305,7 @@
if (oldRegistration == null) {
onRegistrationAdded(key, registration);
} else {
- onRegistrationReplaced(key, oldRegistration, registration);
+ onRegistrationReplaced(oldKey, oldRegistration, key, registration);
}
onRegistrationActiveChanged(registration);
}
@@ -282,29 +313,11 @@
}
/**
- * Removes the registration with the given key. This method cannot be called to remove a
- * registration re-entrantly.
- */
- protected final void removeRegistration(@NonNull Object key) {
- synchronized (mRegistrations) {
- // this method does not support removing listeners reentrantly
- Preconditions.checkState(!mReentrancyGuard.isReentrant());
-
- int index = mRegistrations.indexOfKey(key);
- if (index < 0) {
- return;
- }
-
- removeRegistration(index, true);
- }
- }
-
- /**
* Removes all registrations with keys that satisfy the given predicate. This method cannot be
* called to remove a registration re-entrantly.
*/
protected final void removeRegistrationIf(@NonNull Predicate<TKey> predicate) {
- synchronized (mRegistrations) {
+ synchronized (mMultiplexerLock) {
// this method does not support removing listeners reentrantly
Preconditions.checkState(!mReentrancyGuard.isReentrant());
@@ -329,13 +342,31 @@
}
/**
+ * Removes the registration with the given key. This method cannot be called to remove a
+ * registration re-entrantly.
+ */
+ protected final void removeRegistration(TKey key) {
+ synchronized (mMultiplexerLock) {
+ // this method does not support removing listeners reentrantly
+ Preconditions.checkState(!mReentrancyGuard.isReentrant());
+
+ int index = mRegistrations.indexOfKey(key);
+ if (index < 0) {
+ return;
+ }
+
+ removeRegistration(index);
+ }
+ }
+
+ /**
* Removes the given registration with the given key. If the given key has a different
* registration at the time this method is called, nothing happens. This method allows for
* re-entrancy, and may be called to remove a registration re-entrantly.
*/
- protected final void removeRegistration(@NonNull Object key,
+ protected final void removeRegistration(@NonNull TKey key,
@NonNull ListenerRegistration<?> registration) {
- synchronized (mRegistrations) {
+ synchronized (mMultiplexerLock) {
int index = mRegistrations.indexOfKey(key);
if (index < 0) {
return;
@@ -350,17 +381,13 @@
unregister(typedRegistration);
mReentrancyGuard.markForRemoval(key, typedRegistration);
} else {
- removeRegistration(index, true);
+ removeRegistration(index);
}
}
}
- @GuardedBy("mRegistrations")
- private TRegistration removeRegistration(int index, boolean removeEntry) {
- if (Build.IS_DEBUGGABLE) {
- Preconditions.checkState(Thread.holdsLock(mRegistrations));
- }
-
+ @GuardedBy("mMultiplexerLock")
+ private void removeRegistration(int index) {
TKey key = mRegistrations.keyAt(index);
TRegistration registration = mRegistrations.valueAt(index);
@@ -376,15 +403,11 @@
unregister(registration);
onRegistrationRemoved(key, registration);
registration.onUnregister();
- if (removeEntry) {
- mRegistrations.removeAt(index);
- if (mRegistrations.isEmpty()) {
- onUnregister();
- }
+ mRegistrations.removeAt(index);
+ if (mRegistrations.isEmpty()) {
+ onUnregister();
}
}
-
- return registration;
}
/**
@@ -392,14 +415,14 @@
* registration accordingly.
*/
protected final void updateService() {
- synchronized (mRegistrations) {
+ synchronized (mMultiplexerLock) {
if (mUpdateServiceBuffer.isBuffered()) {
mUpdateServiceBuffer.markUpdateServiceRequired();
return;
}
- ArrayList<TRegistration> actives = new ArrayList<>(mRegistrations.size());
final int size = mRegistrations.size();
+ ArrayList<TRegistration> actives = new ArrayList<>(size);
for (int i = 0; i < size; i++) {
TRegistration registration = mRegistrations.valueAt(i);
if (registration.isActive()) {
@@ -413,17 +436,17 @@
mServiceRegistered = false;
unregisterWithService();
}
- return;
- }
-
- TMergedRegistration merged = mergeRegistrations(actives);
- if (!mServiceRegistered || !Objects.equals(merged, mMerged)) {
+ } else {
+ TMergedRegistration merged = mergeRegistrations(actives);
if (mServiceRegistered) {
- mServiceRegistered = reregisterWithService(mMerged, merged, actives);
+ if (!Objects.equals(merged, mMerged)) {
+ mServiceRegistered = reregisterWithService(mMerged, merged, actives);
+ mMerged = mServiceRegistered ? merged : null;
+ }
} else {
mServiceRegistered = registerWithService(merged, actives);
+ mMerged = mServiceRegistered ? merged : null;
}
- mMerged = mServiceRegistered ? merged : null;
}
}
}
@@ -437,7 +460,7 @@
* reinitialized.
*/
protected final void resetService() {
- synchronized (mRegistrations) {
+ synchronized (mMultiplexerLock) {
if (mServiceRegistered) {
mMerged = null;
mServiceRegistered = false;
@@ -453,7 +476,31 @@
* buffering {@code updateService()} until after multiple adds/removes/updates occur.
*/
public UpdateServiceLock newUpdateServiceLock() {
- return new UpdateServiceLock(mUpdateServiceBuffer.acquire());
+ return new UpdateServiceLock(mUpdateServiceBuffer);
+ }
+
+ /**
+ * Evaluates the predicate on all registrations until the predicate returns true, at which point
+ * evaluation will cease. Returns true if the predicate ever returned true, and returns false
+ * otherwise.
+ */
+ protected final boolean findRegistration(Predicate<TRegistration> predicate) {
+ synchronized (mMultiplexerLock) {
+ // we only acquire a reentrancy guard in case of removal while iterating. this method
+ // does not directly affect active state or merged state, so there is no advantage to
+ // acquiring an update source buffer.
+ try (ReentrancyGuard ignored = mReentrancyGuard.acquire()) {
+ final int size = mRegistrations.size();
+ for (int i = 0; i < size; i++) {
+ TRegistration registration = mRegistrations.valueAt(i);
+ if (predicate.test(registration)) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
}
/**
@@ -463,7 +510,7 @@
* the resulting changes.
*/
protected final void updateRegistrations(@NonNull Predicate<TRegistration> predicate) {
- synchronized (mRegistrations) {
+ synchronized (mMultiplexerLock) {
// since updating a registration can invoke a variety of callbacks, we need to ensure
// those callbacks themselves do not re-enter, as this could lead to out-of-order
// callbacks. note that try-with-resources ordering is meaningful here as well. we want
@@ -492,7 +539,7 @@
*/
protected final boolean updateRegistration(@NonNull Object key,
@NonNull Predicate<TRegistration> predicate) {
- synchronized (mRegistrations) {
+ synchronized (mMultiplexerLock) {
// since updating a registration can invoke a variety of callbacks, we need to ensure
// those callbacks themselves do not re-enter, as this could lead to out-of-order
// callbacks. note that try-with-resources ordering is meaningful here as well. we want
@@ -515,12 +562,8 @@
}
}
- @GuardedBy("mRegistrations")
+ @GuardedBy("mMultiplexerLock")
private void onRegistrationActiveChanged(TRegistration registration) {
- if (Build.IS_DEBUGGABLE) {
- Preconditions.checkState(Thread.holdsLock(mRegistrations));
- }
-
boolean active = registration.isRegistered() && isActive(registration);
boolean changed = registration.setActive(active);
if (changed) {
@@ -547,7 +590,7 @@
*/
protected final void deliverToListeners(
@NonNull Function<TRegistration, ListenerOperation<TListener>> function) {
- synchronized (mRegistrations) {
+ synchronized (mMultiplexerLock) {
try (ReentrancyGuard ignored = mReentrancyGuard.acquire()) {
final int size = mRegistrations.size();
for (int i = 0; i < size; i++) {
@@ -571,7 +614,7 @@
* </pre>
*/
protected final void deliverToListeners(@NonNull ListenerOperation<TListener> operation) {
- synchronized (mRegistrations) {
+ synchronized (mMultiplexerLock) {
try (ReentrancyGuard ignored = mReentrancyGuard.acquire()) {
final int size = mRegistrations.size();
for (int i = 0; i < size; i++) {
@@ -584,6 +627,7 @@
}
}
+ @GuardedBy("mMultiplexerLock")
private void unregister(TRegistration registration) {
registration.unregisterInternal();
onRegistrationActiveChanged(registration);
@@ -593,7 +637,7 @@
* Dumps debug information.
*/
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
- synchronized (mRegistrations) {
+ synchronized (mMultiplexerLock) {
pw.print("service: ");
pw.print(getServiceState());
pw.println();
@@ -620,6 +664,7 @@
* May be overridden to provide additional details on service state when dumping the manager
* state. Invoked while holding the multiplexer's internal lock.
*/
+ @GuardedBy("mMultiplexerLock")
protected String getServiceState() {
if (mServiceRegistered) {
if (mMerged != null) {
@@ -643,61 +688,63 @@
*/
private final class ReentrancyGuard implements AutoCloseable {
- @GuardedBy("mRegistrations")
+ @GuardedBy("mMultiplexerLock")
private int mGuardCount;
- @GuardedBy("mRegistrations")
- private @Nullable ArraySet<Entry<Object, ListenerRegistration<?>>> mScheduledRemovals;
+
+ @GuardedBy("mMultiplexerLock")
+ @Nullable private ArraySet<Entry<TKey, ListenerRegistration<?>>> mScheduledRemovals;
ReentrancyGuard() {
mGuardCount = 0;
mScheduledRemovals = null;
}
- @GuardedBy("mRegistrations")
boolean isReentrant() {
- if (Build.IS_DEBUGGABLE) {
- Preconditions.checkState(Thread.holdsLock(mRegistrations));
+ synchronized (mMultiplexerLock) {
+ return mGuardCount != 0;
}
- return mGuardCount != 0;
}
- @GuardedBy("mRegistrations")
- void markForRemoval(Object key, ListenerRegistration<?> registration) {
- if (Build.IS_DEBUGGABLE) {
- Preconditions.checkState(Thread.holdsLock(mRegistrations));
- }
- Preconditions.checkState(isReentrant());
+ void markForRemoval(TKey key, ListenerRegistration<?> registration) {
+ synchronized (mMultiplexerLock) {
+ Preconditions.checkState(isReentrant());
- if (mScheduledRemovals == null) {
- mScheduledRemovals = new ArraySet<>(mRegistrations.size());
+ if (mScheduledRemovals == null) {
+ mScheduledRemovals = new ArraySet<>(mRegistrations.size());
+ }
+ mScheduledRemovals.add(new AbstractMap.SimpleImmutableEntry<>(key, registration));
}
- mScheduledRemovals.add(new AbstractMap.SimpleImmutableEntry<>(key, registration));
}
ReentrancyGuard acquire() {
- ++mGuardCount;
- return this;
+ synchronized (mMultiplexerLock) {
+ ++mGuardCount;
+ return this;
+ }
}
@Override
public void close() {
- ArraySet<Entry<Object, ListenerRegistration<?>>> scheduledRemovals = null;
+ synchronized (mMultiplexerLock) {
+ Preconditions.checkState(mGuardCount > 0);
- Preconditions.checkState(mGuardCount > 0);
- if (--mGuardCount == 0) {
- scheduledRemovals = mScheduledRemovals;
- mScheduledRemovals = null;
- }
+ ArraySet<Entry<TKey, ListenerRegistration<?>>> scheduledRemovals = null;
- if (scheduledRemovals == null) {
- return;
- }
+ if (--mGuardCount == 0) {
+ scheduledRemovals = mScheduledRemovals;
+ mScheduledRemovals = null;
+ }
- try (UpdateServiceBuffer ignored = mUpdateServiceBuffer.acquire()) {
- final int size = scheduledRemovals.size();
- for (int i = 0; i < size; i++) {
- Entry<Object, ListenerRegistration<?>> entry = scheduledRemovals.valueAt(i);
- removeRegistration(entry.getKey(), entry.getValue());
+ if (scheduledRemovals == null) {
+ return;
+ }
+
+ try (UpdateServiceBuffer ignored = mUpdateServiceBuffer.acquire()) {
+ final int size = scheduledRemovals.size();
+ for (int i = 0; i < size; i++) {
+ Entry<TKey, ListenerRegistration<?>> entry = scheduledRemovals.valueAt(i);
+ removeRegistration(entry.getKey(), entry.getValue());
+ }
}
}
}
@@ -721,6 +768,7 @@
@GuardedBy("this")
private int mBufferCount;
+
@GuardedBy("this")
private boolean mUpdateServiceRequired;
@@ -765,18 +813,18 @@
* {@link #close()}ed. This can be used to save work by acquiring the lock before multiple calls
* to updateService() are expected, and closing the lock after.
*/
- public final class UpdateServiceLock implements AutoCloseable {
+ public static final class UpdateServiceLock implements AutoCloseable {
- private @Nullable UpdateServiceBuffer mUpdateServiceBuffer;
+ @Nullable private ListenerMultiplexer<?, ?, ?, ?>.UpdateServiceBuffer mUpdateServiceBuffer;
- UpdateServiceLock(UpdateServiceBuffer updateServiceBuffer) {
- mUpdateServiceBuffer = updateServiceBuffer;
+ UpdateServiceLock(ListenerMultiplexer<?, ?, ?, ?>.UpdateServiceBuffer updateServiceBuffer) {
+ mUpdateServiceBuffer = updateServiceBuffer.acquire();
}
@Override
public void close() {
if (mUpdateServiceBuffer != null) {
- UpdateServiceBuffer buffer = mUpdateServiceBuffer;
+ ListenerMultiplexer<?, ?, ?, ?>.UpdateServiceBuffer buffer = mUpdateServiceBuffer;
mUpdateServiceBuffer = null;
buffer.close();
}
diff --git a/services/core/java/com/android/server/location/listeners/ListenerRegistration.java b/services/core/java/com/android/server/location/listeners/ListenerRegistration.java
index 711dde8..fcb2a9b 100644
--- a/services/core/java/com/android/server/location/listeners/ListenerRegistration.java
+++ b/services/core/java/com/android/server/location/listeners/ListenerRegistration.java
@@ -35,7 +35,7 @@
private boolean mActive;
- private volatile @Nullable TListener mListener;
+ @Nullable private volatile TListener mListener;
protected ListenerRegistration(Executor executor, TListener listener) {
mExecutor = Objects.requireNonNull(executor);
@@ -43,6 +43,13 @@
mListener = Objects.requireNonNull(listener);
}
+ /**
+ * Returns a tag to use for logging. Should be overridden by subclasses.
+ */
+ protected String getTag() {
+ return "ListenerRegistration";
+ }
+
protected final Executor getExecutor() {
return mExecutor;
}
@@ -50,26 +57,36 @@
/**
* May be overridden by subclasses. Invoked when registration occurs. Invoked while holding the
* owning multiplexer's internal lock.
+ *
+ * <p>If overridden you must ensure the superclass method is invoked (usually as the first thing
+ * in the overridden method).
*/
protected void onRegister(Object key) {}
/**
* May be overridden by subclasses. Invoked when unregistration occurs. Invoked while holding
* the owning multiplexer's internal lock.
+ *
+ * <p>If overridden you must ensure the superclass method is invoked (usually as the last thing
+ * in the overridden method).
*/
protected void onUnregister() {}
/**
- * May be overridden by subclasses. Invoked when this registration becomes active. If this
- * returns a non-null operation, that operation will be invoked for the listener. Invoked
- * while holding the owning multiplexer's internal lock.
+ * May be overridden by subclasses. Invoked when this registration becomes active. Invoked while
+ * holding the owning multiplexer's internal lock.
+ *
+ * <p>If overridden you must ensure the superclass method is invoked (usually as the first thing
+ * in the overridden method).
*/
protected void onActive() {}
/**
- * May be overridden by subclasses. Invoked when registration becomes inactive. If this returns
- * a non-null operation, that operation will be invoked for the listener. Invoked while holding
- * the owning multiplexer's internal lock.
+ * May be overridden by subclasses. Invoked when registration becomes inactive. Invoked while
+ * holding the owning multiplexer's internal lock.
+ *
+ * <p>If overridden you must ensure the superclass method is invoked (usually as the last thing
+ * in the overridden method).
*/
protected void onInactive() {}
diff --git a/services/core/java/com/android/server/location/listeners/PendingIntentListenerRegistration.java b/services/core/java/com/android/server/location/listeners/PendingIntentListenerRegistration.java
index 240ac01..c976601 100644
--- a/services/core/java/com/android/server/location/listeners/PendingIntentListenerRegistration.java
+++ b/services/core/java/com/android/server/location/listeners/PendingIntentListenerRegistration.java
@@ -16,63 +16,47 @@
package com.android.server.location.listeners;
-import android.annotation.Nullable;
+import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR;
+
import android.app.PendingIntent;
-import android.location.util.identity.CallerIdentity;
import android.util.Log;
/**
* A registration that works with PendingIntent keys, and registers a CancelListener to
- * automatically remove the registration if the PendingIntent is canceled. The key for this
- * registration must either be a {@link PendingIntent} or a {@link PendingIntentKey}.
+ * automatically remove the registration if the PendingIntent is canceled.
*
- * @param <TRequest> request type
+ * @param <TKey> key type
* @param <TListener> listener type
*/
-public abstract class PendingIntentListenerRegistration<TRequest, TListener> extends
- RemoteListenerRegistration<TRequest, TListener> implements PendingIntent.CancelListener {
+public abstract class PendingIntentListenerRegistration<TKey, TListener> extends
+ RemovableListenerRegistration<TKey, TListener> implements PendingIntent.CancelListener {
- /**
- * Interface to allowed pending intent retrieval when keys are not themselves PendingIntents.
- */
- public interface PendingIntentKey {
- /**
- * Returns the pending intent associated with this key.
- */
- PendingIntent getPendingIntent();
+ protected PendingIntentListenerRegistration(TListener listener) {
+ super(DIRECT_EXECUTOR, listener);
}
- protected PendingIntentListenerRegistration(@Nullable TRequest request,
- CallerIdentity callerIdentity, TListener listener) {
- super(request, callerIdentity, listener);
+ protected abstract PendingIntent getPendingIntentFromKey(TKey key);
+
+ @Override
+ protected void onRegister() {
+ super.onRegister();
+
+ if (!getPendingIntentFromKey(getKey()).addCancelListener(DIRECT_EXECUTOR, this)) {
+ remove();
+ }
}
@Override
- protected final void onRemovableListenerRegister() {
- getPendingIntentFromKey(getKey()).registerCancelListener(this);
- onPendingIntentListenerRegister();
+ protected void onUnregister() {
+ getPendingIntentFromKey(getKey()).removeCancelListener(this);
+
+ super.onUnregister();
}
@Override
- protected final void onRemovableListenerUnregister() {
- onPendingIntentListenerUnregister();
- getPendingIntentFromKey(getKey()).unregisterCancelListener(this);
- }
-
- /**
- * May be overridden in place of {@link #onRemovableListenerRegister()}.
- */
- protected void onPendingIntentListenerRegister() {}
-
- /**
- * May be overridden in place of {@link #onRemovableListenerUnregister()}.
- */
- protected void onPendingIntentListenerUnregister() {}
-
- @Override
protected void onOperationFailure(ListenerOperation<TListener> operation, Exception e) {
if (e instanceof PendingIntent.CanceledException) {
- Log.w(getOwner().getTag(), "registration " + this + " removed", e);
+ Log.w(getTag(), "registration " + this + " removed", e);
remove();
} else {
super.onOperationFailure(operation, e);
@@ -81,21 +65,10 @@
@Override
public void onCanceled(PendingIntent intent) {
- if (Log.isLoggable(getOwner().getTag(), Log.DEBUG)) {
- Log.d(getOwner().getTag(),
- "pending intent registration " + getIdentity() + " canceled");
+ if (Log.isLoggable(getTag(), Log.DEBUG)) {
+ Log.d(getTag(), "pending intent registration " + this + " canceled");
}
remove();
}
-
- private PendingIntent getPendingIntentFromKey(Object key) {
- if (key instanceof PendingIntent) {
- return (PendingIntent) key;
- } else if (key instanceof PendingIntentKey) {
- return ((PendingIntentKey) key).getPendingIntent();
- } else {
- throw new IllegalArgumentException("key must be PendingIntent or PendingIntentKey");
- }
- }
-}
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/location/listeners/RemoteListenerRegistration.java b/services/core/java/com/android/server/location/listeners/RemoteListenerRegistration.java
deleted file mode 100644
index 4eca577..0000000
--- a/services/core/java/com/android/server/location/listeners/RemoteListenerRegistration.java
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright (C) 2020 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.location.listeners;
-
-
-import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR;
-
-import android.annotation.Nullable;
-import android.location.util.identity.CallerIdentity;
-import android.os.Process;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.server.FgThread;
-
-import java.util.Objects;
-import java.util.concurrent.Executor;
-
-/**
- * A listener registration representing a remote (possibly from a different process) listener.
- * Listeners from a different process will be run on a direct executor, since the x-process listener
- * invocation should already be asynchronous. Listeners from the same process will be run on a
- * normal executor, since in-process listener invocation may be synchronous.
- *
- * @param <TRequest> request type
- * @param <TListener> listener type
- */
-public abstract class RemoteListenerRegistration<TRequest, TListener> extends
- RemovableListenerRegistration<TRequest, TListener> {
-
- @VisibleForTesting
- public static final Executor IN_PROCESS_EXECUTOR = FgThread.getExecutor();
-
- private static Executor chooseExecutor(CallerIdentity identity) {
- // if a client is in the same process as us, binder calls will execute synchronously and
- // we shouldn't run callbacks directly since they might be run under lock and deadlock
- if (identity.getPid() == Process.myPid()) {
- // there's a slight loophole here for pending intents - pending intent callbacks can
- // always be run on the direct executor since they're always asynchronous, but honestly
- // you shouldn't be using pending intent callbacks within the same process anyways
- return IN_PROCESS_EXECUTOR;
- } else {
- return DIRECT_EXECUTOR;
- }
- }
-
- private final CallerIdentity mIdentity;
-
- protected RemoteListenerRegistration(@Nullable TRequest request, CallerIdentity identity,
- TListener listener) {
- super(chooseExecutor(identity), request, listener);
- mIdentity = Objects.requireNonNull(identity);
- }
-
- /**
- * Returns the listener identity.
- */
- public final CallerIdentity getIdentity() {
- return mIdentity;
- }
-}
-
diff --git a/services/core/java/com/android/server/location/listeners/RemovableListenerRegistration.java b/services/core/java/com/android/server/location/listeners/RemovableListenerRegistration.java
index 618ff24..3c302fb 100644
--- a/services/core/java/com/android/server/location/listeners/RemovableListenerRegistration.java
+++ b/services/core/java/com/android/server/location/listeners/RemovableListenerRegistration.java
@@ -20,22 +20,23 @@
import java.util.Objects;
import java.util.concurrent.Executor;
+import java.util.concurrent.atomic.AtomicBoolean;
/**
* A listener registration that stores its own key, and thus can remove itself. By default it will
* remove itself if any checked exception occurs on listener execution.
*
- * @param <TRequest> request type
+ * @param <TKey> key type
* @param <TListener> listener type
*/
-public abstract class RemovableListenerRegistration<TRequest, TListener> extends
- RequestListenerRegistration<TRequest, TListener> {
+public abstract class RemovableListenerRegistration<TKey, TListener> extends
+ ListenerRegistration<TListener> {
- private volatile @Nullable Object mKey;
+ @Nullable private volatile TKey mKey;
+ private final AtomicBoolean mRemoved = new AtomicBoolean(false);
- protected RemovableListenerRegistration(Executor executor, @Nullable TRequest request,
- TListener listener) {
- super(executor, request, listener);
+ protected RemovableListenerRegistration(Executor executor, TListener listener) {
+ super(executor, listener);
}
/**
@@ -43,46 +44,76 @@
* with. Often this is easiest to accomplish by defining registration subclasses as non-static
* inner classes of the multiplexer they are to be used with.
*/
- protected abstract ListenerMultiplexer<?, ? super TListener, ?, ?> getOwner();
+ protected abstract ListenerMultiplexer<TKey, ? super TListener, ?, ?> getOwner();
/**
* Returns the key associated with this registration. May not be invoked before
* {@link #onRegister(Object)} or after {@link #onUnregister()}.
*/
- protected final Object getKey() {
+ protected final TKey getKey() {
return Objects.requireNonNull(mKey);
}
/**
- * Removes this registration. Does nothing if invoked before {@link #onRegister(Object)} or
- * after {@link #onUnregister()}. It is safe to invoke this from within either function.
+ * Convenience method equivalent to invoking {@link #remove(boolean)} with the
+ * {@code immediately} parameter set to true.
*/
public final void remove() {
- Object key = mKey;
- if (key != null) {
- getOwner().removeRegistration(key, this);
+ remove(true);
+ }
+
+ /**
+ * Removes this registration. If the {@code immediately} parameter is true, all pending listener
+ * invocations will fail. If the {@code immediately} parameter is false, listener invocations
+ * that were scheduled before remove was invoked (including invocations scheduled within {@link
+ * #onRemove(boolean)}) will continue, but any listener invocations scheduled after remove was
+ * invoked will fail.
+ *
+ * <p>Only the first call to this method will ever go through (and so {@link #onRemove(boolean)}
+ * will only ever be invoked once).
+ *
+ * <p>Does nothing if invoked before {@link #onRegister()} or after {@link #onUnregister()}.
+ */
+ public final void remove(boolean immediately) {
+ TKey key = mKey;
+ if (key != null && !mRemoved.getAndSet(true)) {
+ onRemove(immediately);
+ if (immediately) {
+ getOwner().removeRegistration(key, this);
+ } else {
+ executeOperation(listener -> getOwner().removeRegistration(key, this));
+ }
}
}
+ /**
+ * Invoked just before this registration is removed due to {@link #remove(boolean)}, on the same
+ * thread as the responsible {@link #remove(boolean)} call.
+ *
+ * <p>This method will only ever be invoked once, no matter how many calls to {@link
+ * #remove(boolean)} are made, as any registration can only be removed once.
+ */
+ protected void onRemove(boolean immediately) {}
+
@Override
protected final void onRegister(Object key) {
- mKey = Objects.requireNonNull(key);
- onRemovableListenerRegister();
+ super.onRegister(key);
+ mKey = (TKey) Objects.requireNonNull(key);
+ onRegister();
}
+ /**
+ * May be overridden by subclasses. Invoked when registration occurs. Invoked while holding the
+ * owning multiplexer's internal lock.
+ *
+ * <p>If overridden you must ensure the superclass method is invoked (usually as the first thing
+ * in the overridden method).
+ */
+ protected void onRegister() {}
+
@Override
- protected final void onUnregister() {
- onRemovableListenerUnregister();
+ protected void onUnregister() {
mKey = null;
+ super.onUnregister();
}
-
- /**
- * May be overridden in place of {@link #onRegister(Object)}.
- */
- protected void onRemovableListenerRegister() {}
-
- /**
- * May be overridden in place of {@link #onUnregister()}.
- */
- protected void onRemovableListenerUnregister() {}
}
diff --git a/services/core/java/com/android/server/location/listeners/RequestListenerRegistration.java b/services/core/java/com/android/server/location/listeners/RequestListenerRegistration.java
deleted file mode 100644
index 0c2fc91..0000000
--- a/services/core/java/com/android/server/location/listeners/RequestListenerRegistration.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright (C) 2020 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.location.listeners;
-
-import java.util.concurrent.Executor;
-
-/**
- * A listener registration object which includes an associated request.
- *
- * @param <TRequest> request type
- * @param <TListener> listener type
- */
-public class RequestListenerRegistration<TRequest, TListener> extends
- ListenerRegistration<TListener> {
-
- private final TRequest mRequest;
-
- protected RequestListenerRegistration(Executor executor, TRequest request,
- TListener listener) {
- super(executor, listener);
- mRequest = request;
- }
-
- /**
- * Returns the request associated with this listener, or null if one wasn't supplied.
- */
- public TRequest getRequest() {
- return mRequest;
- }
-
- @Override
- public String toString() {
- if (mRequest == null) {
- return "[]";
- } else {
- return mRequest.toString();
- }
- }
-}
-
diff --git a/services/core/java/com/android/server/location/provider/LocationProviderManager.java b/services/core/java/com/android/server/location/provider/LocationProviderManager.java
index 549fd49..a69a079 100644
--- a/services/core/java/com/android/server/location/provider/LocationProviderManager.java
+++ b/services/core/java/com/android/server/location/provider/LocationProviderManager.java
@@ -67,7 +67,6 @@
import android.location.provider.ProviderRequest;
import android.location.util.identity.CallerIdentity;
import android.os.Binder;
-import android.os.Build;
import android.os.Bundle;
import android.os.CancellationSignal;
import android.os.IBinder;
@@ -115,7 +114,7 @@
import com.android.server.location.injector.UserInfoHelper;
import com.android.server.location.injector.UserInfoHelper.UserListener;
import com.android.server.location.listeners.ListenerMultiplexer;
-import com.android.server.location.listeners.RemoteListenerRegistration;
+import com.android.server.location.listeners.RemovableListenerRegistration;
import com.android.server.location.settings.LocationSettings;
import com.android.server.location.settings.LocationUserSettings;
@@ -124,8 +123,10 @@
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Collection;
+import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.Executor;
import java.util.function.Predicate;
/**
@@ -354,44 +355,60 @@
public void deliverOnFlushComplete(int requestCode) {}
}
- protected abstract class Registration extends RemoteListenerRegistration<LocationRequest,
- LocationTransport> {
+ protected abstract class Registration extends RemovableListenerRegistration<Object,
+ LocationTransport> {
+ private final LocationRequest mBaseRequest;
+ private final CallerIdentity mIdentity;
private final @PermissionLevel int mPermissionLevel;
// we cache these values because checking/calculating on the fly is more expensive
+ @GuardedBy("mMultiplexerLock")
private boolean mPermitted;
+ @GuardedBy("mMultiplexerLock")
private boolean mForeground;
+ @GuardedBy("mMultiplexerLock")
private LocationRequest mProviderLocationRequest;
+ @GuardedBy("mMultiplexerLock")
private boolean mIsUsingHighPower;
- private @Nullable Location mLastLocation = null;
+ @Nullable private Location mLastLocation = null;
- protected Registration(LocationRequest request, CallerIdentity identity,
+ protected Registration(LocationRequest request, CallerIdentity identity, Executor executor,
LocationTransport transport, @PermissionLevel int permissionLevel) {
- super(Objects.requireNonNull(request), identity, transport);
+ super(executor, transport);
Preconditions.checkArgument(identity.getListenerId() != null);
Preconditions.checkArgument(permissionLevel > PERMISSION_NONE);
Preconditions.checkArgument(!request.getWorkSource().isEmpty());
+ mBaseRequest = Objects.requireNonNull(request);
+ mIdentity = Objects.requireNonNull(identity);
mPermissionLevel = permissionLevel;
mProviderLocationRequest = request;
}
- @GuardedBy("mLock")
- @Override
- protected final void onRemovableListenerRegister() {
- if (Build.IS_DEBUGGABLE) {
- Preconditions.checkState(Thread.holdsLock(mLock));
+ public final CallerIdentity getIdentity() {
+ return mIdentity;
+ }
+
+ public final LocationRequest getRequest() {
+ synchronized (mMultiplexerLock) {
+ return mProviderLocationRequest;
}
+ }
+
+ @GuardedBy("mMultiplexerLock")
+ @Override
+ protected void onRegister() {
+ super.onRegister();
if (D) {
Log.d(TAG, mName + " provider added registration from " + getIdentity() + " -> "
+ getRequest());
}
- EVENT_LOG.logProviderClientRegistered(mName, getIdentity(), super.getRequest());
+ EVENT_LOG.logProviderClientRegistered(mName, getIdentity(), mBaseRequest);
// initialization order is important as there are ordering dependencies
mPermitted = mLocationPermissionsHelper.hasLocationPermissions(mPermissionLevel,
@@ -400,110 +417,72 @@
mProviderLocationRequest = calculateProviderLocationRequest();
mIsUsingHighPower = isUsingHighPower();
- onProviderListenerRegister();
-
if (mForeground) {
EVENT_LOG.logProviderClientForeground(mName, getIdentity());
}
}
- @GuardedBy("mLock")
+ @GuardedBy("mMultiplexerLock")
@Override
- protected final void onRemovableListenerUnregister() {
- if (Build.IS_DEBUGGABLE) {
- Preconditions.checkState(Thread.holdsLock(mLock));
- }
-
- onProviderListenerUnregister();
-
+ protected void onUnregister() {
EVENT_LOG.logProviderClientUnregistered(mName, getIdentity());
if (D) {
Log.d(TAG, mName + " provider removed registration from " + getIdentity());
}
+
+ super.onUnregister();
}
- /**
- * Subclasses may override this instead of {@link #onRemovableListenerRegister()}.
- */
- @GuardedBy("mLock")
- protected void onProviderListenerRegister() {}
-
- /**
- * Subclasses may override this instead of {@link #onRemovableListenerUnregister()}.
- */
- @GuardedBy("mLock")
- protected void onProviderListenerUnregister() {}
-
+ @GuardedBy("mMultiplexerLock")
@Override
- protected final void onActive() {
- if (Build.IS_DEBUGGABLE) {
- Preconditions.checkState(Thread.holdsLock(mLock));
- }
-
+ protected void onActive() {
EVENT_LOG.logProviderClientActive(mName, getIdentity());
if (!getRequest().isHiddenFromAppOps()) {
mAppOpsHelper.startOpNoThrow(OP_MONITOR_LOCATION, getIdentity());
}
onHighPowerUsageChanged();
-
- onProviderListenerActive();
}
+ @GuardedBy("mMultiplexerLock")
@Override
- protected final void onInactive() {
- if (Build.IS_DEBUGGABLE) {
- Preconditions.checkState(Thread.holdsLock(mLock));
- }
-
+ protected void onInactive() {
onHighPowerUsageChanged();
if (!getRequest().isHiddenFromAppOps()) {
mAppOpsHelper.finishOp(OP_MONITOR_LOCATION, getIdentity());
}
- onProviderListenerInactive();
-
EVENT_LOG.logProviderClientInactive(mName, getIdentity());
}
- /**
- * Subclasses may override this instead of {@link #onActive()}.
- */
- @GuardedBy("mLock")
- protected void onProviderListenerActive() {}
-
- /**
- * Subclasses may override this instead of {@link #onInactive()} ()}.
- */
- @GuardedBy("mLock")
- protected void onProviderListenerInactive() {}
-
- @Override
- public final LocationRequest getRequest() {
- return mProviderLocationRequest;
- }
-
- @GuardedBy("mLock")
+ @GuardedBy("mMultiplexerLock")
final void setLastDeliveredLocation(@Nullable Location location) {
mLastLocation = location;
}
- @GuardedBy("mLock")
public final Location getLastDeliveredLocation() {
- return mLastLocation;
+ synchronized (mMultiplexerLock) {
+ return mLastLocation;
+ }
}
public @PermissionLevel int getPermissionLevel() {
- return mPermissionLevel;
+ synchronized (mMultiplexerLock) {
+ return mPermissionLevel;
+ }
}
public final boolean isForeground() {
- return mForeground;
+ synchronized (mMultiplexerLock) {
+ return mForeground;
+ }
}
public final boolean isPermitted() {
- return mPermitted;
+ synchronized (mMultiplexerLock) {
+ return mPermitted;
+ }
}
public final void flush(int requestCode) {
@@ -519,13 +498,14 @@
return LocationProviderManager.this;
}
- @GuardedBy("mLock")
final boolean onProviderPropertiesChanged() {
- onHighPowerUsageChanged();
- return false;
+ synchronized (mMultiplexerLock) {
+ onHighPowerUsageChanged();
+ return false;
+ }
}
- @GuardedBy("mLock")
+ @GuardedBy("mMultiplexerLock")
private void onHighPowerUsageChanged() {
boolean isUsingHighPower = isUsingHighPower();
if (isUsingHighPower != mIsUsingHighPower) {
@@ -541,12 +521,7 @@
}
}
- @GuardedBy("mLock")
private boolean isUsingHighPower() {
- if (Build.IS_DEBUGGABLE) {
- Preconditions.checkState(Thread.holdsLock(mLock));
- }
-
ProviderProperties properties = getProperties();
if (properties == null) {
return false;
@@ -557,30 +532,28 @@
&& properties.getPowerUsage() == ProviderProperties.POWER_USAGE_HIGH;
}
- @GuardedBy("mLock")
final boolean onLocationPermissionsChanged(@Nullable String packageName) {
- if (packageName == null || getIdentity().getPackageName().equals(packageName)) {
- return onLocationPermissionsChanged();
- }
+ synchronized (mMultiplexerLock) {
+ if (packageName == null || getIdentity().getPackageName().equals(packageName)) {
+ return onLocationPermissionsChanged();
+ }
- return false;
+ return false;
+ }
}
- @GuardedBy("mLock")
final boolean onLocationPermissionsChanged(int uid) {
- if (getIdentity().getUid() == uid) {
- return onLocationPermissionsChanged();
- }
+ synchronized (mMultiplexerLock) {
+ if (getIdentity().getUid() == uid) {
+ return onLocationPermissionsChanged();
+ }
- return false;
+ return false;
+ }
}
- @GuardedBy("mLock")
+ @GuardedBy("mMultiplexerLock")
private boolean onLocationPermissionsChanged() {
- if (Build.IS_DEBUGGABLE) {
- Preconditions.checkState(Thread.holdsLock(mLock));
- }
-
boolean permitted = mLocationPermissionsHelper.hasLocationPermissions(mPermissionLevel,
getIdentity());
if (permitted != mPermitted) {
@@ -603,82 +576,73 @@
return false;
}
- @GuardedBy("mLock")
final boolean onAdasGnssLocationEnabledChanged(int userId) {
- if (Build.IS_DEBUGGABLE) {
- Preconditions.checkState(Thread.holdsLock(mLock));
- }
-
- if (getIdentity().getUserId() == userId) {
- return onProviderLocationRequestChanged();
- }
-
- return false;
- }
-
- @GuardedBy("mLock")
- final boolean onForegroundChanged(int uid, boolean foreground) {
- if (Build.IS_DEBUGGABLE) {
- Preconditions.checkState(Thread.holdsLock(mLock));
- }
-
- if (getIdentity().getUid() == uid && foreground != mForeground) {
- if (D) {
- Log.v(TAG, mName + " provider uid " + uid + " foreground = " + foreground);
+ synchronized (mMultiplexerLock) {
+ if (getIdentity().getUserId() == userId) {
+ return onProviderLocationRequestChanged();
}
- mForeground = foreground;
-
- if (mForeground) {
- EVENT_LOG.logProviderClientForeground(mName, getIdentity());
- } else {
- EVENT_LOG.logProviderClientBackground(mName, getIdentity());
- }
-
- // note that onProviderLocationRequestChanged() is always called
- return onProviderLocationRequestChanged()
- || mLocationPowerSaveModeHelper.getLocationPowerSaveMode()
- == LOCATION_MODE_FOREGROUND_ONLY;
- }
-
- return false;
- }
-
- @GuardedBy("mLock")
- final boolean onProviderLocationRequestChanged() {
- if (Build.IS_DEBUGGABLE) {
- Preconditions.checkState(Thread.holdsLock(mLock));
- }
-
- LocationRequest newRequest = calculateProviderLocationRequest();
- if (mProviderLocationRequest.equals(newRequest)) {
return false;
}
-
- LocationRequest oldRequest = mProviderLocationRequest;
- mProviderLocationRequest = newRequest;
- onHighPowerUsageChanged();
- updateService();
-
- // if bypass state has changed then the active state may have changed
- return oldRequest.isBypass() != newRequest.isBypass();
}
+ final boolean onForegroundChanged(int uid, boolean foreground) {
+ synchronized (mMultiplexerLock) {
+ if (getIdentity().getUid() == uid && foreground != mForeground) {
+ if (D) {
+ Log.v(TAG, mName + " provider uid " + uid + " foreground = " + foreground);
+ }
+
+ mForeground = foreground;
+
+ if (mForeground) {
+ EVENT_LOG.logProviderClientForeground(mName, getIdentity());
+ } else {
+ EVENT_LOG.logProviderClientBackground(mName, getIdentity());
+ }
+
+ // note that onProviderLocationRequestChanged() is always called
+ return onProviderLocationRequestChanged()
+ || mLocationPowerSaveModeHelper.getLocationPowerSaveMode()
+ == LOCATION_MODE_FOREGROUND_ONLY;
+ }
+
+ return false;
+ }
+ }
+
+ final boolean onProviderLocationRequestChanged() {
+ synchronized (mMultiplexerLock) {
+ LocationRequest newRequest = calculateProviderLocationRequest();
+ if (mProviderLocationRequest.equals(newRequest)) {
+ return false;
+ }
+
+ LocationRequest oldRequest = mProviderLocationRequest;
+ mProviderLocationRequest = newRequest;
+ onHighPowerUsageChanged();
+ updateService();
+
+ // if bypass state has changed then the active state may have changed
+ return oldRequest.isBypass() != newRequest.isBypass();
+ }
+ }
+
+ @GuardedBy("mMultiplexerLock")
private LocationRequest calculateProviderLocationRequest() {
- LocationRequest baseRequest = super.getRequest();
- LocationRequest.Builder builder = new LocationRequest.Builder(baseRequest);
+ LocationRequest.Builder builder = new LocationRequest.Builder(mBaseRequest);
if (mPermissionLevel < PERMISSION_FINE) {
builder.setQuality(LocationRequest.QUALITY_LOW_POWER);
- if (baseRequest.getIntervalMillis() < MIN_COARSE_INTERVAL_MS) {
+ if (mBaseRequest.getIntervalMillis() < MIN_COARSE_INTERVAL_MS) {
builder.setIntervalMillis(MIN_COARSE_INTERVAL_MS);
}
- if (baseRequest.getMinUpdateIntervalMillis() < MIN_COARSE_INTERVAL_MS) {
+ if (mBaseRequest.getMinUpdateIntervalMillis() < MIN_COARSE_INTERVAL_MS) {
builder.setMinUpdateIntervalMillis(MIN_COARSE_INTERVAL_MS);
}
}
- boolean locationSettingsIgnored = baseRequest.isLocationSettingsIgnored();
+ boolean locationSettingsIgnored = mBaseRequest.isLocationSettingsIgnored();
if (locationSettingsIgnored) {
// if we are not currently allowed use location settings ignored, disable it
if (!mSettingsHelper.getIgnoreSettingsAllowlist().contains(
@@ -690,7 +654,7 @@
builder.setLocationSettingsIgnored(locationSettingsIgnored);
}
- boolean adasGnssBypass = baseRequest.isAdasGnssBypass();
+ boolean adasGnssBypass = mBaseRequest.isAdasGnssBypass();
if (adasGnssBypass) {
// if we are not currently allowed use adas gnss bypass, disable it
if (!GPS_PROVIDER.equals(mName)) {
@@ -710,7 +674,7 @@
if (!locationSettingsIgnored && !isThrottlingExempt()) {
// throttle in the background
if (!mForeground) {
- builder.setIntervalMillis(max(baseRequest.getIntervalMillis(),
+ builder.setIntervalMillis(max(mBaseRequest.getIntervalMillis(),
mSettingsHelper.getBackgroundThrottleIntervalMs()));
}
}
@@ -727,8 +691,7 @@
return mLocationManagerInternal.isProvider(null, getIdentity());
}
- @GuardedBy("mLock")
- abstract @Nullable ListenerOperation<LocationTransport> acceptLocationChange(
+ @Nullable abstract ListenerOperation<LocationTransport> acceptLocationChange(
LocationResult fineLocationResult);
@Override
@@ -769,13 +732,19 @@
final ExternalWakeLockReleaser mWakeLockReleaser;
private volatile ProviderTransport mProviderTransport;
+
+ @GuardedBy("mMultiplexerLock")
private int mNumLocationsDelivered = 0;
+ @GuardedBy("mMultiplexerLock")
private long mExpirationRealtimeMs = Long.MAX_VALUE;
protected <TTransport extends LocationTransport & ProviderTransport> LocationRegistration(
- LocationRequest request, CallerIdentity identity, TTransport transport,
+ LocationRequest request,
+ CallerIdentity identity,
+ Executor executor,
+ TTransport transport,
@PermissionLevel int permissionLevel) {
- super(request, identity, transport, permissionLevel);
+ super(request, identity, executor, transport, permissionLevel);
mProviderTransport = transport;
mWakeLock = Objects.requireNonNull(mContext.getSystemService(PowerManager.class))
.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKELOCK_TAG);
@@ -789,9 +758,13 @@
mProviderTransport = null;
}
- @GuardedBy("mLock")
+ // TODO: remove suppression when GuardedBy analysis can recognize lock from super class
+ @SuppressWarnings("GuardedBy")
+ @GuardedBy("mMultiplexerLock")
@Override
- protected final void onProviderListenerRegister() {
+ protected void onRegister() {
+ super.onRegister();
+
long registerTimeMs = SystemClock.elapsedRealtime();
mExpirationRealtimeMs = getRequest().getExpirationRealtimeMs(registerTimeMs);
@@ -810,8 +783,6 @@
// start listening for provider enabled/disabled events
addEnabledListener(this);
- onLocationListenerRegister();
-
// if the provider is currently disabled, let the client know immediately
int userId = getIdentity().getUserId();
if (!isEnabled(userId)) {
@@ -819,9 +790,11 @@
}
}
- @GuardedBy("mLock")
+ // TODO: remove suppression when GuardedBy analysis can recognize lock from super class
+ @SuppressWarnings("GuardedBy")
+ @GuardedBy("mMultiplexerLock")
@Override
- protected final void onProviderListenerUnregister() {
+ protected void onUnregister() {
// stop listening for provider enabled/disabled events
removeEnabledListener(this);
@@ -830,24 +803,16 @@
mAlarmHelper.cancel(this);
}
- onLocationListenerUnregister();
+ super.onUnregister();
}
- /**
- * Subclasses may override this instead of {@link #onRemovableListenerRegister()}.
- */
- @GuardedBy("mLock")
- protected void onLocationListenerRegister() {}
-
- /**
- * Subclasses may override this instead of {@link #onRemovableListenerUnregister()}.
- */
- @GuardedBy("mLock")
- protected void onLocationListenerUnregister() {}
-
- @GuardedBy("mLock")
+ // TODO: remove suppression when GuardedBy analysis can recognize lock from super class
+ @SuppressWarnings("GuardedBy")
+ @GuardedBy("mMultiplexerLock")
@Override
- protected final void onProviderListenerActive() {
+ protected void onActive() {
+ super.onActive();
+
// a new registration may not get a location immediately, the provider request may be
// delayed. therefore we deliver a historical location if available. since delivering an
// older location could be considered a breaking change for some applications, we only
@@ -883,21 +848,17 @@
+ " expired at " + TimeUtils.formatRealtime(mExpirationRealtimeMs));
}
- synchronized (mLock) {
+ synchronized (mMultiplexerLock) {
// no need to remove alarm after it's fired
mExpirationRealtimeMs = Long.MAX_VALUE;
remove();
}
}
- @GuardedBy("mLock")
+ @GuardedBy("mMultiplexerLock")
@Override
@Nullable ListenerOperation<LocationTransport> acceptLocationChange(
LocationResult fineLocationResult) {
- if (Build.IS_DEBUGGABLE) {
- Preconditions.checkState(Thread.holdsLock(mLock));
- }
-
// check expiration time - alarm is not guaranteed to go off at the right time,
// especially for short intervals
if (SystemClock.elapsedRealtime() >= mExpirationRealtimeMs) {
@@ -1017,9 +978,7 @@
+ " finished after " + mNumLocationsDelivered + " updates");
}
- synchronized (mLock) {
- remove();
- }
+ remove();
}
}
}
@@ -1049,12 +1008,18 @@
LocationListenerRegistration(LocationRequest request, CallerIdentity identity,
LocationListenerTransport transport, @PermissionLevel int permissionLevel) {
- super(request, identity, transport, permissionLevel);
+ super(request, identity,
+ identity.isMyProcess() ? FgThread.getExecutor() : DIRECT_EXECUTOR, transport,
+ permissionLevel);
}
- @GuardedBy("mLock")
+ // TODO: remove suppression when GuardedBy analysis can recognize lock from super class
+ @SuppressWarnings("GuardedBy")
+ @GuardedBy("mMultiplexerLock")
@Override
- protected void onLocationListenerRegister() {
+ protected void onRegister() {
+ super.onRegister();
+
try {
((IBinder) getKey()).linkToDeath(this, 0);
} catch (RemoteException e) {
@@ -1062,10 +1027,22 @@
}
}
- @GuardedBy("mLock")
+ // TODO: remove suppression when GuardedBy analysis can recognize lock from super class
+ @SuppressWarnings("GuardedBy")
+ @GuardedBy("mMultiplexerLock")
@Override
- protected void onLocationListenerUnregister() {
- ((IBinder) getKey()).unlinkToDeath(this, 0);
+ protected void onUnregister() {
+ try {
+ ((IBinder) getKey()).unlinkToDeath(this, 0);
+ } catch (NoSuchElementException e) {
+ // the only way this exception can occur should be if another exception has been
+ // thrown prior to registration completing, and that exception is currently
+ // unwinding the call stack and causing this cleanup. since that exception should
+ // crash us anyways, drop this exception so we're not hiding the original exception.
+ Log.w(getTag(), "failed to unregister binder death listener", e);
+ }
+
+ super.onUnregister();
}
@Override
@@ -1083,9 +1060,7 @@
private void onTransportFailure(Exception e) {
if (e instanceof RemoteException) {
Log.w(TAG, mName + " provider registration " + getIdentity() + " removed", e);
- synchronized (mLock) {
- remove();
- }
+ remove();
} else {
throw new AssertionError(e);
}
@@ -1098,9 +1073,7 @@
Log.d(TAG, mName + " provider registration " + getIdentity() + " died");
}
- synchronized (mLock) {
- remove();
- }
+ remove();
} catch (RuntimeException e) {
// the caller may swallow runtime exceptions, so we rethrow as assertion errors to
// ensure the crash is seen
@@ -1115,21 +1088,27 @@
LocationPendingIntentRegistration(LocationRequest request,
CallerIdentity identity, LocationPendingIntentTransport transport,
@PermissionLevel int permissionLevel) {
- super(request, identity, transport, permissionLevel);
+ super(request, identity, DIRECT_EXECUTOR, transport, permissionLevel);
}
- @GuardedBy("mLock")
+ // TODO: remove suppression when GuardedBy analysis can recognize lock from super class
+ @SuppressWarnings("GuardedBy")
+ @GuardedBy("mMultiplexerLock")
@Override
- protected void onLocationListenerRegister() {
+ protected void onRegister() {
+ super.onRegister();
if (!((PendingIntent) getKey()).addCancelListener(DIRECT_EXECUTOR, this)) {
remove();
}
}
- @GuardedBy("mLock")
+ // TODO: remove suppression when GuardedBy analysis can recognize lock from super class
+ @SuppressWarnings("GuardedBy")
+ @GuardedBy("mMultiplexerLock")
@Override
- protected void onLocationListenerUnregister() {
+ protected void onUnregister() {
((PendingIntent) getKey()).removeCancelListener(this);
+ super.onUnregister();
}
@Override
@@ -1147,9 +1126,7 @@
private void onTransportFailure(Exception e) {
if (e instanceof PendingIntent.CanceledException) {
Log.w(TAG, mName + " provider registration " + getIdentity() + " removed", e);
- synchronized (mLock) {
- remove();
- }
+ remove();
} else {
throw new AssertionError(e);
}
@@ -1161,25 +1138,32 @@
Log.d(TAG, mName + " provider registration " + getIdentity() + " canceled");
}
- synchronized (mLock) {
- remove();
- }
+ remove();
}
}
protected final class GetCurrentLocationListenerRegistration extends Registration implements
IBinder.DeathRecipient, OnAlarmListener {
+ @GuardedBy("mMultiplexerLock")
private long mExpirationRealtimeMs = Long.MAX_VALUE;
GetCurrentLocationListenerRegistration(LocationRequest request,
CallerIdentity identity, LocationTransport transport, int permissionLevel) {
- super(request, identity, transport, permissionLevel);
+ super(request,
+ identity,
+ identity.isMyProcess() ? FgThread.getExecutor() : DIRECT_EXECUTOR,
+ transport,
+ permissionLevel);
}
- @GuardedBy("mLock")
+ // TODO: remove suppression when GuardedBy analysis can recognize lock from super class
+ @SuppressWarnings("GuardedBy")
+ @GuardedBy("mMultiplexerLock")
@Override
- protected void onProviderListenerRegister() {
+ protected void onRegister() {
+ super.onRegister();
+
try {
((IBinder) getKey()).linkToDeath(this, 0);
} catch (RemoteException e) {
@@ -1202,20 +1186,36 @@
}
}
- @GuardedBy("mLock")
+ // TODO: remove suppression when GuardedBy analysis can recognize lock from super class
+ @SuppressWarnings("GuardedBy")
+ @GuardedBy("mMultiplexerLock")
@Override
- protected void onProviderListenerUnregister() {
+ protected void onUnregister() {
// remove alarm for expiration
if (mExpirationRealtimeMs < Long.MAX_VALUE) {
mAlarmHelper.cancel(this);
}
- ((IBinder) getKey()).unlinkToDeath(this, 0);
+ try {
+ ((IBinder) getKey()).unlinkToDeath(this, 0);
+ } catch (NoSuchElementException e) {
+ // the only way this exception can occur should be if another exception has been
+ // thrown prior to registration completing, and that exception is currently
+ // unwinding the call stack and causing this cleanup. since that exception should
+ // crash us anyways, drop this exception so we're not hiding the original exception.
+ Log.w(getTag(), "failed to unregister binder death listener", e);
+ }
+
+ super.onUnregister();
}
- @GuardedBy("mLock")
+ // TODO: remove suppression when GuardedBy analysis can recognize lock from super class
+ @SuppressWarnings("GuardedBy")
+ @GuardedBy("mMultiplexerLock")
@Override
- protected void onProviderListenerActive() {
+ protected void onActive() {
+ super.onActive();
+
Location lastLocation = getLastLocationUnsafe(
getIdentity().getUserId(),
getPermissionLevel(),
@@ -1226,17 +1226,19 @@
}
}
- @GuardedBy("mLock")
+ // TODO: remove suppression when GuardedBy analysis can recognize lock from super class
+ @SuppressWarnings("GuardedBy")
+ @GuardedBy("mMultiplexerLock")
@Override
- protected void onProviderListenerInactive() {
+ protected void onInactive() {
// if we go inactive for any reason, fail immediately
executeOperation(acceptLocationChange(null));
+ super.onInactive();
}
+ @GuardedBy("mMultiplexerLock")
void deliverNull() {
- synchronized (mLock) {
- executeOperation(acceptLocationChange(null));
- }
+ executeOperation(acceptLocationChange(null));
}
@Override
@@ -1246,21 +1248,17 @@
+ " expired at " + TimeUtils.formatRealtime(mExpirationRealtimeMs));
}
- synchronized (mLock) {
+ synchronized (mMultiplexerLock) {
// no need to remove alarm after it's fired
mExpirationRealtimeMs = Long.MAX_VALUE;
executeOperation(acceptLocationChange(null));
}
}
- @GuardedBy("mLock")
+ @GuardedBy("mMultiplexerLock")
@Override
@Nullable ListenerOperation<LocationTransport> acceptLocationChange(
@Nullable LocationResult fineLocationResult) {
- if (Build.IS_DEBUGGABLE) {
- Preconditions.checkState(Thread.holdsLock(mLock));
- }
-
// check expiration time - alarm is not guaranteed to go off at the right time,
// especially for short intervals
if (SystemClock.elapsedRealtime() >= mExpirationRealtimeMs) {
@@ -1311,9 +1309,7 @@
// on failure we're automatically removed anyways, no need to attempt removal
// again
if (success) {
- synchronized (mLock) {
- remove();
- }
+ remove();
}
}
};
@@ -1324,9 +1320,7 @@
Exception e) {
if (e instanceof RemoteException) {
Log.w(TAG, mName + " provider registration " + getIdentity() + " removed", e);
- synchronized (mLock) {
- remove();
- }
+ remove();
} else {
throw new AssertionError(e);
}
@@ -1339,9 +1333,7 @@
Log.d(TAG, mName + " provider registration " + getIdentity() + " died");
}
- synchronized (mLock) {
- remove();
- }
+ remove();
} catch (RuntimeException e) {
// the caller may swallow runtime exceptions, so we rethrow as assertion errors to
// ensure the crash is seen
@@ -1350,23 +1342,21 @@
}
}
- protected final Object mLock = new Object();
-
protected final String mName;
- private final @Nullable PassiveLocationProviderManager mPassiveManager;
+ @Nullable private final PassiveLocationProviderManager mPassiveManager;
protected final Context mContext;
- @GuardedBy("mLock")
+ @GuardedBy("mMultiplexerLock")
private @State int mState;
// maps of user id to value
- @GuardedBy("mLock")
+ @GuardedBy("mMultiplexerLock")
private final SparseBooleanArray mEnabled; // null or not present means unknown
- @GuardedBy("mLock")
+ @GuardedBy("mMultiplexerLock")
private final SparseArray<LastLocation> mLastLocations;
- @GuardedBy("mLock")
+ @GuardedBy("mMultiplexerLock")
private final ArrayList<ProviderEnabledListener> mEnabledListeners;
private final CopyOnWriteArrayList<IProviderRequestListener> mProviderRequestListeners;
@@ -1418,14 +1408,14 @@
private final ScreenInteractiveChangedListener mScreenInteractiveChangedListener =
this::onScreenInteractiveChanged;
- // acquiring mLock makes operations on mProvider atomic, but is otherwise unnecessary
+ // acquiring mMultiplexerLock makes operations on mProvider atomic, but is otherwise unnecessary
protected final MockableLocationProvider mProvider;
- @GuardedBy("mLock")
- private @Nullable OnAlarmListener mDelayedRegister;
+ @GuardedBy("mMultiplexerLock")
+ @Nullable private OnAlarmListener mDelayedRegister;
- @GuardedBy("mLock")
- private @Nullable StateChangedListener mStateChangedListener;
+ @GuardedBy("mMultiplexerLock")
+ @Nullable private StateChangedListener mStateChangedListener;
public LocationProviderManager(Context context, Injector injector,
String name, @Nullable PassiveLocationProviderManager passiveManager) {
@@ -1453,19 +1443,14 @@
mLocationUsageLogger = injector.getLocationUsageLogger();
mLocationFudger = new LocationFudger(mSettingsHelper.getCoarseLocationAccuracyM());
- mProvider = new MockableLocationProvider(mLock);
+ mProvider = new MockableLocationProvider(mMultiplexerLock);
// set listener last, since this lets our reference escape
mProvider.getController().setListener(this);
}
- @Override
- public String getTag() {
- return TAG;
- }
-
public void startManager(@Nullable StateChangedListener listener) {
- synchronized (mLock) {
+ synchronized (mMultiplexerLock) {
Preconditions.checkState(mState == STATE_STOPPED);
mState = STATE_STARTED;
mStateChangedListener = listener;
@@ -1485,7 +1470,7 @@
}
public void stopManager() {
- synchronized (mLock) {
+ synchronized (mMultiplexerLock) {
Preconditions.checkState(mState == STATE_STARTED);
mState = STATE_STOPPING;
@@ -1522,11 +1507,11 @@
return mProvider.getState();
}
- public @Nullable CallerIdentity getProviderIdentity() {
+ @Nullable public CallerIdentity getProviderIdentity() {
return mProvider.getState().identity;
}
- public @Nullable ProviderProperties getProperties() {
+ @Nullable public ProviderProperties getProperties() {
return mProvider.getState().properties;
}
@@ -1543,7 +1528,7 @@
Preconditions.checkArgument(userId >= 0);
- synchronized (mLock) {
+ synchronized (mMultiplexerLock) {
int index = mEnabled.indexOfKey(userId);
if (index < 0) {
// this generally shouldn't occur, but might be possible due to race conditions
@@ -1558,14 +1543,14 @@
}
public void addEnabledListener(ProviderEnabledListener listener) {
- synchronized (mLock) {
+ synchronized (mMultiplexerLock) {
Preconditions.checkState(mState != STATE_STOPPED);
mEnabledListeners.add(listener);
}
}
public void removeEnabledListener(ProviderEnabledListener listener) {
- synchronized (mLock) {
+ synchronized (mMultiplexerLock) {
Preconditions.checkState(mState != STATE_STOPPED);
mEnabledListeners.remove(listener);
}
@@ -1582,7 +1567,7 @@
}
public void setRealProvider(@Nullable AbstractLocationProvider provider) {
- synchronized (mLock) {
+ synchronized (mMultiplexerLock) {
Preconditions.checkState(mState != STATE_STOPPED);
final long identity = Binder.clearCallingIdentity();
@@ -1595,7 +1580,7 @@
}
public void setMockProvider(@Nullable MockLocationProvider provider) {
- synchronized (mLock) {
+ synchronized (mMultiplexerLock) {
Preconditions.checkState(mState != STATE_STOPPED);
EVENT_LOG.logProviderMocked(mName, provider != null);
@@ -1622,7 +1607,7 @@
}
public void setMockProviderAllowed(boolean enabled) {
- synchronized (mLock) {
+ synchronized (mMultiplexerLock) {
if (!mProvider.isMock()) {
throw new IllegalArgumentException(mName + " provider is not a test provider");
}
@@ -1637,7 +1622,7 @@
}
public void setMockProviderLocation(Location location) {
- synchronized (mLock) {
+ synchronized (mMultiplexerLock) {
if (!mProvider.isMock()) {
throw new IllegalArgumentException(mName + " provider is not a test provider");
}
@@ -1659,7 +1644,7 @@
}
}
- public @Nullable Location getLastLocation(LastLocationRequest request,
+ @Nullable public Location getLastLocation(LastLocationRequest request,
CallerIdentity identity, @PermissionLevel int permissionLevel) {
request = calculateLastLocationRequest(request, identity);
@@ -1732,7 +1717,7 @@
* location, even if the permissionLevel is coarse. You are responsible for coarsening the
* location if necessary.
*/
- public @Nullable Location getLastLocationUnsafe(int userId,
+ @Nullable public Location getLastLocationUnsafe(int userId,
@PermissionLevel int permissionLevel, boolean isBypass,
long maximumAgeMs) {
if (userId == UserHandle.USER_ALL) {
@@ -1756,7 +1741,7 @@
Preconditions.checkArgument(userId >= 0);
Location location;
- synchronized (mLock) {
+ synchronized (mMultiplexerLock) {
Preconditions.checkState(mState != STATE_STOPPED);
LastLocation lastLocation = mLastLocations.get(userId);
if (lastLocation == null) {
@@ -1778,7 +1763,7 @@
}
public void injectLastLocation(Location location, int userId) {
- synchronized (mLock) {
+ synchronized (mMultiplexerLock) {
Preconditions.checkState(mState != STATE_STOPPED);
if (getLastLocationUnsafe(userId, PERMISSION_FINE, false, Long.MAX_VALUE) == null) {
setLastLocation(location, userId);
@@ -1800,7 +1785,7 @@
Preconditions.checkArgument(userId >= 0);
- synchronized (mLock) {
+ synchronized (mMultiplexerLock) {
LastLocation lastLocation = mLastLocations.get(userId);
if (lastLocation == null) {
lastLocation = new LastLocation();
@@ -1814,7 +1799,7 @@
}
}
- public @Nullable ICancellationSignal getCurrentLocation(LocationRequest request,
+ @Nullable public ICancellationSignal getCurrentLocation(LocationRequest request,
CallerIdentity identity, int permissionLevel, ILocationCallback callback) {
if (request.getDurationMillis() > MAX_GET_CURRENT_LOCATION_TIMEOUT_MS) {
request = new LocationRequest.Builder(request)
@@ -1829,7 +1814,7 @@
new GetCurrentLocationTransport(callback),
permissionLevel);
- synchronized (mLock) {
+ synchronized (mMultiplexerLock) {
Preconditions.checkState(mState != STATE_STOPPED);
final long ident = Binder.clearCallingIdentity();
try {
@@ -1849,9 +1834,7 @@
() -> {
final long ident = Binder.clearCallingIdentity();
try {
- synchronized (mLock) {
- removeRegistration(callback.asBinder(), registration);
- }
+ removeRegistration(callback.asBinder(), registration);
} catch (RuntimeException e) {
// since this is within a oneway binder transaction there is nowhere
// for exceptions to go - move onto another thread to crash system
@@ -1885,7 +1868,7 @@
new LocationListenerTransport(listener),
permissionLevel);
- synchronized (mLock) {
+ synchronized (mMultiplexerLock) {
Preconditions.checkState(mState != STATE_STOPPED);
final long ident = Binder.clearCallingIdentity();
try {
@@ -1904,7 +1887,7 @@
new LocationPendingIntentTransport(mContext, pendingIntent),
permissionLevel);
- synchronized (mLock) {
+ synchronized (mMultiplexerLock) {
Preconditions.checkState(mState != STATE_STOPPED);
final long identity = Binder.clearCallingIdentity();
try {
@@ -1916,42 +1899,38 @@
}
public void flush(ILocationListener listener, int requestCode) {
- synchronized (mLock) {
- final long identity = Binder.clearCallingIdentity();
- try {
- boolean flushed = updateRegistration(listener.asBinder(), registration -> {
- registration.flush(requestCode);
- return false;
- });
- if (!flushed) {
- throw new IllegalArgumentException("unregistered listener cannot be flushed");
- }
- } finally {
- Binder.restoreCallingIdentity(identity);
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ boolean flushed = updateRegistration(listener.asBinder(), registration -> {
+ registration.flush(requestCode);
+ return false;
+ });
+ if (!flushed) {
+ throw new IllegalArgumentException("unregistered listener cannot be flushed");
}
+ } finally {
+ Binder.restoreCallingIdentity(identity);
}
}
public void flush(PendingIntent pendingIntent, int requestCode) {
- synchronized (mLock) {
- final long identity = Binder.clearCallingIdentity();
- try {
- boolean flushed = updateRegistration(pendingIntent, registration -> {
- registration.flush(requestCode);
- return false;
- });
- if (!flushed) {
- throw new IllegalArgumentException(
- "unregistered pending intent cannot be flushed");
- }
- } finally {
- Binder.restoreCallingIdentity(identity);
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ boolean flushed = updateRegistration(pendingIntent, registration -> {
+ registration.flush(requestCode);
+ return false;
+ });
+ if (!flushed) {
+ throw new IllegalArgumentException(
+ "unregistered pending intent cannot be flushed");
}
+ } finally {
+ Binder.restoreCallingIdentity(identity);
}
}
public void unregisterLocationRequest(ILocationListener listener) {
- synchronized (mLock) {
+ synchronized (mMultiplexerLock) {
Preconditions.checkState(mState != STATE_STOPPED);
final long identity = Binder.clearCallingIdentity();
try {
@@ -1963,7 +1942,7 @@
}
public void unregisterLocationRequest(PendingIntent pendingIntent) {
- synchronized (mLock) {
+ synchronized (mMultiplexerLock) {
Preconditions.checkState(mState != STATE_STOPPED);
final long identity = Binder.clearCallingIdentity();
try {
@@ -1974,13 +1953,9 @@
}
}
- @GuardedBy("mLock")
+ @GuardedBy("mMultiplexerLock")
@Override
protected void onRegister() {
- if (Build.IS_DEBUGGABLE) {
- Preconditions.checkState(Thread.holdsLock(mLock));
- }
-
mSettingsHelper.addOnBackgroundThrottleIntervalChangedListener(
mBackgroundThrottleIntervalChangedListener);
mSettingsHelper.addOnBackgroundThrottlePackageWhitelistChangedListener(
@@ -1997,13 +1972,9 @@
mScreenInteractiveHelper.addListener(mScreenInteractiveChangedListener);
}
- @GuardedBy("mLock")
+ @GuardedBy("mMultiplexerLock")
@Override
protected void onUnregister() {
- if (Build.IS_DEBUGGABLE) {
- Preconditions.checkState(Thread.holdsLock(mLock));
- }
-
mSettingsHelper.removeOnBackgroundThrottleIntervalChangedListener(
mBackgroundThrottleIntervalChangedListener);
mSettingsHelper.removeOnBackgroundThrottlePackageWhitelistChangedListener(
@@ -2019,13 +1990,9 @@
mScreenInteractiveHelper.removeListener(mScreenInteractiveChangedListener);
}
- @GuardedBy("mLock")
+ @GuardedBy("mMultiplexerLock")
@Override
protected void onRegistrationAdded(Object key, Registration registration) {
- if (Build.IS_DEBUGGABLE) {
- Preconditions.checkState(Thread.holdsLock(mLock));
- }
-
mLocationUsageLogger.logLocationApiUsage(
LocationStatsEnums.USAGE_STARTED,
LocationStatsEnums.API_REQUEST_LOCATION_UPDATES,
@@ -2038,23 +2005,21 @@
null, registration.isForeground());
}
- @GuardedBy("mLock")
+ // TODO: remove suppression when GuardedBy analysis can recognize lock from super class
+ @SuppressWarnings("GuardedBy")
+ @GuardedBy("mMultiplexerLock")
@Override
- protected void onRegistrationReplaced(Object key, Registration oldRegistration,
- Registration newRegistration) {
+ protected void onRegistrationReplaced(Object oldKey, Registration oldRegistration,
+ Object newKey, Registration newRegistration) {
// by saving the last delivered location state we are able to potentially delay the
// resulting provider request longer and save additional power
newRegistration.setLastDeliveredLocation(oldRegistration.getLastDeliveredLocation());
- super.onRegistrationReplaced(key, oldRegistration, newRegistration);
+ super.onRegistrationReplaced(oldKey, oldRegistration, newKey, newRegistration);
}
- @GuardedBy("mLock")
+ @GuardedBy("mMultiplexerLock")
@Override
protected void onRegistrationRemoved(Object key, Registration registration) {
- if (Build.IS_DEBUGGABLE) {
- Preconditions.checkState(Thread.holdsLock(mLock));
- }
-
mLocationUsageLogger.logLocationApiUsage(
LocationStatsEnums.USAGE_ENDED,
LocationStatsEnums.API_REQUEST_LOCATION_UPDATES,
@@ -2067,21 +2032,17 @@
null, registration.isForeground());
}
- @GuardedBy("mLock")
+ @GuardedBy("mMultiplexerLock")
@Override
protected boolean registerWithService(ProviderRequest request,
Collection<Registration> registrations) {
return reregisterWithService(ProviderRequest.EMPTY_REQUEST, request, registrations);
}
- @GuardedBy("mLock")
+ @GuardedBy("mMultiplexerLock")
@Override
protected boolean reregisterWithService(ProviderRequest oldRequest,
ProviderRequest newRequest, Collection<Registration> registrations) {
- if (Build.IS_DEBUGGABLE) {
- Preconditions.checkState(Thread.holdsLock(mLock));
- }
-
// calculate how long the new request should be delayed before sending it off to the
// provider, under the assumption that once we send the request off, the provider will
// immediately attempt to deliver a new location satisfying that request.
@@ -2117,7 +2078,7 @@
mDelayedRegister = new OnAlarmListener() {
@Override
public void onAlarm() {
- synchronized (mLock) {
+ synchronized (mMultiplexerLock) {
if (mDelayedRegister == this) {
mDelayedRegister = null;
setProviderRequest(newRequest);
@@ -2135,17 +2096,13 @@
return true;
}
- @GuardedBy("mLock")
+ @GuardedBy("mMultiplexerLock")
@Override
protected void unregisterWithService() {
- if (Build.IS_DEBUGGABLE) {
- Preconditions.checkState(Thread.holdsLock(mLock));
- }
-
setProviderRequest(ProviderRequest.EMPTY_REQUEST);
}
- @GuardedBy("mLock")
+ @GuardedBy("mMultiplexerLock")
void setProviderRequest(ProviderRequest request) {
if (mDelayedRegister != null) {
mAlarmHelper.cancel(mDelayedRegister);
@@ -2166,13 +2123,9 @@
});
}
- @GuardedBy("mLock")
+ @GuardedBy("mMultiplexerLock")
@Override
protected boolean isActive(Registration registration) {
- if (Build.IS_DEBUGGABLE) {
- Preconditions.checkState(Thread.holdsLock(mLock));
- }
-
if (!registration.isPermitted()) {
return false;
}
@@ -2236,13 +2189,9 @@
return true;
}
- @GuardedBy("mLock")
+ @GuardedBy("mMultiplexerLock")
@Override
protected ProviderRequest mergeRegistrations(Collection<Registration> registrations) {
- if (Build.IS_DEBUGGABLE) {
- Preconditions.checkState(Thread.holdsLock(mLock));
- }
-
long intervalMs = ProviderRequest.INTERVAL_DISABLED;
int quality = LocationRequest.QUALITY_LOW_POWER;
long maxUpdateDelayMs = Long.MAX_VALUE;
@@ -2307,7 +2256,7 @@
.build();
}
- @GuardedBy("mLock")
+ @GuardedBy("mMultiplexerLock")
protected long calculateRequestDelayMillis(long newIntervalMs,
Collection<Registration> registrations) {
// calculate the minimum delay across all registrations, ensuring that it is not more than
@@ -2349,7 +2298,7 @@
}
private void onUserChanged(int userId, int change) {
- synchronized (mLock) {
+ synchronized (mMultiplexerLock) {
if (mState == STATE_STOPPED) {
return;
}
@@ -2372,15 +2321,13 @@
private void onLocationUserSettingsChanged(int userId, LocationUserSettings oldSettings,
LocationUserSettings newSettings) {
if (oldSettings.isAdasGnssLocationEnabled() != newSettings.isAdasGnssLocationEnabled()) {
- synchronized (mLock) {
- updateRegistrations(
- registration -> registration.onAdasGnssLocationEnabledChanged(userId));
- }
+ updateRegistrations(
+ registration -> registration.onAdasGnssLocationEnabledChanged(userId));
}
}
private void onLocationEnabledChanged(int userId) {
- synchronized (mLock) {
+ synchronized (mMultiplexerLock) {
if (mState == STATE_STOPPED) {
return;
}
@@ -2390,88 +2337,64 @@
}
private void onScreenInteractiveChanged(boolean screenInteractive) {
- synchronized (mLock) {
- switch (mLocationPowerSaveModeHelper.getLocationPowerSaveMode()) {
- case LOCATION_MODE_GPS_DISABLED_WHEN_SCREEN_OFF:
- if (!GPS_PROVIDER.equals(mName)) {
- break;
- }
- // fall through
- case LOCATION_MODE_THROTTLE_REQUESTS_WHEN_SCREEN_OFF:
- // fall through
- case LOCATION_MODE_ALL_DISABLED_WHEN_SCREEN_OFF:
- updateRegistrations(registration -> true);
+ switch (mLocationPowerSaveModeHelper.getLocationPowerSaveMode()) {
+ case LOCATION_MODE_GPS_DISABLED_WHEN_SCREEN_OFF:
+ if (!GPS_PROVIDER.equals(mName)) {
break;
- default:
- break;
- }
+ }
+ // fall through
+ case LOCATION_MODE_THROTTLE_REQUESTS_WHEN_SCREEN_OFF:
+ // fall through
+ case LOCATION_MODE_ALL_DISABLED_WHEN_SCREEN_OFF:
+ updateRegistrations(registration -> true);
+ break;
+ default:
+ break;
}
}
private void onBackgroundThrottlePackageWhitelistChanged() {
- synchronized (mLock) {
- updateRegistrations(Registration::onProviderLocationRequestChanged);
- }
+ updateRegistrations(Registration::onProviderLocationRequestChanged);
}
private void onBackgroundThrottleIntervalChanged() {
- synchronized (mLock) {
- updateRegistrations(Registration::onProviderLocationRequestChanged);
- }
+ updateRegistrations(Registration::onProviderLocationRequestChanged);
}
private void onLocationPowerSaveModeChanged(@LocationPowerSaveMode int locationPowerSaveMode) {
- synchronized (mLock) {
- // this is rare, just assume everything has changed to keep it simple
- updateRegistrations(registration -> true);
- }
+ // this is rare, just assume everything has changed to keep it simple
+ updateRegistrations(registration -> true);
}
private void onAppForegroundChanged(int uid, boolean foreground) {
- synchronized (mLock) {
- updateRegistrations(registration -> registration.onForegroundChanged(uid, foreground));
- }
+ updateRegistrations(registration -> registration.onForegroundChanged(uid, foreground));
}
private void onAdasAllowlistChanged() {
- synchronized (mLock) {
- updateRegistrations(Registration::onProviderLocationRequestChanged);
- }
+ updateRegistrations(Registration::onProviderLocationRequestChanged);
}
private void onIgnoreSettingsWhitelistChanged() {
- synchronized (mLock) {
- updateRegistrations(Registration::onProviderLocationRequestChanged);
- }
+ updateRegistrations(Registration::onProviderLocationRequestChanged);
}
private void onLocationPackageBlacklistChanged(int userId) {
- synchronized (mLock) {
- updateRegistrations(registration -> registration.getIdentity().getUserId() == userId);
- }
+ updateRegistrations(registration -> registration.getIdentity().getUserId() == userId);
}
private void onLocationPermissionsChanged(@Nullable String packageName) {
- synchronized (mLock) {
- updateRegistrations(
- registration -> registration.onLocationPermissionsChanged(packageName));
- }
+ updateRegistrations(
+ registration -> registration.onLocationPermissionsChanged(packageName));
}
private void onLocationPermissionsChanged(int uid) {
- synchronized (mLock) {
- updateRegistrations(registration -> registration.onLocationPermissionsChanged(uid));
- }
+ updateRegistrations(registration -> registration.onLocationPermissionsChanged(uid));
}
- @GuardedBy("mLock")
+ @GuardedBy("mMultiplexerLock")
@Override
public void onStateChanged(
AbstractLocationProvider.State oldState, AbstractLocationProvider.State newState) {
- if (Build.IS_DEBUGGABLE) {
- Preconditions.checkState(Thread.holdsLock(mLock));
- }
-
if (oldState.allowed != newState.allowed) {
onEnabledChanged(UserHandle.USER_ALL);
}
@@ -2487,13 +2410,9 @@
}
}
- @GuardedBy("mLock")
+ @GuardedBy("mMultiplexerLock")
@Override
public void onReportLocation(LocationResult locationResult) {
- if (Build.IS_DEBUGGABLE) {
- Preconditions.checkState(Thread.holdsLock(mLock));
- }
-
LocationResult filtered;
if (mPassiveManager != null) {
filtered = locationResult.filter(location -> {
@@ -2549,12 +2468,8 @@
}
}
- @GuardedBy("mLock")
+ @GuardedBy("mMultiplexerLock")
private void onUserStarted(int userId) {
- if (Build.IS_DEBUGGABLE) {
- Preconditions.checkState(Thread.holdsLock(mLock));
- }
-
if (userId == UserHandle.USER_NULL) {
return;
}
@@ -2572,12 +2487,8 @@
}
}
- @GuardedBy("mLock")
+ @GuardedBy("mMultiplexerLock")
private void onUserStopped(int userId) {
- if (Build.IS_DEBUGGABLE) {
- Preconditions.checkState(Thread.holdsLock(mLock));
- }
-
if (userId == UserHandle.USER_NULL) {
return;
}
@@ -2592,12 +2503,8 @@
}
}
- @GuardedBy("mLock")
+ @GuardedBy("mMultiplexerLock")
private void onEnabledChanged(int userId) {
- if (Build.IS_DEBUGGABLE) {
- Preconditions.checkState(Thread.holdsLock(mLock));
- }
-
if (userId == UserHandle.USER_NULL) {
// used during initialization - ignore since many lower level operations (checking
// settings for instance) do not support the null user
@@ -2697,7 +2604,7 @@
}
public void dump(FileDescriptor fd, IndentingPrintWriter ipw, String[] args) {
- synchronized (mLock) {
+ synchronized (mMultiplexerLock) {
ipw.print(mName);
ipw.print(" provider");
if (mProvider.isMock()) {
@@ -2738,10 +2645,10 @@
private static class LastLocation {
- private @Nullable Location mFineLocation;
- private @Nullable Location mCoarseLocation;
- private @Nullable Location mFineBypassLocation;
- private @Nullable Location mCoarseBypassLocation;
+ @Nullable private Location mFineLocation;
+ @Nullable private Location mCoarseLocation;
+ @Nullable private Location mFineBypassLocation;
+ @Nullable private Location mCoarseBypassLocation;
LastLocation() {}
@@ -2765,7 +2672,7 @@
mCoarseLocation = null;
}
- public @Nullable Location get(@PermissionLevel int permissionLevel,
+ @Nullable public Location get(@PermissionLevel int permissionLevel,
boolean isBypass) {
switch (permissionLevel) {
case PERMISSION_FINE:
@@ -2862,7 +2769,7 @@
private static class GatedCallback implements Runnable {
@GuardedBy("this")
- private @Nullable Runnable mCallback;
+ @Nullable private Runnable mCallback;
@GuardedBy("this")
private boolean mGate;
diff --git a/services/core/java/com/android/server/location/provider/PassiveLocationProviderManager.java b/services/core/java/com/android/server/location/provider/PassiveLocationProviderManager.java
index b35af4f..0cb4f9e 100644
--- a/services/core/java/com/android/server/location/provider/PassiveLocationProviderManager.java
+++ b/services/core/java/com/android/server/location/provider/PassiveLocationProviderManager.java
@@ -54,7 +54,7 @@
* Reports a new location to passive location provider clients.
*/
public void updateLocation(LocationResult locationResult) {
- synchronized (mLock) {
+ synchronized (mMultiplexerLock) {
PassiveLocationProvider passive = (PassiveLocationProvider) mProvider.getProvider();
Preconditions.checkState(passive != null);
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index c5f73625..8ab3a94 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -1587,7 +1587,7 @@
if (!savedCredential.isNone()) {
throw new IllegalStateException("Saved credential given, but user has no SP");
}
- initializeSyntheticPasswordLocked(savedCredential, userId);
+ initializeSyntheticPasswordLocked(userId);
} else if (savedCredential.isNone() && isProfileWithUnifiedLock(userId)) {
// get credential from keystore when profile has unified lock
try {
@@ -2513,35 +2513,21 @@
}
/**
- * Creates the synthetic password (SP) for the given user and protects it with the user's LSKF.
+ * Creates the synthetic password (SP) for the given user and protects it with an empty LSKF.
* This is called just once in the lifetime of the user: the first time a nonempty LSKF is set,
* or when an escrow token is activated on a device with an empty LSKF.
- *
- * Maintains the SP invariants described in {@link SyntheticPasswordManager}.
*/
@GuardedBy("mSpManager")
@VisibleForTesting
- SyntheticPassword initializeSyntheticPasswordLocked(LockscreenCredential credential,
- int userId) {
+ SyntheticPassword initializeSyntheticPasswordLocked(int userId) {
Slog.i(TAG, "Initialize SyntheticPassword for user: " + userId);
Preconditions.checkState(getCurrentLskfBasedProtectorId(userId) ==
SyntheticPasswordManager.NULL_PROTECTOR_ID,
"Cannot reinitialize SP");
final SyntheticPassword sp = mSpManager.newSyntheticPassword(userId);
- long protectorId = mSpManager.createLskfBasedProtector(getGateKeeperService(), credential,
- sp, userId);
- if (!credential.isNone()) {
- mSpManager.newSidForUser(getGateKeeperService(), sp, userId);
- mSpManager.verifyChallenge(getGateKeeperService(), sp, 0L, userId);
- setUserKeyProtection(userId, sp.deriveFileBasedEncryptionKey());
- setKeystorePassword(sp.deriveKeyStorePassword(), userId);
- } else {
- clearUserKeyProtection(userId, null);
- setKeystorePassword(null, userId);
- gateKeeperClearSecureUserId(userId);
- }
- fixateNewestUserKeyAuth(userId);
+ final long protectorId = mSpManager.createLskfBasedProtector(getGateKeeperService(),
+ LockscreenCredential.createNone(), sp, userId);
setCurrentLskfBasedProtectorId(protectorId, userId);
onSyntheticPasswordKnown(userId, sp);
return sp;
@@ -2818,8 +2804,7 @@
if (!isUserSecure(userId)) {
long protectorId = getCurrentLskfBasedProtectorId(userId);
if (protectorId == SyntheticPasswordManager.NULL_PROTECTOR_ID) {
- sp = initializeSyntheticPasswordLocked(LockscreenCredential.createNone(),
- userId);
+ sp = initializeSyntheticPasswordLocked(userId);
} else {
sp = mSpManager.unlockLskfBasedProtector(getGateKeeperService(), protectorId,
LockscreenCredential.createNone(), userId, null).syntheticPassword;
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 1e86d02..b97aa5f 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -7833,7 +7833,8 @@
&& (record.getSuppressedVisualEffects() & SUPPRESSED_EFFECT_STATUS_BAR) != 0;
if (!record.isUpdate
&& record.getImportance() > IMPORTANCE_MIN
- && !suppressedByDnd) {
+ && !suppressedByDnd
+ && isNotificationForCurrentUser(record)) {
sendAccessibilityEvent(record);
sentAccessibilityEvent = true;
}
diff --git a/services/core/java/com/android/server/pm/AppsFilterBase.java b/services/core/java/com/android/server/pm/AppsFilterBase.java
index 8501c5e..5b3eff9 100644
--- a/services/core/java/com/android/server/pm/AppsFilterBase.java
+++ b/services/core/java/com/android/server/pm/AppsFilterBase.java
@@ -28,6 +28,7 @@
import android.os.Binder;
import android.os.Handler;
import android.os.Process;
+import android.os.SystemProperties;
import android.os.Trace;
import android.os.UserHandle;
import android.text.TextUtils;
@@ -340,7 +341,9 @@
return !isForceQueryable(targetPkgSetting.getAppId())
&& !isImplicitlyQueryable(callingAppId, targetPkgSetting.getAppId());
}
- if (mCacheReady) { // use cache
+ // use cache
+ if (mCacheReady && SystemProperties.getBoolean("debug.pm.use_app_filter_cache",
+ true)) {
if (!shouldFilterApplicationUsingCache(callingUid,
targetPkgSetting.getAppId(),
userId)) {
diff --git a/services/core/java/com/android/server/pm/UserManagerInternal.java b/services/core/java/com/android/server/pm/UserManagerInternal.java
index d358e43..5731af6 100644
--- a/services/core/java/com/android/server/pm/UserManagerInternal.java
+++ b/services/core/java/com/android/server/pm/UserManagerInternal.java
@@ -21,6 +21,7 @@
import android.annotation.UserIdInt;
import android.content.Context;
import android.content.pm.UserInfo;
+import android.content.pm.UserProperties;
import android.graphics.Bitmap;
import android.os.Bundle;
import android.os.UserManager;
@@ -306,6 +307,12 @@
*/
public abstract boolean shouldIgnorePrepareStorageErrors(int userId);
+ /**
+ * Returns the {@link UserProperties} of the given user, or {@code null} if it is not found.
+ * NB: The actual object is returned. So do NOT modify it!
+ */
+ public abstract @Nullable UserProperties getUserProperties(@UserIdInt int userId);
+
/** TODO(b/239982558): add javadoc / mention invalid_id is used to unassing */
public abstract void assignUserToDisplay(@UserIdInt int userId, int displayId);
}
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index f08bea3..025e973 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -52,6 +52,7 @@
import android.content.pm.ShortcutServiceInternal;
import android.content.pm.UserInfo;
import android.content.pm.UserInfo.UserInfoFlag;
+import android.content.pm.UserProperties;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Bitmap;
@@ -181,7 +182,7 @@
private static final String TAG_NAME = "name";
private static final String TAG_ACCOUNT = "account";
private static final String ATTR_FLAGS = "flags";
- private static final String ATTR_TYPE = "type";
+ private static final String ATTR_TYPE = "type"; // userType
private static final String ATTR_ICON_PATH = "icon";
private static final String ATTR_ID = "id";
private static final String ATTR_CREATION_TIME = "created";
@@ -216,6 +217,7 @@
private static final String TAG_ENTRY = "entry";
private static final String TAG_VALUE = "value";
private static final String TAG_SEED_ACCOUNT_OPTIONS = "seedAccountOptions";
+ private static final String TAG_USER_PROPERTIES = "userProperties";
private static final String TAG_LAST_REQUEST_QUIET_MODE_ENABLED_CALL =
"lastRequestQuietModeEnabledCall";
private static final String TAG_IGNORE_PREPARE_STORAGE_ERRORS =
@@ -260,7 +262,7 @@
@VisibleForTesting
static final int MAX_RECENTLY_REMOVED_IDS_SIZE = 100;
- private static final int USER_VERSION = 9;
+ private static final int USER_VERSION = 10;
private static final long EPOCH_PLUS_30_YEARS = 30L * 365 * 24 * 60 * 60 * 1000L; // ms
@@ -326,6 +328,9 @@
// Whether to perist the seed account information to be available after a boot
boolean persistSeedData;
+ /** Properties of the user whose default values originate from its user type. */
+ UserProperties userProperties;
+
/** Elapsed realtime since boot when the user started. */
long startRealtime;
@@ -1498,6 +1503,36 @@
return userTypeDetails != null && userTypeDetails.isSystem();
}
+ /**
+ * Returns a *copy* of the given user's UserProperties, stripping out any information for which
+ * the caller lacks permission.
+ */
+ @Override
+ public @NonNull UserProperties getUserPropertiesCopy(@UserIdInt int userId) {
+ checkQueryOrInteractPermissionIfCallerInOtherProfileGroup(userId, "getUserProperties");
+ final UserProperties origProperties = getUserPropertiesInternal(userId);
+ if (origProperties != null) {
+ int callingUid = Binder.getCallingUid();
+ boolean exposeAllFields = callingUid == Process.SYSTEM_UID;
+ boolean hasManage = hasPermissionGranted(Manifest.permission.MANAGE_USERS, callingUid);
+ boolean hasQuery = hasPermissionGranted(Manifest.permission.QUERY_USERS, callingUid);
+ return new UserProperties(origProperties, exposeAllFields, hasManage, hasQuery);
+ }
+ // A non-existent or partial user will reach here.
+ throw new IllegalArgumentException("Cannot access properties for user " + userId);
+ }
+
+ /** Returns the user's actual, canonical UserProperties object. Do not edit it externally. */
+ private @Nullable UserProperties getUserPropertiesInternal(@UserIdInt int userId) {
+ synchronized (mUsersLock) {
+ final UserData userData = getUserDataLU(userId);
+ if (userData != null) {
+ return userData.userProperties;
+ }
+ }
+ return null;
+ }
+
@Override
public boolean hasBadge(@UserIdInt int userId) {
checkManageOrInteractPermissionIfCallerInOtherProfileGroup(userId, "hasBadge");
@@ -1587,6 +1622,10 @@
public boolean isProfile(@UserIdInt int userId) {
checkQueryOrInteractPermissionIfCallerInOtherProfileGroup(userId, "isProfile");
+ return isProfileUnchecked(userId);
+ }
+
+ private boolean isProfileUnchecked(@UserIdInt int userId) {
synchronized (mUsersLock) {
UserInfo userInfo = getUserInfoLU(userId);
return userInfo != null && userInfo.isProfile();
@@ -3311,6 +3350,7 @@
@VisibleForTesting
void upgradeIfNecessaryLP(Bundle oldGlobalUserRestrictions, int userVersion,
int userTypeVersion) {
+ Slog.i(LOG_TAG, "Upgrading users from userVersion " + userVersion + " to " + USER_VERSION);
Set<Integer> userIdsToWrite = new ArraySet<>();
final int originalVersion = mUserVersion;
final int originalUserTypeVersion = mUserTypeVersion;
@@ -3446,6 +3486,27 @@
userVersion = 9;
}
+ if (userVersion < 10) {
+ // Add UserProperties.
+ synchronized (mUsersLock) {
+ for (int i = 0; i < mUsers.size(); i++) {
+ final UserData userData = mUsers.valueAt(i);
+ final UserTypeDetails userTypeDetails = mUserTypes.get(userData.info.userType);
+ if (userTypeDetails == null) {
+ throw new IllegalStateException(
+ "Cannot upgrade user because " + userData.info.userType
+ + " isn't defined on this device!");
+ }
+ userData.userProperties = new UserProperties(
+ userTypeDetails.getDefaultUserPropertiesReference());
+ userIdsToWrite.add(userData.info.id);
+ }
+ }
+ userVersion = 10;
+ }
+
+ // Reminder: If you add another upgrade, make sure to increment USER_VERSION too.
+
// Done with userVersion changes, moving on to deal with userTypeVersion upgrades
// Upgrade from previous user type to a new user type
final int newUserTypeVersion = UserTypeFactory.getUserTypeVersion();
@@ -3460,6 +3521,11 @@
Slog.w(LOG_TAG, "User version " + mUserVersion + " didn't upgrade as expected to "
+ USER_VERSION);
} else {
+ if (userVersion > USER_VERSION) {
+ Slog.wtf(LOG_TAG, "Upgraded user version " + mUserVersion + " is higher the SDK's "
+ + "one of " + USER_VERSION + ". Someone forgot to update USER_VERSION?");
+ }
+
mUserVersion = userVersion;
mUserTypeVersion = newUserTypeVersion;
@@ -3579,6 +3645,8 @@
flags |= mUserTypes.get(systemUserType).getDefaultUserInfoFlags();
UserInfo system = new UserInfo(UserHandle.USER_SYSTEM, null, null, flags, systemUserType);
UserData userData = putUserInfo(system);
+ userData.userProperties = new UserProperties(
+ mUserTypes.get(userData.info.userType).getDefaultUserPropertiesReference());
mNextSerialNumber = MIN_USER_ID;
mUserVersion = USER_VERSION;
mUserTypeVersion = UserTypeFactory.getUserTypeVersion();
@@ -3753,6 +3821,12 @@
serializer.endTag(null, TAG_SEED_ACCOUNT_OPTIONS);
}
+ if (userData.userProperties != null) {
+ serializer.startTag(null, TAG_USER_PROPERTIES);
+ userData.userProperties.writeToXml(serializer);
+ serializer.endTag(null, TAG_USER_PROPERTIES);
+ }
+
if (userData.getLastRequestQuietModeEnabledMillis() != 0L) {
serializer.startTag(/* namespace */ null, TAG_LAST_REQUEST_QUIET_MODE_ENABLED_CALL);
serializer.text(String.valueOf(userData.getLastRequestQuietModeEnabledMillis()));
@@ -3872,6 +3946,7 @@
String seedAccountName = null;
String seedAccountType = null;
PersistableBundle seedAccountOptions = null;
+ UserProperties userProperties = null;
Bundle baseRestrictions = null;
Bundle legacyLocalRestrictions = null;
RestrictionsSet localRestrictions = null;
@@ -3950,6 +4025,17 @@
} else if (TAG_SEED_ACCOUNT_OPTIONS.equals(tag)) {
seedAccountOptions = PersistableBundle.restoreFromXml(parser);
persistSeedData = true;
+ } else if (TAG_USER_PROPERTIES.equals(tag)) {
+ // We already got the userType above (if it exists), so we can use it.
+ // And it must exist, since ATTR_TYPE historically predates PROPERTIES.
+ final UserTypeDetails userTypeDetails = mUserTypes.get(userType);
+ if (userTypeDetails == null) {
+ Slog.e(LOG_TAG, "User has properties but no user type!");
+ return null;
+ }
+ final UserProperties defaultProps
+ = userTypeDetails.getDefaultUserPropertiesReference();
+ userProperties = new UserProperties(parser, defaultProps);
} else if (TAG_LAST_REQUEST_QUIET_MODE_ENABLED_CALL.equals(tag)) {
type = parser.next();
if (type == XmlPullParser.TEXT) {
@@ -3986,6 +4072,7 @@
userData.seedAccountType = seedAccountType;
userData.persistSeedData = persistSeedData;
userData.seedAccountOptions = seedAccountOptions;
+ userData.userProperties = userProperties;
userData.setLastRequestQuietModeEnabledMillis(lastRequestQuietModeEnabledTimestamp);
if (ignorePrepareStorageErrors) {
userData.setIgnorePrepareStorageErrors();
@@ -4321,6 +4408,8 @@
}
userData = new UserData();
userData.info = userInfo;
+ userData.userProperties = new UserProperties(
+ userTypeDetails.getDefaultUserPropertiesReference());
mUsers.put(userId, userData);
}
writeUserLP(userData);
@@ -5647,6 +5736,7 @@
}
// If we got here, we probably recycled user ids, so invalidate any caches.
UserManager.invalidateStaticUserProperties();
+ UserManager.invalidateUserPropertiesCache();
if (nextId < 0) {
throw new IllegalStateException("No user id available!");
}
@@ -6408,6 +6498,10 @@
}
}
+ if (userData.userProperties != null) {
+ userData.userProperties.println(pw, " ");
+ }
+
pw.println(" Ignore errors preparing storage: "
+ userData.getIgnorePrepareStorageErrors());
}
@@ -6796,6 +6890,15 @@
}
@Override
+ public @Nullable UserProperties getUserProperties(@UserIdInt int userId) {
+ final UserProperties props = getUserPropertiesInternal(userId);
+ if (props == null) {
+ Slog.w(LOG_TAG, "A null UserProperties was returned for user " + userId);
+ }
+ return props;
+ }
+
+ @Override
public void assignUserToDisplay(int userId, int displayId) {
if (DBG) {
Slogf.d(LOG_TAG, "assignUserToDisplay(%d, %d): mUsersOnSecondaryDisplays=%s",
@@ -6827,9 +6930,6 @@
return;
}
- // TODO(b/239982558) check for invalid cases like:
- // - userId already assigned to another display
- // - displayId already assigned to another user
synchronized (mUsersLock) {
if (mUsersOnSecondaryDisplays == null) {
if (DBG) {
@@ -6841,6 +6941,36 @@
Slogf.d(LOG_TAG, "Adding %d->%d to mUsersOnSecondaryDisplays",
userId, displayId);
}
+
+ if (isProfileUnchecked(userId)) {
+ // Profile can only start in the same display as parent
+ int parentUserId = getProfileParentId(userId);
+ int parentDisplayId = mUsersOnSecondaryDisplays.get(parentUserId);
+ if (displayId != parentDisplayId) {
+ throw new IllegalStateException("Cannot assign profile " + userId + " to "
+ + "display " + displayId + " as its parent (user " + parentUserId
+ + ") is assigned to display " + parentDisplayId);
+ }
+ } else {
+ // Check if display is available
+ for (int i = 0; i < mUsersOnSecondaryDisplays.size(); i++) {
+ // Make sure display is not used by other users...
+ // TODO(b/240736142); currently, if a user was started in a display, it
+ // would need to be stopped first, so "switching" a user on secondary
+ // diplay requires 2 non-atomic operations (stop and start). Once this logic
+ // is refactored, it should be atomic.
+ if (mUsersOnSecondaryDisplays.valueAt(i) == displayId) {
+ throw new IllegalStateException("Cannot assign " + userId + " to "
+ + "display " + displayId + " as it's already assigned to "
+ + "user " + mUsersOnSecondaryDisplays.keyAt(i));
+ }
+ // TODO(b/239982558) also check that user is not already assigned to other
+ // display (including 0). That would be harder to tested under CTS though
+ // (for example, would need to add a new AM method to start user in bg on
+ // main display), so it's better to test on unit tests
+ }
+ }
+
mUsersOnSecondaryDisplays.put(userId, displayId);
}
}
diff --git a/services/core/java/com/android/server/pm/UserTypeDetails.java b/services/core/java/com/android/server/pm/UserTypeDetails.java
index 2f3ca66..ebb9f98 100644
--- a/services/core/java/com/android/server/pm/UserTypeDetails.java
+++ b/services/core/java/com/android/server/pm/UserTypeDetails.java
@@ -23,6 +23,7 @@
import android.annotation.StringRes;
import android.content.pm.UserInfo;
import android.content.pm.UserInfo.UserInfoFlag;
+import android.content.pm.UserProperties;
import android.content.res.Resources;
import android.os.Bundle;
import android.os.UserManager;
@@ -171,6 +172,12 @@
private final @CrossProfileIntentFilter.AccessControlLevel int
mCrossProfileIntentFilterAccessControl;
+ /**
+ * The default {@link UserProperties} for the user type.
+ * <p> The uninitialized value of each property is implied by {@link UserProperties.Builder}.
+ */
+ private final @NonNull UserProperties mDefaultUserProperties;
+
private UserTypeDetails(@NonNull String name, boolean enabled, int maxAllowed,
@UserInfoFlag int baseType, @UserInfoFlag int defaultUserInfoPropertyFlags, int label,
int maxAllowedPerParent,
@@ -183,7 +190,8 @@
@Nullable List<DefaultCrossProfileIntentFilter> defaultCrossProfileIntentFilters,
boolean isMediaSharedWithParent,
boolean isCredentialSharableWithParent,
- @CrossProfileIntentFilter.AccessControlLevel int accessControlLevel) {
+ @CrossProfileIntentFilter.AccessControlLevel int accessControlLevel,
+ @NonNull UserProperties defaultUserProperties) {
this.mName = name;
this.mEnabled = enabled;
this.mMaxAllowed = maxAllowed;
@@ -205,6 +213,7 @@
this.mIsMediaSharedWithParent = isMediaSharedWithParent;
this.mIsCredentialSharableWithParent = isCredentialSharableWithParent;
this.mCrossProfileIntentFilterAccessControl = accessControlLevel;
+ this.mDefaultUserProperties = defaultUserProperties;
}
/**
@@ -310,18 +319,6 @@
return mDarkThemeBadgeColors[Math.min(badgeIndex, mDarkThemeBadgeColors.length - 1)];
}
- public boolean isProfile() {
- return (mBaseType & UserInfo.FLAG_PROFILE) != 0;
- }
-
- public boolean isFull() {
- return (mBaseType & UserInfo.FLAG_FULL) != 0;
- }
-
- public boolean isSystem() {
- return (mBaseType & UserInfo.FLAG_SYSTEM) != 0;
- }
-
/**
* Returns true if the user has shared media with parent user or false otherwise.
*/
@@ -347,6 +344,26 @@
return mCrossProfileIntentFilterAccessControl;
}
+ /**
+ * Returns the reference to the default {@link UserProperties} for this type of user.
+ * This is not a copy. Do NOT modify this object.
+ */
+ public @NonNull UserProperties getDefaultUserPropertiesReference() {
+ return mDefaultUserProperties;
+ }
+
+ public boolean isProfile() {
+ return (mBaseType & UserInfo.FLAG_PROFILE) != 0;
+ }
+
+ public boolean isFull() {
+ return (mBaseType & UserInfo.FLAG_FULL) != 0;
+ }
+
+ public boolean isSystem() {
+ return (mBaseType & UserInfo.FLAG_SYSTEM) != 0;
+ }
+
/** Returns a {@link Bundle} representing the default user restrictions. */
@NonNull Bundle getDefaultRestrictions() {
return BundleUtils.clone(mDefaultRestrictions);
@@ -384,6 +401,7 @@
pw.print(prefix); pw.print("mDefaultUserInfoFlags: ");
pw.println(UserInfo.flagsToString(mDefaultUserInfoPropertyFlags));
pw.print(prefix); pw.print("mLabel: "); pw.println(mLabel);
+ mDefaultUserProperties.println(pw, prefix);
final String restrictionsPrefix = prefix + " ";
if (isSystem()) {
@@ -442,6 +460,9 @@
private boolean mIsCredentialSharableWithParent = false;
private @CrossProfileIntentFilter.AccessControlLevel int
mCrossProfileIntentFilterAccessControl = CrossProfileIntentFilter.ACCESS_LEVEL_ALL;
+ // Default UserProperties cannot be null but for efficiency we don't initialize it now.
+ // If it isn't set explicitly, {@link UserProperties.Builder#build()} will be used.
+ private @Nullable UserProperties mDefaultUserProperties = null;
public Builder setName(String name) {
mName = name;
@@ -560,6 +581,23 @@
return this;
}
+ /**
+ * Sets (replacing if necessary) the default UserProperties object for this user type.
+ * Takes a builder, rather than a built object, to efficiently ensure that a fresh copy of
+ * properties is stored (since it later might be modified by UserProperties#updateFromXml).
+ */
+ public Builder setDefaultUserProperties(UserProperties.Builder userPropertiesBuilder) {
+ mDefaultUserProperties = userPropertiesBuilder.build();
+ return this;
+ }
+
+ public @NonNull UserProperties getDefaultUserProperties() {
+ if (mDefaultUserProperties == null) {
+ mDefaultUserProperties = new UserProperties.Builder().build();
+ }
+ return mDefaultUserProperties;
+ }
+
@UserInfoFlag int getBaseType() {
return mBaseType;
}
@@ -604,7 +642,8 @@
mDefaultCrossProfileIntentFilters,
mIsMediaSharedWithParent,
mIsCredentialSharableWithParent,
- mCrossProfileIntentFilterAccessControl);
+ mCrossProfileIntentFilterAccessControl,
+ getDefaultUserProperties());
}
private boolean hasBadge() {
diff --git a/services/core/java/com/android/server/pm/UserTypeFactory.java b/services/core/java/com/android/server/pm/UserTypeFactory.java
index 857a975..b98d20e 100644
--- a/services/core/java/com/android/server/pm/UserTypeFactory.java
+++ b/services/core/java/com/android/server/pm/UserTypeFactory.java
@@ -37,6 +37,7 @@
import static com.android.server.pm.UserTypeDetails.UNLIMITED_NUMBER_OF_USERS;
import android.content.pm.UserInfo;
+import android.content.pm.UserProperties;
import android.content.res.Resources;
import android.content.res.XmlResourceParser;
import android.os.Build;
@@ -124,7 +125,10 @@
.setIsMediaSharedWithParent(true)
.setCrossProfileIntentFilterAccessControl(
CrossProfileIntentFilter.ACCESS_LEVEL_SYSTEM)
- .setIsCredentialSharableWithParent(true);
+ .setIsCredentialSharableWithParent(true)
+ .setDefaultUserProperties(new UserProperties.Builder()
+ .setStartWithParent(true)
+ .setShowInLauncher(UserProperties.SHOW_IN_LAUNCHER_WITH_PARENT));
}
/**
@@ -156,7 +160,10 @@
.setDefaultRestrictions(getDefaultManagedProfileRestrictions())
.setDefaultSecureSettings(getDefaultManagedProfileSecureSettings())
.setDefaultCrossProfileIntentFilters(getDefaultManagedCrossProfileIntentFilter())
- .setIsCredentialSharableWithParent(true);
+ .setIsCredentialSharableWithParent(true)
+ .setDefaultUserProperties(new UserProperties.Builder()
+ .setStartWithParent(true)
+ .setShowInLauncher(UserProperties.SHOW_IN_LAUNCHER_SEPARATE));
}
/**
@@ -396,6 +403,9 @@
setResAttributeArray(parser, builder::setBadgeColors);
} else if (isProfile && "badge-colors-dark".equals(childName)) {
setResAttributeArray(parser, builder::setDarkThemeBadgeColors);
+ } else if ("user-properties".equals(childName)) {
+ builder.getDefaultUserProperties()
+ .updateFromXml(XmlUtils.makeTyped(parser));
} else {
Slog.w(LOG_TAG, "Unrecognized tag " + childName + " in "
+ parser.getPositionDescription());
diff --git a/services/core/java/com/android/server/power/stats/BatteryExternalStatsWorker.java b/services/core/java/com/android/server/power/stats/BatteryExternalStatsWorker.java
index fe4aa53..df902c2 100644
--- a/services/core/java/com/android/server/power/stats/BatteryExternalStatsWorker.java
+++ b/services/core/java/com/android/server/power/stats/BatteryExternalStatsWorker.java
@@ -657,7 +657,7 @@
// Now that we have finally received all the data, we can tell mStats about it.
synchronized (mStats) {
- mStats.addHistoryEventLocked(
+ mStats.recordHistoryEventLocked(
elapsedRealtime,
uptime,
BatteryStats.HistoryItem.EVENT_COLLECT_EXTERNAL_STATS,
diff --git a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
index 202beb3..37643c3 100644
--- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
+++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
@@ -108,6 +108,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.BackgroundThread;
import com.android.internal.os.BatteryStatsHistory;
+import com.android.internal.os.BatteryStatsHistory.HistoryStepDetailsCalculator;
import com.android.internal.os.BatteryStatsHistoryIterator;
import com.android.internal.os.BinderCallsStats;
import com.android.internal.os.BinderTransactionNameResolver;
@@ -173,7 +174,6 @@
private static final boolean DEBUG_ENERGY_CPU = DEBUG_ENERGY;
private static final boolean DEBUG_BINDER_STATS = false;
private static final boolean DEBUG_MEMORY = false;
- private static final boolean DEBUG_HISTORY = false;
// TODO: remove "tcp" from network methods, since we measure total stats.
@@ -322,6 +322,11 @@
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
protected Queue<UidToRemove> mPendingRemovedUids = new LinkedList<>();
+ @NonNull
+ BatteryStatsHistory copyHistory() {
+ return mHistory.copy();
+ }
+
@VisibleForTesting
public final class UidToRemove {
private final int mStartUid;
@@ -413,7 +418,7 @@
if (changed) {
final long uptimeMs = mClock.uptimeMillis();
final long elapsedRealtimeMs = mClock.elapsedRealtime();
- addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
+ mHistory.writeHistoryItem(elapsedRealtimeMs, uptimeMs);
}
}
}
@@ -668,16 +673,16 @@
/**
* Mapping isolated uids to the actual owning app uid.
*/
- final SparseIntArray mIsolatedUids = new SparseIntArray();
+ private final SparseIntArray mIsolatedUids = new SparseIntArray();
/**
* Internal reference count of isolated uids.
*/
- final SparseIntArray mIsolatedUidRefCounts = new SparseIntArray();
+ private final SparseIntArray mIsolatedUidRefCounts = new SparseIntArray();
/**
* The statistics we have collected organized by uids.
*/
- final SparseArray<BatteryStatsImpl.Uid> mUidStats = new SparseArray<>();
+ private final SparseArray<BatteryStatsImpl.Uid> mUidStats = new SparseArray<>();
// A set of pools of currently active timers. When a timer is queried, we will divide the
// elapsed time by the number of active timers to arrive at that timer's share of the time.
@@ -685,20 +690,21 @@
// changes.
@VisibleForTesting
protected ArrayList<StopwatchTimer> mPartialTimers = new ArrayList<>();
- final ArrayList<StopwatchTimer> mFullTimers = new ArrayList<>();
- final ArrayList<StopwatchTimer> mWindowTimers = new ArrayList<>();
- final ArrayList<StopwatchTimer> mDrawTimers = new ArrayList<>();
- final SparseArray<ArrayList<StopwatchTimer>> mSensorTimers = new SparseArray<>();
- final ArrayList<StopwatchTimer> mWifiRunningTimers = new ArrayList<>();
- final ArrayList<StopwatchTimer> mFullWifiLockTimers = new ArrayList<>();
- final ArrayList<StopwatchTimer> mWifiMulticastTimers = new ArrayList<>();
- final ArrayList<StopwatchTimer> mWifiScanTimers = new ArrayList<>();
- final SparseArray<ArrayList<StopwatchTimer>> mWifiBatchedScanTimers = new SparseArray<>();
- final ArrayList<StopwatchTimer> mAudioTurnedOnTimers = new ArrayList<>();
- final ArrayList<StopwatchTimer> mVideoTurnedOnTimers = new ArrayList<>();
- final ArrayList<StopwatchTimer> mFlashlightTurnedOnTimers = new ArrayList<>();
- final ArrayList<StopwatchTimer> mCameraTurnedOnTimers = new ArrayList<>();
- final ArrayList<StopwatchTimer> mBluetoothScanOnTimers = new ArrayList<>();
+ private final ArrayList<StopwatchTimer> mFullTimers = new ArrayList<>();
+ private final ArrayList<StopwatchTimer> mWindowTimers = new ArrayList<>();
+ private final ArrayList<StopwatchTimer> mDrawTimers = new ArrayList<>();
+ private final SparseArray<ArrayList<StopwatchTimer>> mSensorTimers = new SparseArray<>();
+ private final ArrayList<StopwatchTimer> mWifiRunningTimers = new ArrayList<>();
+ private final ArrayList<StopwatchTimer> mFullWifiLockTimers = new ArrayList<>();
+ private final ArrayList<StopwatchTimer> mWifiMulticastTimers = new ArrayList<>();
+ private final ArrayList<StopwatchTimer> mWifiScanTimers = new ArrayList<>();
+ private final SparseArray<ArrayList<StopwatchTimer>> mWifiBatchedScanTimers =
+ new SparseArray<>();
+ private final ArrayList<StopwatchTimer> mAudioTurnedOnTimers = new ArrayList<>();
+ private final ArrayList<StopwatchTimer> mVideoTurnedOnTimers = new ArrayList<>();
+ private final ArrayList<StopwatchTimer> mFlashlightTurnedOnTimers = new ArrayList<>();
+ private final ArrayList<StopwatchTimer> mCameraTurnedOnTimers = new ArrayList<>();
+ private final ArrayList<StopwatchTimer> mBluetoothScanOnTimers = new ArrayList<>();
// Last partial timers we use for distributing CPU usage.
@VisibleForTesting
@@ -713,69 +719,24 @@
protected final TimeBase mOnBatteryScreenOffTimeBase = new TimeBase(true);
private boolean mSystemReady;
- boolean mShuttingDown;
+ private boolean mShuttingDown;
- final HistoryEventTracker mActiveEvents = new HistoryEventTracker();
+ private final HistoryEventTracker mActiveEvents = new HistoryEventTracker();
+ private final HistoryStepDetailsCalculatorImpl mStepDetailsCalculator =
+ new HistoryStepDetailsCalculatorImpl();
- long mHistoryBaseTimeMs;
- protected boolean mHaveBatteryLevel = false;
- protected boolean mRecordingHistory = false;
- int mNumHistoryItems;
-
- private static final int HISTORY_TAG_INDEX_LIMIT = 0x7ffe;
- private static final int MAX_HISTORY_TAG_STRING_LENGTH = 1024;
-
- final HashMap<HistoryTag, Integer> mHistoryTagPool = new HashMap<>();
- private SparseArray<HistoryTag> mHistoryTags;
- final Parcel mHistoryBuffer = Parcel.obtain();
- final HistoryItem mHistoryLastWritten = new HistoryItem();
- final HistoryItem mHistoryLastLastWritten = new HistoryItem();
- final HistoryItem mHistoryAddTmp = new HistoryItem();
- int mNextHistoryTagIdx = 0;
- int mNumHistoryTagChars = 0;
- int mHistoryBufferLastPos = -1;
- int mActiveHistoryStates = 0xffffffff;
- int mActiveHistoryStates2 = 0xffffffff;
- long mLastHistoryElapsedRealtimeMs = 0;
- long mTrackRunningHistoryElapsedRealtimeMs = 0;
- long mTrackRunningHistoryUptimeMs = 0;
+ private boolean mHaveBatteryLevel = false;
+ private boolean mBatteryPluggedIn;
+ private int mBatteryStatus;
+ private int mBatteryLevel;
+ private int mBatteryPlugType;
+ private int mBatteryChargeUah;
+ private int mBatteryHealth;
+ private int mBatteryTemperature;
+ private int mBatteryVoltageMv = -1;
@NonNull
- final BatteryStatsHistory mBatteryStatsHistory;
-
- final HistoryItem mHistoryCur = new HistoryItem();
-
- // Used by computeHistoryStepDetails
- HistoryStepDetails mLastHistoryStepDetails = null;
- byte mLastHistoryStepLevel = 0;
- final HistoryStepDetails mCurHistoryStepDetails = new HistoryStepDetails();
- final HistoryStepDetails mTmpHistoryStepDetails = new HistoryStepDetails();
-
- /**
- * Total time (in milliseconds) spent executing in user code.
- */
- long mLastStepCpuUserTimeMs;
- long mCurStepCpuUserTimeMs;
- /**
- * Total time (in milliseconds) spent executing in kernel code.
- */
- long mLastStepCpuSystemTimeMs;
- long mCurStepCpuSystemTimeMs;
- /**
- * Times from /proc/stat (but measured in milliseconds).
- */
- long mLastStepStatUserTimeMs;
- long mLastStepStatSystemTimeMs;
- long mLastStepStatIOWaitTimeMs;
- long mLastStepStatIrqTimeMs;
- long mLastStepStatSoftIrqTimeMs;
- long mLastStepStatIdleTimeMs;
- long mCurStepStatUserTimeMs;
- long mCurStepStatSystemTimeMs;
- long mCurStepStatIOWaitTimeMs;
- long mCurStepStatIrqTimeMs;
- long mCurStepStatSoftIrqTimeMs;
- long mCurStepStatIdleTimeMs;
+ private final BatteryStatsHistory mHistory;
private BatteryStatsHistoryIterator mBatteryStatsHistoryIterator;
@@ -1391,7 +1352,6 @@
int mDischargeUnplugLevel;
int mDischargePlugLevel;
int mDischargeCurrentLevel;
- int mCurrentBatteryLevel;
int mLowDischargeAmountSinceCharge;
int mHighDischargeAmountSinceCharge;
int mDischargeScreenOnUnplugLevel;
@@ -1443,7 +1403,6 @@
private int mNumConnectivityChange;
- private int mBatteryVoltageMv = -1;
private int mEstimatedBatteryCapacityMah = -1;
private int mLastLearnedBatteryCapacityUah = -1;
@@ -1627,28 +1586,27 @@
}
public BatteryStatsImpl(Clock clock) {
- this(clock, (File) null);
+ this(clock, null);
}
public BatteryStatsImpl(Clock clock, File historyDirectory) {
init(clock);
+ mHandler = null;
+ mConstants = new Constants(mHandler);
mStartClockTimeMs = clock.currentTimeMillis();
mCheckinFile = null;
mDailyFile = null;
if (historyDirectory == null) {
mStatsFile = null;
- mBatteryStatsHistory = new BatteryStatsHistory(mHistoryBuffer);
+ mHistory = new BatteryStatsHistory(mStepDetailsCalculator, mClock);
} else {
mStatsFile = new AtomicFile(new File(historyDirectory, "batterystats.bin"));
- mBatteryStatsHistory = new BatteryStatsHistory(mHistoryBuffer, historyDirectory,
- this::getMaxHistoryFiles);
+ mHistory = new BatteryStatsHistory(historyDirectory, mConstants.MAX_HISTORY_FILES,
+ mConstants.MAX_HISTORY_BUFFER, mStepDetailsCalculator, mClock);
}
- mHandler = null;
mPlatformIdleStateCallback = null;
mMeasuredEnergyRetriever = null;
mUserInfoProvider = null;
- mConstants = new Constants(mHandler);
- clearHistoryLocked();
}
private void init(Clock clock) {
@@ -3911,406 +3869,188 @@
return kmt;
}
- /**
- * Returns the index for the specified tag. If this is the first time the tag is encountered
- * while writing the current history buffer, the method returns
- * <code>(index | TAG_FIRST_OCCURRENCE_FLAG)</code>
- */
- private int writeHistoryTag(HistoryTag tag) {
- if (tag.string == null) {
- Slog.wtfStack(TAG, "writeHistoryTag called with null name");
- }
+ private class HistoryStepDetailsCalculatorImpl implements HistoryStepDetailsCalculator {
+ private final HistoryStepDetails mDetails = new HistoryStepDetails();
- final int stringLength = tag.string.length();
- if (stringLength > MAX_HISTORY_TAG_STRING_LENGTH) {
- Slog.e(TAG, "Long battery history tag: " + tag.string);
- tag.string = tag.string.substring(0, MAX_HISTORY_TAG_STRING_LENGTH);
- }
+ private boolean mHasHistoryStepDetails;
- Integer idxObj = mHistoryTagPool.get(tag);
- int idx;
- if (idxObj != null) {
- idx = idxObj;
- if ((idx & BatteryStatsHistory.TAG_FIRST_OCCURRENCE_FLAG) != 0) {
- mHistoryTagPool.put(tag, idx & ~BatteryStatsHistory.TAG_FIRST_OCCURRENCE_FLAG);
+ private int mLastHistoryStepLevel;
+
+ /**
+ * Total time (in milliseconds) spent executing in user code.
+ */
+ private long mLastStepCpuUserTimeMs;
+ private long mCurStepCpuUserTimeMs;
+ /**
+ * Total time (in milliseconds) spent executing in kernel code.
+ */
+ private long mLastStepCpuSystemTimeMs;
+ private long mCurStepCpuSystemTimeMs;
+ /**
+ * Times from /proc/stat (but measured in milliseconds).
+ */
+ private long mLastStepStatUserTimeMs;
+ private long mLastStepStatSystemTimeMs;
+ private long mLastStepStatIOWaitTimeMs;
+ private long mLastStepStatIrqTimeMs;
+ private long mLastStepStatSoftIrqTimeMs;
+ private long mLastStepStatIdleTimeMs;
+ private long mCurStepStatUserTimeMs;
+ private long mCurStepStatSystemTimeMs;
+ private long mCurStepStatIOWaitTimeMs;
+ private long mCurStepStatIrqTimeMs;
+ private long mCurStepStatSoftIrqTimeMs;
+ private long mCurStepStatIdleTimeMs;
+
+ @Override
+ public HistoryStepDetails getHistoryStepDetails() {
+ if (mBatteryLevel >= mLastHistoryStepLevel && mHasHistoryStepDetails) {
+ mLastHistoryStepLevel = mBatteryLevel;
+ return null;
}
- return idx;
- } else if (mNextHistoryTagIdx < HISTORY_TAG_INDEX_LIMIT) {
- idx = mNextHistoryTagIdx;
- HistoryTag key = new HistoryTag();
- key.setTo(tag);
- tag.poolIdx = idx;
- mHistoryTagPool.put(key, idx);
- mNextHistoryTagIdx++;
- mNumHistoryTagChars += stringLength + 1;
- if (mHistoryTags != null) {
- mHistoryTags.put(idx, key);
- }
- return idx | BatteryStatsHistory.TAG_FIRST_OCCURRENCE_FLAG;
- } else {
- // Tag pool overflow: include the tag itself in the parcel
- return HISTORY_TAG_INDEX_LIMIT | BatteryStatsHistory.TAG_FIRST_OCCURRENCE_FLAG;
- }
- }
+ // Perform a CPU update right after we do this collection, so we have started
+ // collecting good data for the next step.
+ requestImmediateCpuUpdate();
- /*
- The history delta format uses flags to denote further data in subsequent ints in the parcel.
-
- There is always the first token, which may contain the delta time, or an indicator of
- the length of the time (int or long) following this token.
-
- First token: always present,
- 31 23 15 7 0
- â–ˆM|L|K|J|I|H|G|Fâ–ˆE|D|C|B|A|T|T|Tâ–ˆT|T|T|T|T|T|T|Tâ–ˆT|T|T|T|T|T|T|Tâ–ˆ
-
- T: the delta time if it is <= 0x7fffd. Otherwise 0x7fffe indicates an int immediately
- follows containing the time, and 0x7ffff indicates a long immediately follows with the
- delta time.
- A: battery level changed and an int follows with battery data.
- B: state changed and an int follows with state change data.
- C: state2 has changed and an int follows with state2 change data.
- D: wakelock/wakereason has changed and an wakelock/wakereason struct follows.
- E: event data has changed and an event struct follows.
- F: battery charge in coulombs has changed and an int with the charge follows.
- G: state flag denoting that the mobile radio was active.
- H: state flag denoting that the wifi radio was active.
- I: state flag denoting that a wifi scan occurred.
- J: state flag denoting that a wifi full lock was held.
- K: state flag denoting that the gps was on.
- L: state flag denoting that a wakelock was held.
- M: state flag denoting that the cpu was running.
-
- Time int/long: if T in the first token is 0x7ffff or 0x7fffe, then an int or long follows
- with the time delta.
-
- Battery level int: if A in the first token is set,
- 31 23 15 7 0
- â–ˆL|L|L|L|L|L|L|Tâ–ˆT|T|T|T|T|T|T|Tâ–ˆT|V|V|V|V|V|V|Vâ–ˆV|V|V|V|V|V|V|Dâ–ˆ
-
- D: indicates that extra history details follow.
- V: the battery voltage.
- T: the battery temperature.
- L: the battery level (out of 100).
-
- State change int: if B in the first token is set,
- 31 23 15 7 0
- â–ˆS|S|S|H|H|H|P|Pâ–ˆF|E|D|C|B| | |Aâ–ˆ | | | | | | | â–ˆ | | | | | | | â–ˆ
-
- A: wifi multicast was on.
- B: battery was plugged in.
- C: screen was on.
- D: phone was scanning for signal.
- E: audio was on.
- F: a sensor was active.
-
- State2 change int: if C in the first token is set,
- 31 23 15 7 0
- â–ˆM|L|K|J|I|H|H|Gâ–ˆF|E|D|C| | | | â–ˆ | | | | | | | â–ˆ |B|B|B|A|A|A|Aâ–ˆ
-
- A: 4 bits indicating the wifi supplicant state: {@link BatteryStats#WIFI_SUPPL_STATE_NAMES}.
- B: 3 bits indicating the wifi signal strength: 0, 1, 2, 3, 4.
- C: a bluetooth scan was active.
- D: the camera was active.
- E: bluetooth was on.
- F: a phone call was active.
- G: the device was charging.
- H: 2 bits indicating the device-idle (doze) state: off, light, full
- I: the flashlight was on.
- J: wifi was on.
- K: wifi was running.
- L: video was playing.
- M: power save mode was on.
-
- Wakelock/wakereason struct: if D in the first token is set,
- TODO(adamlesinski): describe wakelock/wakereason struct.
-
- Event struct: if E in the first token is set,
- TODO(adamlesinski): describe the event struct.
-
- History step details struct: if D in the battery level int is set,
- TODO(adamlesinski): describe the history step details struct.
-
- Battery charge int: if F in the first token is set, an int representing the battery charge
- in coulombs follows.
- */
-
- @GuardedBy("this")
- public void writeHistoryDelta(Parcel dest, HistoryItem cur, HistoryItem last) {
- if (last == null || cur.cmd != HistoryItem.CMD_UPDATE) {
- dest.writeInt(BatteryStatsHistory.DELTA_TIME_ABS);
- cur.writeToParcel(dest, 0);
- return;
- }
-
- final long deltaTime = cur.time - last.time;
- final int lastBatteryLevelInt = buildBatteryLevelInt(last);
- final int lastStateInt = buildStateInt(last);
-
- int deltaTimeToken;
- if (deltaTime < 0 || deltaTime > Integer.MAX_VALUE) {
- deltaTimeToken = BatteryStatsHistory.DELTA_TIME_LONG;
- } else if (deltaTime >= BatteryStatsHistory.DELTA_TIME_ABS) {
- deltaTimeToken = BatteryStatsHistory.DELTA_TIME_INT;
- } else {
- deltaTimeToken = (int)deltaTime;
- }
- int firstToken = deltaTimeToken | (cur.states & BatteryStatsHistory.DELTA_STATE_MASK);
- final int includeStepDetails = mLastHistoryStepLevel > cur.batteryLevel
- ? BatteryStatsHistory.BATTERY_DELTA_LEVEL_FLAG : 0;
- final boolean computeStepDetails = includeStepDetails != 0
- || mLastHistoryStepDetails == null;
- final int batteryLevelInt = buildBatteryLevelInt(cur) | includeStepDetails;
- final boolean batteryLevelIntChanged = batteryLevelInt != lastBatteryLevelInt;
- if (batteryLevelIntChanged) {
- firstToken |= BatteryStatsHistory.DELTA_BATTERY_LEVEL_FLAG;
- }
- final int stateInt = buildStateInt(cur);
- final boolean stateIntChanged = stateInt != lastStateInt;
- if (stateIntChanged) {
- firstToken |= BatteryStatsHistory.DELTA_STATE_FLAG;
- }
- final boolean state2IntChanged = cur.states2 != last.states2;
- if (state2IntChanged) {
- firstToken |= BatteryStatsHistory.DELTA_STATE2_FLAG;
- }
- if (cur.wakelockTag != null || cur.wakeReasonTag != null) {
- firstToken |= BatteryStatsHistory.DELTA_WAKELOCK_FLAG;
- }
- if (cur.eventCode != HistoryItem.EVENT_NONE) {
- firstToken |= BatteryStatsHistory.DELTA_EVENT_FLAG;
- }
-
- final boolean batteryChargeChanged = cur.batteryChargeUah != last.batteryChargeUah;
- if (batteryChargeChanged) {
- firstToken |= BatteryStatsHistory.DELTA_BATTERY_CHARGE_FLAG;
- }
- dest.writeInt(firstToken);
- if (DEBUG) Slog.i(TAG, "WRITE DELTA: firstToken=0x" + Integer.toHexString(firstToken)
- + " deltaTime=" + deltaTime);
-
- if (deltaTimeToken >= BatteryStatsHistory.DELTA_TIME_INT) {
- if (deltaTimeToken == BatteryStatsHistory.DELTA_TIME_INT) {
- if (DEBUG) Slog.i(TAG, "WRITE DELTA: int deltaTime=" + (int)deltaTime);
- dest.writeInt((int)deltaTime);
- } else {
- if (DEBUG) Slog.i(TAG, "WRITE DELTA: long deltaTime=" + deltaTime);
- dest.writeLong(deltaTime);
- }
- }
- if (batteryLevelIntChanged) {
- dest.writeInt(batteryLevelInt);
- if (DEBUG) Slog.i(TAG, "WRITE DELTA: batteryToken=0x"
- + Integer.toHexString(batteryLevelInt)
- + " batteryLevel=" + cur.batteryLevel
- + " batteryTemp=" + cur.batteryTemperature
- + " batteryVolt=" + (int)cur.batteryVoltage);
- }
- if (stateIntChanged) {
- dest.writeInt(stateInt);
- if (DEBUG) Slog.i(TAG, "WRITE DELTA: stateToken=0x"
- + Integer.toHexString(stateInt)
- + " batteryStatus=" + cur.batteryStatus
- + " batteryHealth=" + cur.batteryHealth
- + " batteryPlugType=" + cur.batteryPlugType
- + " states=0x" + Integer.toHexString(cur.states));
- }
- if (state2IntChanged) {
- dest.writeInt(cur.states2);
- if (DEBUG) Slog.i(TAG, "WRITE DELTA: states2=0x"
- + Integer.toHexString(cur.states2));
- }
- if (cur.wakelockTag != null || cur.wakeReasonTag != null) {
- int wakeLockIndex;
- int wakeReasonIndex;
- if (cur.wakelockTag != null) {
- wakeLockIndex = writeHistoryTag(cur.wakelockTag);
- if (DEBUG) Slog.i(TAG, "WRITE DELTA: wakelockTag=#" + cur.wakelockTag.poolIdx
- + " " + cur.wakelockTag.uid + ":" + cur.wakelockTag.string);
- } else {
- wakeLockIndex = 0xffff;
- }
- if (cur.wakeReasonTag != null) {
- wakeReasonIndex = writeHistoryTag(cur.wakeReasonTag);
- if (DEBUG) Slog.i(TAG, "WRITE DELTA: wakeReasonTag=#" + cur.wakeReasonTag.poolIdx
- + " " + cur.wakeReasonTag.uid + ":" + cur.wakeReasonTag.string);
- } else {
- wakeReasonIndex = 0xffff;
- }
- dest.writeInt((wakeReasonIndex<<16) | wakeLockIndex);
- if (cur.wakelockTag != null
- && (wakeLockIndex & BatteryStatsHistory.TAG_FIRST_OCCURRENCE_FLAG) != 0) {
- cur.wakelockTag.writeToParcel(dest, 0);
- cur.tagsFirstOccurrence = true;
- }
- if (cur.wakeReasonTag != null
- && (wakeReasonIndex & BatteryStatsHistory.TAG_FIRST_OCCURRENCE_FLAG) != 0) {
- cur.wakeReasonTag.writeToParcel(dest, 0);
- cur.tagsFirstOccurrence = true;
- }
- }
- if (cur.eventCode != HistoryItem.EVENT_NONE) {
- final int index = writeHistoryTag(cur.eventTag);
- final int codeAndIndex = (cur.eventCode & 0xffff) | (index << 16);
- dest.writeInt(codeAndIndex);
- if ((index & BatteryStatsHistory.TAG_FIRST_OCCURRENCE_FLAG) != 0) {
- cur.eventTag.writeToParcel(dest, 0);
- cur.tagsFirstOccurrence = true;
- }
- if (DEBUG) Slog.i(TAG, "WRITE DELTA: event=" + cur.eventCode + " tag=#"
- + cur.eventTag.poolIdx + " " + cur.eventTag.uid + ":"
- + cur.eventTag.string);
- }
- if (computeStepDetails) {
if (mPlatformIdleStateCallback != null) {
- mCurHistoryStepDetails.statSubsystemPowerState =
+ mDetails.statSubsystemPowerState =
mPlatformIdleStateCallback.getSubsystemLowPowerStats();
if (DEBUG) Slog.i(TAG, "WRITE SubsystemPowerState:" +
- mCurHistoryStepDetails.statSubsystemPowerState);
-
+ mDetails.statSubsystemPowerState);
}
- computeHistoryStepDetails(mCurHistoryStepDetails, mLastHistoryStepDetails);
- if (includeStepDetails != 0) {
- mCurHistoryStepDetails.writeToParcel(dest);
- }
- cur.stepDetails = mCurHistoryStepDetails;
- mLastHistoryStepDetails = mCurHistoryStepDetails;
- } else {
- cur.stepDetails = null;
- }
- if (mLastHistoryStepLevel < cur.batteryLevel) {
- mLastHistoryStepDetails = null;
- }
- mLastHistoryStepLevel = cur.batteryLevel;
- if (batteryChargeChanged) {
- if (DEBUG) Slog.i(TAG, "WRITE DELTA: batteryChargeUah=" + cur.batteryChargeUah);
- dest.writeInt(cur.batteryChargeUah);
- }
- dest.writeDouble(cur.modemRailChargeMah);
- dest.writeDouble(cur.wifiRailChargeMah);
- }
-
- private int buildBatteryLevelInt(HistoryItem h) {
- return ((((int)h.batteryLevel)<<25)&0xfe000000)
- | ((((int)h.batteryTemperature)<<15)&0x01ff8000)
- | ((((int)h.batteryVoltage)<<1)&0x00007ffe);
- }
-
- private int buildStateInt(HistoryItem h) {
- int plugType = 0;
- if ((h.batteryPlugType&BatteryManager.BATTERY_PLUGGED_AC) != 0) {
- plugType = 1;
- } else if ((h.batteryPlugType&BatteryManager.BATTERY_PLUGGED_USB) != 0) {
- plugType = 2;
- } else if ((h.batteryPlugType&BatteryManager.BATTERY_PLUGGED_WIRELESS) != 0) {
- plugType = 3;
- }
- return ((h.batteryStatus & BatteryStatsHistory.STATE_BATTERY_STATUS_MASK)
- << BatteryStatsHistory.STATE_BATTERY_STATUS_SHIFT)
- | ((h.batteryHealth & BatteryStatsHistory.STATE_BATTERY_HEALTH_MASK)
- << BatteryStatsHistory.STATE_BATTERY_HEALTH_SHIFT)
- | ((plugType & BatteryStatsHistory.STATE_BATTERY_PLUG_MASK)
- << BatteryStatsHistory.STATE_BATTERY_PLUG_SHIFT)
- | (h.states & (~BatteryStatsHistory.STATE_BATTERY_MASK));
- }
-
- private void computeHistoryStepDetails(final HistoryStepDetails out,
- final HistoryStepDetails last) {
- final HistoryStepDetails tmp = last != null ? mTmpHistoryStepDetails : out;
-
- // Perform a CPU update right after we do this collection, so we have started
- // collecting good data for the next step.
- requestImmediateCpuUpdate();
-
- if (last == null) {
- // We are not generating a delta, so all we need to do is reset the stats
- // we will later be doing a delta from.
- final int NU = mUidStats.size();
- for (int i=0; i<NU; i++) {
- final BatteryStatsImpl.Uid uid = mUidStats.valueAt(i);
- uid.mLastStepUserTimeMs = uid.mCurStepUserTimeMs;
- uid.mLastStepSystemTimeMs = uid.mCurStepSystemTimeMs;
- }
- mLastStepCpuUserTimeMs = mCurStepCpuUserTimeMs;
- mLastStepCpuSystemTimeMs = mCurStepCpuSystemTimeMs;
- mLastStepStatUserTimeMs = mCurStepStatUserTimeMs;
- mLastStepStatSystemTimeMs = mCurStepStatSystemTimeMs;
- mLastStepStatIOWaitTimeMs = mCurStepStatIOWaitTimeMs;
- mLastStepStatIrqTimeMs = mCurStepStatIrqTimeMs;
- mLastStepStatSoftIrqTimeMs = mCurStepStatSoftIrqTimeMs;
- mLastStepStatIdleTimeMs = mCurStepStatIdleTimeMs;
- tmp.clear();
- return;
- }
- if (DEBUG) {
- Slog.d(TAG, "Step stats last: user=" + mLastStepCpuUserTimeMs + " sys="
- + mLastStepStatSystemTimeMs + " io=" + mLastStepStatIOWaitTimeMs
- + " irq=" + mLastStepStatIrqTimeMs + " sirq="
- + mLastStepStatSoftIrqTimeMs + " idle=" + mLastStepStatIdleTimeMs);
- Slog.d(TAG, "Step stats cur: user=" + mCurStepCpuUserTimeMs + " sys="
- + mCurStepStatSystemTimeMs + " io=" + mCurStepStatIOWaitTimeMs
- + " irq=" + mCurStepStatIrqTimeMs + " sirq="
- + mCurStepStatSoftIrqTimeMs + " idle=" + mCurStepStatIdleTimeMs);
- }
- out.userTime = (int) (mCurStepCpuUserTimeMs - mLastStepCpuUserTimeMs);
- out.systemTime = (int) (mCurStepCpuSystemTimeMs - mLastStepCpuSystemTimeMs);
- out.statUserTime = (int) (mCurStepStatUserTimeMs - mLastStepStatUserTimeMs);
- out.statSystemTime = (int) (mCurStepStatSystemTimeMs - mLastStepStatSystemTimeMs);
- out.statIOWaitTime = (int) (mCurStepStatIOWaitTimeMs - mLastStepStatIOWaitTimeMs);
- out.statIrqTime = (int) (mCurStepStatIrqTimeMs - mLastStepStatIrqTimeMs);
- out.statSoftIrqTime = (int) (mCurStepStatSoftIrqTimeMs - mLastStepStatSoftIrqTimeMs);
- out.statIdlTime = (int) (mCurStepStatIdleTimeMs - mLastStepStatIdleTimeMs);
- out.appCpuUid1 = out.appCpuUid2 = out.appCpuUid3 = -1;
- out.appCpuUTime1 = out.appCpuUTime2 = out.appCpuUTime3 = 0;
- out.appCpuSTime1 = out.appCpuSTime2 = out.appCpuSTime3 = 0;
- final int NU = mUidStats.size();
- for (int i=0; i<NU; i++) {
- final BatteryStatsImpl.Uid uid = mUidStats.valueAt(i);
- final int totalUTimeMs = (int) (uid.mCurStepUserTimeMs - uid.mLastStepUserTimeMs);
- final int totalSTimeMs = (int) (uid.mCurStepSystemTimeMs - uid.mLastStepSystemTimeMs);
- final int totalTimeMs = totalUTimeMs + totalSTimeMs;
- uid.mLastStepUserTimeMs = uid.mCurStepUserTimeMs;
- uid.mLastStepSystemTimeMs = uid.mCurStepSystemTimeMs;
- if (totalTimeMs <= (out.appCpuUTime3 + out.appCpuSTime3)) {
- continue;
- }
- if (totalTimeMs <= (out.appCpuUTime2 + out.appCpuSTime2)) {
- out.appCpuUid3 = uid.mUid;
- out.appCpuUTime3 = totalUTimeMs;
- out.appCpuSTime3 = totalSTimeMs;
- } else {
- out.appCpuUid3 = out.appCpuUid2;
- out.appCpuUTime3 = out.appCpuUTime2;
- out.appCpuSTime3 = out.appCpuSTime2;
- if (totalTimeMs <= (out.appCpuUTime1 + out.appCpuSTime1)) {
- out.appCpuUid2 = uid.mUid;
- out.appCpuUTime2 = totalUTimeMs;
- out.appCpuSTime2 = totalSTimeMs;
- } else {
- out.appCpuUid2 = out.appCpuUid1;
- out.appCpuUTime2 = out.appCpuUTime1;
- out.appCpuSTime2 = out.appCpuSTime1;
- out.appCpuUid1 = uid.mUid;
- out.appCpuUTime1 = totalUTimeMs;
- out.appCpuSTime1 = totalSTimeMs;
+ if (!mHasHistoryStepDetails) {
+ // We are not generating a delta, so all we need to do is reset the stats
+ // we will later be doing a delta from.
+ final int uidCount = mUidStats.size();
+ for (int i = 0; i < uidCount; i++) {
+ final BatteryStatsImpl.Uid uid = mUidStats.valueAt(i);
+ uid.mLastStepUserTimeMs = uid.mCurStepUserTimeMs;
+ uid.mLastStepSystemTimeMs = uid.mCurStepSystemTimeMs;
}
+ mLastStepCpuUserTimeMs = mCurStepCpuUserTimeMs;
+ mLastStepCpuSystemTimeMs = mCurStepCpuSystemTimeMs;
+ mLastStepStatUserTimeMs = mCurStepStatUserTimeMs;
+ mLastStepStatSystemTimeMs = mCurStepStatSystemTimeMs;
+ mLastStepStatIOWaitTimeMs = mCurStepStatIOWaitTimeMs;
+ mLastStepStatIrqTimeMs = mCurStepStatIrqTimeMs;
+ mLastStepStatSoftIrqTimeMs = mCurStepStatSoftIrqTimeMs;
+ mLastStepStatIdleTimeMs = mCurStepStatIdleTimeMs;
+ mDetails.clear();
+ } else {
+ if (DEBUG) {
+ Slog.d(TAG, "Step stats last: user=" + mLastStepCpuUserTimeMs + " sys="
+ + mLastStepStatSystemTimeMs + " io=" + mLastStepStatIOWaitTimeMs
+ + " irq=" + mLastStepStatIrqTimeMs + " sirq="
+ + mLastStepStatSoftIrqTimeMs + " idle=" + mLastStepStatIdleTimeMs);
+ Slog.d(TAG, "Step stats cur: user=" + mCurStepCpuUserTimeMs + " sys="
+ + mCurStepStatSystemTimeMs + " io=" + mCurStepStatIOWaitTimeMs
+ + " irq=" + mCurStepStatIrqTimeMs + " sirq="
+ + mCurStepStatSoftIrqTimeMs + " idle=" + mCurStepStatIdleTimeMs);
+ }
+ mDetails.userTime = (int) (mCurStepCpuUserTimeMs - mLastStepCpuUserTimeMs);
+ mDetails.systemTime = (int) (mCurStepCpuSystemTimeMs - mLastStepCpuSystemTimeMs);
+ mDetails.statUserTime = (int) (mCurStepStatUserTimeMs - mLastStepStatUserTimeMs);
+ mDetails.statSystemTime =
+ (int) (mCurStepStatSystemTimeMs - mLastStepStatSystemTimeMs);
+ mDetails.statIOWaitTime =
+ (int) (mCurStepStatIOWaitTimeMs - mLastStepStatIOWaitTimeMs);
+ mDetails.statIrqTime = (int) (mCurStepStatIrqTimeMs - mLastStepStatIrqTimeMs);
+ mDetails.statSoftIrqTime =
+ (int) (mCurStepStatSoftIrqTimeMs - mLastStepStatSoftIrqTimeMs);
+ mDetails.statIdlTime = (int) (mCurStepStatIdleTimeMs - mLastStepStatIdleTimeMs);
+ mDetails.appCpuUid1 = mDetails.appCpuUid2 = mDetails.appCpuUid3 = -1;
+ mDetails.appCpuUTime1 = mDetails.appCpuUTime2 = mDetails.appCpuUTime3 = 0;
+ mDetails.appCpuSTime1 = mDetails.appCpuSTime2 = mDetails.appCpuSTime3 = 0;
+ final int uidCount = mUidStats.size();
+ for (int i = 0; i < uidCount; i++) {
+ final BatteryStatsImpl.Uid uid = mUidStats.valueAt(i);
+ final int totalUTimeMs =
+ (int) (uid.mCurStepUserTimeMs - uid.mLastStepUserTimeMs);
+ final int totalSTimeMs =
+ (int) (uid.mCurStepSystemTimeMs - uid.mLastStepSystemTimeMs);
+ final int totalTimeMs = totalUTimeMs + totalSTimeMs;
+ uid.mLastStepUserTimeMs = uid.mCurStepUserTimeMs;
+ uid.mLastStepSystemTimeMs = uid.mCurStepSystemTimeMs;
+ if (totalTimeMs <= (mDetails.appCpuUTime3 + mDetails.appCpuSTime3)) {
+ continue;
+ }
+ if (totalTimeMs <= (mDetails.appCpuUTime2 + mDetails.appCpuSTime2)) {
+ mDetails.appCpuUid3 = uid.mUid;
+ mDetails.appCpuUTime3 = totalUTimeMs;
+ mDetails.appCpuSTime3 = totalSTimeMs;
+ } else {
+ mDetails.appCpuUid3 = mDetails.appCpuUid2;
+ mDetails.appCpuUTime3 = mDetails.appCpuUTime2;
+ mDetails.appCpuSTime3 = mDetails.appCpuSTime2;
+ if (totalTimeMs <= (mDetails.appCpuUTime1 + mDetails.appCpuSTime1)) {
+ mDetails.appCpuUid2 = uid.mUid;
+ mDetails.appCpuUTime2 = totalUTimeMs;
+ mDetails.appCpuSTime2 = totalSTimeMs;
+ } else {
+ mDetails.appCpuUid2 = mDetails.appCpuUid1;
+ mDetails.appCpuUTime2 = mDetails.appCpuUTime1;
+ mDetails.appCpuSTime2 = mDetails.appCpuSTime1;
+ mDetails.appCpuUid1 = uid.mUid;
+ mDetails.appCpuUTime1 = totalUTimeMs;
+ mDetails.appCpuSTime1 = totalSTimeMs;
+ }
+ }
+ }
+ mLastStepCpuUserTimeMs = mCurStepCpuUserTimeMs;
+ mLastStepCpuSystemTimeMs = mCurStepCpuSystemTimeMs;
+ mLastStepStatUserTimeMs = mCurStepStatUserTimeMs;
+ mLastStepStatSystemTimeMs = mCurStepStatSystemTimeMs;
+ mLastStepStatIOWaitTimeMs = mCurStepStatIOWaitTimeMs;
+ mLastStepStatIrqTimeMs = mCurStepStatIrqTimeMs;
+ mLastStepStatSoftIrqTimeMs = mCurStepStatSoftIrqTimeMs;
+ mLastStepStatIdleTimeMs = mCurStepStatIdleTimeMs;
}
+
+ mHasHistoryStepDetails = mBatteryLevel <= mLastHistoryStepLevel;
+ mLastHistoryStepLevel = mBatteryLevel;
+
+ return mDetails;
}
- mLastStepCpuUserTimeMs = mCurStepCpuUserTimeMs;
- mLastStepCpuSystemTimeMs = mCurStepCpuSystemTimeMs;
- mLastStepStatUserTimeMs = mCurStepStatUserTimeMs;
- mLastStepStatSystemTimeMs = mCurStepStatSystemTimeMs;
- mLastStepStatIOWaitTimeMs = mCurStepStatIOWaitTimeMs;
- mLastStepStatIrqTimeMs = mCurStepStatIrqTimeMs;
- mLastStepStatSoftIrqTimeMs = mCurStepStatSoftIrqTimeMs;
- mLastStepStatIdleTimeMs = mCurStepStatIdleTimeMs;
+
+ public void addCpuStats(int totalUTimeMs, int totalSTimeMs, int statUserTimeMs,
+ int statSystemTimeMs, int statIOWaitTimeMs, int statIrqTimeMs,
+ int statSoftIrqTimeMs, int statIdleTimeMs) {
+ if (DEBUG) {
+ Slog.d(TAG, "Adding cpu: tuser=" + totalUTimeMs + " tsys=" + totalSTimeMs
+ + " user=" + statUserTimeMs + " sys=" + statSystemTimeMs
+ + " io=" + statIOWaitTimeMs + " irq=" + statIrqTimeMs
+ + " sirq=" + statSoftIrqTimeMs + " idle=" + statIdleTimeMs);
+ }
+ mCurStepCpuUserTimeMs += totalUTimeMs;
+ mCurStepCpuSystemTimeMs += totalSTimeMs;
+ mCurStepStatUserTimeMs += statUserTimeMs;
+ mCurStepStatSystemTimeMs += statSystemTimeMs;
+ mCurStepStatIOWaitTimeMs += statIOWaitTimeMs;
+ mCurStepStatIrqTimeMs += statIrqTimeMs;
+ mCurStepStatSoftIrqTimeMs += statSoftIrqTimeMs;
+ mCurStepStatIdleTimeMs += statIdleTimeMs;
+ }
+
+ @Override
+ public void clear() {
+ mHasHistoryStepDetails = false;
+ mLastStepCpuUserTimeMs = mCurStepCpuUserTimeMs = 0;
+ mLastStepCpuSystemTimeMs = mCurStepCpuSystemTimeMs = 0;
+ mLastStepStatUserTimeMs = mCurStepStatUserTimeMs = 0;
+ mLastStepStatSystemTimeMs = mCurStepStatSystemTimeMs = 0;
+ mLastStepStatIOWaitTimeMs = mCurStepStatIOWaitTimeMs = 0;
+ mLastStepStatIrqTimeMs = mCurStepStatIrqTimeMs = 0;
+ mLastStepStatSoftIrqTimeMs = mCurStepStatSoftIrqTimeMs = 0;
+ mLastStepStatIdleTimeMs = mCurStepStatIdleTimeMs = 0;
+ }
}
@GuardedBy("this")
@Override
public void commitCurrentHistoryBatchLocked() {
- mHistoryLastWritten.cmd = HistoryItem.CMD_NULL;
+ mHistory.commitCurrentHistoryBatchLocked();
}
@GuardedBy("this")
@@ -4326,191 +4066,9 @@
}
@GuardedBy("this")
- void addHistoryBufferLocked(long elapsedRealtimeMs, long uptimeMs, HistoryItem cur) {
- if (!mHaveBatteryLevel || !mRecordingHistory) {
- return;
- }
-
- final long timeDiffMs = (mHistoryBaseTimeMs + elapsedRealtimeMs) - mHistoryLastWritten.time;
- final int diffStates = mHistoryLastWritten.states^(cur.states&mActiveHistoryStates);
- final int diffStates2 = mHistoryLastWritten.states2^(cur.states2&mActiveHistoryStates2);
- final int lastDiffStates = mHistoryLastWritten.states^mHistoryLastLastWritten.states;
- final int lastDiffStates2 = mHistoryLastWritten.states2^mHistoryLastLastWritten.states2;
- if (DEBUG) {
- Slog.i(TAG, "ADD: tdelta=" + timeDiffMs + " diff="
- + Integer.toHexString(diffStates) + " lastDiff="
- + Integer.toHexString(lastDiffStates) + " diff2="
- + Integer.toHexString(diffStates2) + " lastDiff2="
- + Integer.toHexString(lastDiffStates2));
- }
- if (mHistoryBufferLastPos >= 0 && mHistoryLastWritten.cmd == HistoryItem.CMD_UPDATE
- && timeDiffMs < 1000 && (diffStates & lastDiffStates) == 0
- && (diffStates2&lastDiffStates2) == 0
- && (!mHistoryLastWritten.tagsFirstOccurrence && !cur.tagsFirstOccurrence)
- && (mHistoryLastWritten.wakelockTag == null || cur.wakelockTag == null)
- && (mHistoryLastWritten.wakeReasonTag == null || cur.wakeReasonTag == null)
- && mHistoryLastWritten.stepDetails == null
- && (mHistoryLastWritten.eventCode == HistoryItem.EVENT_NONE
- || cur.eventCode == HistoryItem.EVENT_NONE)
- && mHistoryLastWritten.batteryLevel == cur.batteryLevel
- && mHistoryLastWritten.batteryStatus == cur.batteryStatus
- && mHistoryLastWritten.batteryHealth == cur.batteryHealth
- && mHistoryLastWritten.batteryPlugType == cur.batteryPlugType
- && mHistoryLastWritten.batteryTemperature == cur.batteryTemperature
- && mHistoryLastWritten.batteryVoltage == cur.batteryVoltage) {
- // We can merge this new change in with the last one. Merging is
- // allowed as long as only the states have changed, and within those states
- // as long as no bit has changed both between now and the last entry, as
- // well as the last entry and the one before it (so we capture any toggles).
- if (DEBUG) Slog.i(TAG, "ADD: rewinding back to " + mHistoryBufferLastPos);
- mHistoryBuffer.setDataSize(mHistoryBufferLastPos);
- mHistoryBuffer.setDataPosition(mHistoryBufferLastPos);
- mHistoryBufferLastPos = -1;
- elapsedRealtimeMs = mHistoryLastWritten.time - mHistoryBaseTimeMs;
- // If the last written history had a wakelock tag, we need to retain it.
- // Note that the condition above made sure that we aren't in a case where
- // both it and the current history item have a wakelock tag.
- if (mHistoryLastWritten.wakelockTag != null) {
- cur.wakelockTag = cur.localWakelockTag;
- cur.wakelockTag.setTo(mHistoryLastWritten.wakelockTag);
- }
- // If the last written history had a wake reason tag, we need to retain it.
- // Note that the condition above made sure that we aren't in a case where
- // both it and the current history item have a wakelock tag.
- if (mHistoryLastWritten.wakeReasonTag != null) {
- cur.wakeReasonTag = cur.localWakeReasonTag;
- cur.wakeReasonTag.setTo(mHistoryLastWritten.wakeReasonTag);
- }
- // If the last written history had an event, we need to retain it.
- // Note that the condition above made sure that we aren't in a case where
- // both it and the current history item have an event.
- if (mHistoryLastWritten.eventCode != HistoryItem.EVENT_NONE) {
- cur.eventCode = mHistoryLastWritten.eventCode;
- cur.eventTag = cur.localEventTag;
- cur.eventTag.setTo(mHistoryLastWritten.eventTag);
- }
- mHistoryLastWritten.setTo(mHistoryLastLastWritten);
- }
- final int dataSize = mHistoryBuffer.dataSize();
-
- if (dataSize >= mConstants.MAX_HISTORY_BUFFER) {
- //open a new history file.
- final long start = SystemClock.uptimeMillis();
- writeHistoryLocked();
- if (DEBUG) {
- Slog.d(TAG, "addHistoryBufferLocked writeHistoryLocked takes ms:"
- + (SystemClock.uptimeMillis() - start));
- }
- mBatteryStatsHistory.startNextFile();
- mHistoryBuffer.setDataSize(0);
- mHistoryBuffer.setDataPosition(0);
- mHistoryBuffer.setDataCapacity(mConstants.MAX_HISTORY_BUFFER / 2);
- mHistoryBufferLastPos = -1;
- mHistoryLastWritten.clear();
- mHistoryLastLastWritten.clear();
-
- // Mark every entry in the pool with a flag indicating that the tag
- // has not yet been encountered while writing the current history buffer.
- for (Map.Entry<HistoryTag, Integer> entry: mHistoryTagPool.entrySet()) {
- entry.setValue(entry.getValue() | BatteryStatsHistory.TAG_FIRST_OCCURRENCE_FLAG);
- }
- // Make a copy of mHistoryCur.
- HistoryItem copy = new HistoryItem();
- copy.setTo(cur);
- // startRecordingHistory will reset mHistoryCur.
- startRecordingHistory(elapsedRealtimeMs, uptimeMs, false);
- // Add the copy into history buffer.
- addHistoryBufferLocked(elapsedRealtimeMs, HistoryItem.CMD_UPDATE, copy);
- return;
- }
-
- if (dataSize == 0) {
- // The history is currently empty; we need it to start with a time stamp.
- cur.currentTime = mClock.currentTimeMillis();
- addHistoryBufferLocked(elapsedRealtimeMs, HistoryItem.CMD_RESET, cur);
- }
- addHistoryBufferLocked(elapsedRealtimeMs, HistoryItem.CMD_UPDATE, cur);
- }
-
- @GuardedBy("this")
- private void addHistoryBufferLocked(long elapsedRealtimeMs, byte cmd, HistoryItem cur) {
- if (mBatteryStatsHistoryIterator != null) {
- throw new IllegalStateException("Can't do this while iterating history!");
- }
- mHistoryBufferLastPos = mHistoryBuffer.dataPosition();
- mHistoryLastLastWritten.setTo(mHistoryLastWritten);
- final boolean hasTags = mHistoryLastWritten.tagsFirstOccurrence || cur.tagsFirstOccurrence;
- mHistoryLastWritten.setTo(mHistoryBaseTimeMs + elapsedRealtimeMs, cmd, cur);
- mHistoryLastWritten.tagsFirstOccurrence = hasTags;
- mHistoryLastWritten.states &= mActiveHistoryStates;
- mHistoryLastWritten.states2 &= mActiveHistoryStates2;
- writeHistoryDelta(mHistoryBuffer, mHistoryLastWritten, mHistoryLastLastWritten);
- mLastHistoryElapsedRealtimeMs = elapsedRealtimeMs;
- cur.wakelockTag = null;
- cur.wakeReasonTag = null;
- cur.eventCode = HistoryItem.EVENT_NONE;
- cur.eventTag = null;
- cur.tagsFirstOccurrence = false;
- if (DEBUG_HISTORY) Slog.i(TAG, "Writing history buffer: was " + mHistoryBufferLastPos
- + " now " + mHistoryBuffer.dataPosition()
- + " size is now " + mHistoryBuffer.dataSize());
- }
-
- @GuardedBy("this")
- void addHistoryRecordLocked(long elapsedRealtimeMs, long uptimeMs) {
- if (mTrackRunningHistoryElapsedRealtimeMs != 0) {
- final long diffElapsedMs = elapsedRealtimeMs - mTrackRunningHistoryElapsedRealtimeMs;
- final long diffUptimeMs = uptimeMs - mTrackRunningHistoryUptimeMs;
- if (diffUptimeMs < (diffElapsedMs - 20)) {
- final long wakeElapsedTimeMs = elapsedRealtimeMs - (diffElapsedMs - diffUptimeMs);
- mHistoryAddTmp.setTo(mHistoryLastWritten);
- mHistoryAddTmp.wakelockTag = null;
- mHistoryAddTmp.wakeReasonTag = null;
- mHistoryAddTmp.eventCode = HistoryItem.EVENT_NONE;
- mHistoryAddTmp.states &= ~HistoryItem.STATE_CPU_RUNNING_FLAG;
- addHistoryRecordInnerLocked(wakeElapsedTimeMs, uptimeMs, mHistoryAddTmp);
- }
- }
- mHistoryCur.states |= HistoryItem.STATE_CPU_RUNNING_FLAG;
- mTrackRunningHistoryElapsedRealtimeMs = elapsedRealtimeMs;
- mTrackRunningHistoryUptimeMs = uptimeMs;
- addHistoryRecordInnerLocked(elapsedRealtimeMs, uptimeMs, mHistoryCur);
- }
-
- @GuardedBy("this")
- void addHistoryRecordInnerLocked(long elapsedRealtimeMs, long uptimeMs, HistoryItem cur) {
- addHistoryBufferLocked(elapsedRealtimeMs, uptimeMs, cur);
- }
-
- @GuardedBy("this")
- public void addHistoryEventLocked(long elapsedRealtimeMs, long uptimeMs, int code,
+ public void recordHistoryEventLocked(long elapsedRealtimeMs, long uptimeMs, int code,
String name, int uid) {
- mHistoryCur.eventCode = code;
- mHistoryCur.eventTag = mHistoryCur.localEventTag;
- mHistoryCur.eventTag.string = name;
- mHistoryCur.eventTag.uid = uid;
- addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
- }
-
- @GuardedBy("this")
- void clearHistoryLocked() {
- if (DEBUG_HISTORY) Slog.i(TAG, "********** CLEARING HISTORY!");
- mHistoryBaseTimeMs = 0;
- mLastHistoryElapsedRealtimeMs = 0;
- mTrackRunningHistoryElapsedRealtimeMs = 0;
- mTrackRunningHistoryUptimeMs = 0;
-
- mHistoryBuffer.setDataSize(0);
- mHistoryBuffer.setDataPosition(0);
- mHistoryBuffer.setDataCapacity(mConstants.MAX_HISTORY_BUFFER / 2);
- mHistoryLastLastWritten.clear();
- mHistoryLastWritten.clear();
- mHistoryTagPool.clear();
- mNextHistoryTagIdx = 0;
- mNumHistoryTagChars = 0;
- mHistoryBufferLastPos = -1;
- mActiveHistoryStates = 0xffffffff;
- mActiveHistoryStates2 = 0xffffffff;
+ mHistory.recordEvent(elapsedRealtimeMs, uptimeMs, code, name, uid);
}
@GuardedBy("this")
@@ -4663,13 +4221,13 @@
if (!mActiveEvents.updateState(code, name, uid, 0)) {
return;
}
- addHistoryEventLocked(elapsedRealtimeMs, uptimeMs, code, name, uid);
+ mHistory.recordEvent(elapsedRealtimeMs, uptimeMs, code, name, uid);
}
@GuardedBy("this")
public void noteCurrentTimeChangedLocked(long currentTimeMs,
long elapsedRealtimeMs, long uptimeMs) {
- recordCurrentTimeChangeLocked(currentTimeMs, elapsedRealtimeMs, uptimeMs);
+ mHistory.recordCurrentTimeChange(elapsedRealtimeMs, uptimeMs, currentTimeMs);
}
@GuardedBy("this")
@@ -4686,7 +4244,7 @@
if (!mRecordAllHistory) {
return;
}
- addHistoryEventLocked(elapsedRealtimeMs, uptimeMs, HistoryItem.EVENT_PROC_START, name, uid);
+ mHistory.recordEvent(elapsedRealtimeMs, uptimeMs, HistoryItem.EVENT_PROC_START, name, uid);
}
@GuardedBy("this")
@@ -4744,8 +4302,7 @@
if (!mRecordAllHistory) {
return;
}
- addHistoryEventLocked(elapsedRealtimeMs, uptimeMs, HistoryItem.EVENT_PROC_FINISH,
- name, uid);
+ mHistory.recordEvent(elapsedRealtimeMs, uptimeMs, HistoryItem.EVENT_PROC_FINISH, name, uid);
}
@GuardedBy("this")
@@ -4761,7 +4318,7 @@
if (!mActiveEvents.updateState(HistoryItem.EVENT_SYNC_START, name, uid, 0)) {
return;
}
- addHistoryEventLocked(elapsedRealtimeMs, uptimeMs, HistoryItem.EVENT_SYNC_START, name, uid);
+ mHistory.recordEvent(elapsedRealtimeMs, uptimeMs, HistoryItem.EVENT_SYNC_START, name, uid);
}
@GuardedBy("this")
@@ -4777,8 +4334,7 @@
if (!mActiveEvents.updateState(HistoryItem.EVENT_SYNC_FINISH, name, uid, 0)) {
return;
}
- addHistoryEventLocked(elapsedRealtimeMs, uptimeMs, HistoryItem.EVENT_SYNC_FINISH,
- name, uid);
+ mHistory.recordEvent(elapsedRealtimeMs, uptimeMs, HistoryItem.EVENT_SYNC_FINISH, name, uid);
}
@GuardedBy("this")
@@ -4794,7 +4350,7 @@
if (!mActiveEvents.updateState(HistoryItem.EVENT_JOB_START, name, uid, 0)) {
return;
}
- addHistoryEventLocked(elapsedRealtimeMs, uptimeMs, HistoryItem.EVENT_JOB_START, name, uid);
+ mHistory.recordEvent(elapsedRealtimeMs, uptimeMs, HistoryItem.EVENT_JOB_START, name, uid);
}
@GuardedBy("this")
@@ -4812,7 +4368,7 @@
if (!mActiveEvents.updateState(HistoryItem.EVENT_JOB_FINISH, name, uid, 0)) {
return;
}
- addHistoryEventLocked(elapsedRealtimeMs, uptimeMs, HistoryItem.EVENT_JOB_FINISH, name, uid);
+ mHistory.recordEvent(elapsedRealtimeMs, uptimeMs, HistoryItem.EVENT_JOB_FINISH, name, uid);
}
@GuardedBy("this")
@@ -4860,7 +4416,7 @@
for (int i = 0; i < workSource.size(); ++i) {
uid = mapUid(workSource.getUid(i));
if (mActiveEvents.updateState(historyItem, name, uid, 0)) {
- addHistoryEventLocked(elapsedRealtimeMs, uptimeMs, historyItem, name, uid);
+ mHistory.recordEvent(elapsedRealtimeMs, uptimeMs, historyItem, name, uid);
}
}
@@ -4869,7 +4425,7 @@
for (int i = 0; i < workChains.size(); ++i) {
uid = mapUid(workChains.get(i).getAttributionUid());
if (mActiveEvents.updateState(historyItem, name, uid, 0)) {
- addHistoryEventLocked(elapsedRealtimeMs, uptimeMs, historyItem, name, uid);
+ mHistory.recordEvent(elapsedRealtimeMs, uptimeMs, historyItem, name, uid);
}
}
}
@@ -4877,7 +4433,7 @@
uid = mapUid(uid);
if (mActiveEvents.updateState(historyItem, name, uid, 0)) {
- addHistoryEventLocked(elapsedRealtimeMs, uptimeMs, historyItem, name, uid);
+ mHistory.recordEvent(elapsedRealtimeMs, uptimeMs, historyItem, name, uid);
}
}
}
@@ -4952,7 +4508,7 @@
for (HashMap.Entry<String, SparseIntArray> ent : active.entrySet()) {
SparseIntArray uids = ent.getValue();
for (int j=0; j<uids.size(); j++) {
- addHistoryEventLocked(mSecRealtime, mSecUptime,
+ mHistory.recordEvent(mSecRealtime, mSecUptime,
HistoryItem.EVENT_PROC_FINISH, ent.getKey(), uids.keyAt(j));
}
}
@@ -4967,8 +4523,8 @@
for (HashMap.Entry<String, SparseIntArray> ent : active.entrySet()) {
SparseIntArray uids = ent.getValue();
for (int j=0; j<uids.size(); j++) {
- addHistoryEventLocked(mSecRealtime, mSecUptime,
- HistoryItem.EVENT_PROC_START, ent.getKey(), uids.keyAt(j));
+ mHistory.recordEvent(mSecRealtime, mSecUptime, HistoryItem.EVENT_PROC_START,
+ ent.getKey(), uids.keyAt(j));
}
}
}
@@ -5011,30 +4567,19 @@
if (mRecordAllHistory) {
if (mActiveEvents.updateState(HistoryItem.EVENT_WAKE_LOCK_START, historyName,
mappedUid, 0)) {
- addHistoryEventLocked(elapsedRealtimeMs, uptimeMs,
+ mHistory.recordEvent(elapsedRealtimeMs, uptimeMs,
HistoryItem.EVENT_WAKE_LOCK_START, historyName, mappedUid);
}
}
if (mWakeLockNesting == 0) {
- mHistoryCur.states |= HistoryItem.STATE_WAKE_LOCK_FLAG;
- if (DEBUG_HISTORY) Slog.v(TAG, "Start wake lock to: "
- + Integer.toHexString(mHistoryCur.states));
- mHistoryCur.wakelockTag = mHistoryCur.localWakelockTag;
- mHistoryCur.wakelockTag.string = historyName;
- mHistoryCur.wakelockTag.uid = mappedUid;
mWakeLockImportant = !unimportantForLogging;
- addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
- } else if (!mWakeLockImportant && !unimportantForLogging
- && mHistoryLastWritten.cmd == HistoryItem.CMD_UPDATE) {
- if (mHistoryLastWritten.wakelockTag != null) {
- // We'll try to update the last tag.
- mHistoryLastWritten.wakelockTag = null;
- mHistoryCur.wakelockTag = mHistoryCur.localWakelockTag;
- mHistoryCur.wakelockTag.string = historyName;
- mHistoryCur.wakelockTag.uid = mappedUid;
- addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
+ mHistory.recordWakelockStartEvent(elapsedRealtimeMs, uptimeMs, historyName,
+ mappedUid);
+ } else if (!mWakeLockImportant && !unimportantForLogging) {
+ if (mHistory.maybeUpdateWakelockTag(elapsedRealtimeMs, uptimeMs, historyName,
+ mappedUid)) {
+ mWakeLockImportant = true;
}
- mWakeLockImportant = true;
}
mWakeLockNesting++;
}
@@ -5087,15 +4632,13 @@
}
if (mActiveEvents.updateState(HistoryItem.EVENT_WAKE_LOCK_FINISH, historyName,
mappedUid, 0)) {
- addHistoryEventLocked(elapsedRealtimeMs, uptimeMs,
+ mHistory.recordEvent(elapsedRealtimeMs, uptimeMs,
HistoryItem.EVENT_WAKE_LOCK_FINISH, historyName, mappedUid);
}
}
if (mWakeLockNesting == 0) {
- mHistoryCur.states &= ~HistoryItem.STATE_WAKE_LOCK_FLAG;
- if (DEBUG_HISTORY) Slog.v(TAG, "Stop wake lock to: "
- + Integer.toHexString(mHistoryCur.states));
- addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
+ mHistory.recordStateStopEvent(elapsedRealtimeMs, uptimeMs,
+ HistoryItem.STATE_WAKE_LOCK_FLAG);
}
}
if (mappedUid >= 0) {
@@ -5286,7 +4829,7 @@
mappedUid, 0)) {
return;
}
- addHistoryEventLocked(elapsedRealtimeMs, uptimeMs, HistoryItem.EVENT_LONG_WAKE_LOCK_START,
+ mHistory.recordEvent(elapsedRealtimeMs, uptimeMs, HistoryItem.EVENT_LONG_WAKE_LOCK_START,
historyName, mappedUid);
if (mappedUid != uid) {
// Prevent the isolated uid mapping from being removed while the wakelock is
@@ -5339,7 +4882,7 @@
mappedUid, 0)) {
return;
}
- addHistoryEventLocked(elapsedRealtimeMs, uptimeMs, HistoryItem.EVENT_LONG_WAKE_LOCK_FINISH,
+ mHistory.recordEvent(elapsedRealtimeMs, uptimeMs, HistoryItem.EVENT_LONG_WAKE_LOCK_FINISH,
historyName, mappedUid);
if (mappedUid != uid) {
// Decrement the ref count for the isolated uid and delete the mapping if uneeded.
@@ -5361,15 +4904,10 @@
@GuardedBy("this")
public void noteWakeupReasonLocked(String reason, long elapsedRealtimeMs, long uptimeMs) {
- if (DEBUG_HISTORY) Slog.v(TAG, "Wakeup reason \"" + reason +"\": "
- + Integer.toHexString(mHistoryCur.states));
aggregateLastWakeupUptimeLocked(elapsedRealtimeMs, uptimeMs);
- mHistoryCur.wakeReasonTag = mHistoryCur.localWakeReasonTag;
- mHistoryCur.wakeReasonTag.string = reason;
- mHistoryCur.wakeReasonTag.uid = 0;
+ mHistory.recordWakeupEvent(elapsedRealtimeMs, uptimeMs, reason);
mLastWakeupReason = reason;
mLastWakeupUptimeMs = uptimeMs;
- addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
}
@GuardedBy("this")
@@ -5380,22 +4918,11 @@
@GuardedBy("this")
public void finishAddingCpuLocked(int totalUTimeMs, int totalSTimeMs, int statUserTimeMs,
- int statSystemTimeMs, int statIOWaitTimeMs, int statIrqTimeMs,
- int statSoftIrqTimeMs, int statIdleTimeMs) {
- if (DEBUG) {
- Slog.d(TAG, "Adding cpu: tuser=" + totalUTimeMs + " tsys=" + totalSTimeMs
- + " user=" + statUserTimeMs + " sys=" + statSystemTimeMs
- + " io=" + statIOWaitTimeMs + " irq=" + statIrqTimeMs
- + " sirq=" + statSoftIrqTimeMs + " idle=" + statIdleTimeMs);
- }
- mCurStepCpuUserTimeMs += totalUTimeMs;
- mCurStepCpuSystemTimeMs += totalSTimeMs;
- mCurStepStatUserTimeMs += statUserTimeMs;
- mCurStepStatSystemTimeMs += statSystemTimeMs;
- mCurStepStatIOWaitTimeMs += statIOWaitTimeMs;
- mCurStepStatIrqTimeMs += statIrqTimeMs;
- mCurStepStatSoftIrqTimeMs += statSoftIrqTimeMs;
- mCurStepStatIdleTimeMs += statIdleTimeMs;
+ int statSystemTimeMs, int statIOWaitTimeMs, int statIrqTimeMs,
+ int statSoftIrqTimeMs, int statIdleTimeMs) {
+ mStepDetailsCalculator.addCpuStats(totalUTimeMs, totalSTimeMs, statUserTimeMs,
+ statSystemTimeMs, statIOWaitTimeMs, statIrqTimeMs,
+ statSoftIrqTimeMs, statIdleTimeMs);
}
public void noteProcessDiedLocked(int uid, int pid) {
@@ -5425,10 +4952,8 @@
public void noteStartSensorLocked(int uid, int sensor, long elapsedRealtimeMs, long uptimeMs) {
uid = mapUid(uid);
if (mSensorNesting == 0) {
- mHistoryCur.states |= HistoryItem.STATE_SENSOR_ON_FLAG;
- if (DEBUG_HISTORY) Slog.v(TAG, "Start sensor to: "
- + Integer.toHexString(mHistoryCur.states));
- addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
+ mHistory.recordStateStartEvent(elapsedRealtimeMs, uptimeMs,
+ HistoryItem.STATE_SENSOR_ON_FLAG);
}
mSensorNesting++;
getUidStatsLocked(uid, elapsedRealtimeMs, uptimeMs)
@@ -5445,10 +4970,8 @@
uid = mapUid(uid);
mSensorNesting--;
if (mSensorNesting == 0) {
- mHistoryCur.states &= ~HistoryItem.STATE_SENSOR_ON_FLAG;
- if (DEBUG_HISTORY) Slog.v(TAG, "Stop sensor to: "
- + Integer.toHexString(mHistoryCur.states));
- addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
+ mHistory.recordStateStopEvent(elapsedRealtimeMs, uptimeMs,
+ HistoryItem.STATE_SENSOR_ON_FLAG);
}
getUidStatsLocked(uid, elapsedRealtimeMs, uptimeMs)
.noteStopSensor(sensor, elapsedRealtimeMs);
@@ -5498,10 +5021,8 @@
}
final int mappedUid = mapUid(uid);
if (mGpsNesting == 0) {
- mHistoryCur.states |= HistoryItem.STATE_GPS_ON_FLAG;
- if (DEBUG_HISTORY) Slog.v(TAG, "Start GPS to: "
- + Integer.toHexString(mHistoryCur.states));
- addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
+ mHistory.recordStateStartEvent(elapsedRealtimeMs, uptimeMs,
+ HistoryItem.STATE_GPS_ON_FLAG);
}
mGpsNesting++;
@@ -5526,10 +5047,8 @@
final int mappedUid = mapUid(uid);
mGpsNesting--;
if (mGpsNesting == 0) {
- mHistoryCur.states &= ~HistoryItem.STATE_GPS_ON_FLAG;
- if (DEBUG_HISTORY) Slog.v(TAG, "Stop GPS to: "
- + Integer.toHexString(mHistoryCur.states));
- addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
+ mHistory.recordStateStopEvent(elapsedRealtimeMs, uptimeMs,
+ HistoryItem.STATE_GPS_ON_FLAG);
stopAllGpsSignalQualityTimersLocked(-1, elapsedRealtimeMs);
mGpsSignalQualityBin = -1;
}
@@ -5562,12 +5081,9 @@
if(!mGpsSignalQualityTimer[signalLevel].isRunningLocked()) {
mGpsSignalQualityTimer[signalLevel].startRunningLocked(elapsedRealtimeMs);
}
- mHistoryCur.states2 = (mHistoryCur.states2&~HistoryItem.STATE2_GPS_SIGNAL_QUALITY_MASK)
- | (signalLevel << HistoryItem.STATE2_GPS_SIGNAL_QUALITY_SHIFT);
- addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
+ mHistory.recordGpsSignalQualityEvent(elapsedRealtimeMs, uptimeMs, signalLevel);
mGpsSignalQualityBin = signalLevel;
}
- return;
}
@GuardedBy("this")
@@ -5740,41 +5256,33 @@
}
}
- boolean updateHistory = false;
+ int startStates = 0;
+ int stopStates = 0;
if (Display.isDozeState(state) && !Display.isDozeState(oldState)) {
- mHistoryCur.states |= HistoryItem.STATE_SCREEN_DOZE_FLAG;
+ startStates |= HistoryItem.STATE_SCREEN_DOZE_FLAG;
mScreenDozeTimer.startRunningLocked(elapsedRealtimeMs);
- updateHistory = true;
} else if (Display.isDozeState(oldState) && !Display.isDozeState(state)) {
- mHistoryCur.states &= ~HistoryItem.STATE_SCREEN_DOZE_FLAG;
+ stopStates |= HistoryItem.STATE_SCREEN_DOZE_FLAG;
mScreenDozeTimer.stopRunningLocked(elapsedRealtimeMs);
- updateHistory = true;
}
if (Display.isOnState(state)) {
- mHistoryCur.states |= HistoryItem.STATE_SCREEN_ON_FLAG;
- if (DEBUG_HISTORY) Slog.v(TAG, "Screen on to: "
- + Integer.toHexString(mHistoryCur.states));
+ startStates |= HistoryItem.STATE_SCREEN_ON_FLAG;
mScreenOnTimer.startRunningLocked(elapsedRealtimeMs);
if (mScreenBrightnessBin >= 0) {
mScreenBrightnessTimer[mScreenBrightnessBin]
.startRunningLocked(elapsedRealtimeMs);
}
- updateHistory = true;
} else if (Display.isOnState(oldState)) {
- mHistoryCur.states &= ~HistoryItem.STATE_SCREEN_ON_FLAG;
- if (DEBUG_HISTORY) Slog.v(TAG, "Screen off to: "
- + Integer.toHexString(mHistoryCur.states));
+ stopStates |= HistoryItem.STATE_SCREEN_ON_FLAG;
mScreenOnTimer.stopRunningLocked(elapsedRealtimeMs);
if (mScreenBrightnessBin >= 0) {
mScreenBrightnessTimer[mScreenBrightnessBin]
.stopRunningLocked(elapsedRealtimeMs);
}
- updateHistory = true;
}
- if (updateHistory) {
- if (DEBUG_HISTORY) Slog.v(TAG, "Screen state to: "
- + Display.stateToString(state));
- addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
+ if (startStates != 0 || stopStates != 0) {
+ mHistory.recordStateChangeEvent(elapsedRealtimeMs, uptimeMs, startStates,
+ stopStates);
}
// Per screen state Cpu stats needed. Prepare to schedule an external sync.
@@ -5888,13 +5396,7 @@
long uptimeMs) {
if (mScreenBrightnessBin != overallBin) {
if (overallBin >= 0) {
- mHistoryCur.states = (mHistoryCur.states & ~HistoryItem.STATE_BRIGHTNESS_MASK)
- | (overallBin << HistoryItem.STATE_BRIGHTNESS_SHIFT);
- if (DEBUG_HISTORY) {
- Slog.v(TAG, "Screen brightness " + overallBin + " to: "
- + Integer.toHexString(mHistoryCur.states));
- }
- addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
+ mHistory.recordScreenBrightnessEvent(elapsedRealtimeMs, uptimeMs, overallBin);
}
if (mScreenState == Display.STATE_ON) {
if (mScreenBrightnessBin >= 0) {
@@ -5922,8 +5424,8 @@
@GuardedBy("this")
public void noteWakeUpLocked(String reason, int reasonUid,
long elapsedRealtimeMs, long uptimeMs) {
- addHistoryEventLocked(elapsedRealtimeMs, uptimeMs, HistoryItem.EVENT_SCREEN_WAKE_UP,
- reason, reasonUid);
+ mHistory.recordEvent(elapsedRealtimeMs, uptimeMs, HistoryItem.EVENT_SCREEN_WAKE_UP, reason,
+ reasonUid);
}
@GuardedBy("this")
@@ -5942,7 +5444,7 @@
@GuardedBy("this")
public void noteConnectivityChangedLocked(int type, String extra,
long elapsedRealtimeMs, long uptimeMs) {
- addHistoryEventLocked(elapsedRealtimeMs, uptimeMs, HistoryItem.EVENT_CONNECTIVITY_CHANGED,
+ mHistory.recordEvent(elapsedRealtimeMs, uptimeMs, HistoryItem.EVENT_CONNECTIVITY_CHANGED,
extra, type);
mNumConnectivityChange++;
}
@@ -5951,7 +5453,7 @@
private void noteMobileRadioApWakeupLocked(final long elapsedRealtimeMillis,
final long uptimeMillis, int uid) {
uid = mapUid(uid);
- addHistoryEventLocked(elapsedRealtimeMillis, uptimeMillis, HistoryItem.EVENT_WAKEUP_AP, "",
+ mHistory.recordEvent(elapsedRealtimeMillis, uptimeMillis, HistoryItem.EVENT_WAKEUP_AP, "",
uid);
getUidStatsLocked(uid, elapsedRealtimeMillis, uptimeMillis).noteMobileRadioApWakeupLocked();
}
@@ -5977,7 +5479,8 @@
}
mMobileRadioActiveStartTimeMs = realElapsedRealtimeMs = timestampNs / (1000 * 1000);
- mHistoryCur.states |= HistoryItem.STATE_MOBILE_RADIO_ACTIVE_FLAG;
+ mHistory.recordStateStartEvent(elapsedRealtimeMs, uptimeMs,
+ HistoryItem.STATE_MOBILE_RADIO_ACTIVE_FLAG);
} else {
realElapsedRealtimeMs = timestampNs / (1000*1000);
long lastUpdateTimeMs = mMobileRadioActiveStartTimeMs;
@@ -5989,11 +5492,9 @@
mMobileRadioActiveAdjustedTime.addCountLocked(elapsedRealtimeMs
- realElapsedRealtimeMs);
}
- mHistoryCur.states &= ~HistoryItem.STATE_MOBILE_RADIO_ACTIVE_FLAG;
+ mHistory.recordStateStopEvent(elapsedRealtimeMs, uptimeMs,
+ HistoryItem.STATE_MOBILE_RADIO_ACTIVE_FLAG);
}
- if (DEBUG_HISTORY) Slog.v(TAG, "Mobile network active " + active + " to: "
- + Integer.toHexString(mHistoryCur.states));
- addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
mMobileRadioPowerState = powerState;
// Inform current RatBatteryStats that the modem active state might have changed.
@@ -6043,17 +5544,14 @@
mCurStepMode = (mCurStepMode&~STEP_LEVEL_MODE_POWER_SAVE) | stepState;
mPowerSaveModeEnabled = enabled;
if (enabled) {
- mHistoryCur.states2 |= HistoryItem.STATE2_POWER_SAVE_FLAG;
- if (DEBUG_HISTORY) Slog.v(TAG, "Power save mode enabled to: "
- + Integer.toHexString(mHistoryCur.states2));
+ mHistory.recordState2StartEvent(elapsedRealtimeMs, uptimeMs,
+ HistoryItem.STATE2_POWER_SAVE_FLAG);
mPowerSaveModeEnabledTimer.startRunningLocked(elapsedRealtimeMs);
} else {
- mHistoryCur.states2 &= ~HistoryItem.STATE2_POWER_SAVE_FLAG;
- if (DEBUG_HISTORY) Slog.v(TAG, "Power save mode disabled to: "
- + Integer.toHexString(mHistoryCur.states2));
+ mHistory.recordState2StopEvent(elapsedRealtimeMs, uptimeMs,
+ HistoryItem.STATE2_POWER_SAVE_FLAG);
mPowerSaveModeEnabledTimer.stopRunningLocked(elapsedRealtimeMs);
}
- addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
FrameworkStatsLog.write(FrameworkStatsLog.BATTERY_SAVER_MODE_STATE_CHANGED,
enabled
? FrameworkStatsLog.BATTERY_SAVER_MODE_STATE_CHANGED__STATE__ON
@@ -6077,7 +5575,7 @@
nowLightIdling = true;
}
if (activeReason != null && (mDeviceIdling || mDeviceLightIdling)) {
- addHistoryEventLocked(elapsedRealtimeMs, uptimeMs, HistoryItem.EVENT_ACTIVE,
+ mHistory.recordEvent(elapsedRealtimeMs, uptimeMs, HistoryItem.EVENT_ACTIVE,
activeReason, activeUid);
}
if (mDeviceIdling != nowIdling || mDeviceLightIdling != nowLightIdling) {
@@ -6107,11 +5605,7 @@
}
}
if (mDeviceIdleMode != mode) {
- mHistoryCur.states2 = (mHistoryCur.states2 & ~HistoryItem.STATE2_DEVICE_IDLE_MASK)
- | (mode << HistoryItem.STATE2_DEVICE_IDLE_SHIFT);
- if (DEBUG_HISTORY) Slog.v(TAG, "Device idle mode changed to: "
- + Integer.toHexString(mHistoryCur.states2));
- addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
+ mHistory.recordDeviceIdleEvent(elapsedRealtimeMs, uptimeMs, mode);
long lastDuration = elapsedRealtimeMs - mLastIdleTimeStartMs;
mLastIdleTimeStartMs = elapsedRealtimeMs;
if (mDeviceIdleMode == DEVICE_IDLE_MODE_LIGHT) {
@@ -6139,7 +5633,7 @@
public void notePackageInstalledLocked(String pkgName, long versionCode,
long elapsedRealtimeMs, long uptimeMs) {
// XXX need to figure out what to do with long version codes.
- addHistoryEventLocked(elapsedRealtimeMs, uptimeMs, HistoryItem.EVENT_PACKAGE_INSTALLED,
+ mHistory.recordEvent(elapsedRealtimeMs, uptimeMs, HistoryItem.EVENT_PACKAGE_INSTALLED,
pkgName, (int)versionCode);
PackageChange pc = new PackageChange();
pc.mPackageName = pkgName;
@@ -6151,8 +5645,8 @@
@GuardedBy("this")
public void notePackageUninstalledLocked(String pkgName,
long elapsedRealtimeMs, long uptimeMs) {
- addHistoryEventLocked(elapsedRealtimeMs, uptimeMs,
- HistoryItem.EVENT_PACKAGE_UNINSTALLED, pkgName, 0);
+ mHistory.recordEvent(elapsedRealtimeMs, uptimeMs, HistoryItem.EVENT_PACKAGE_UNINSTALLED,
+ pkgName, 0);
PackageChange pc = new PackageChange();
pc.mPackageName = pkgName;
pc.mUpdate = true;
@@ -6181,10 +5675,8 @@
@GuardedBy("this")
public void notePhoneOnLocked(long elapsedRealtimeMs, long uptimeMs) {
if (!mPhoneOn) {
- mHistoryCur.states2 |= HistoryItem.STATE2_PHONE_IN_CALL_FLAG;
- if (DEBUG_HISTORY) Slog.v(TAG, "Phone on to: "
- + Integer.toHexString(mHistoryCur.states));
- addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
+ mHistory.recordState2StartEvent(elapsedRealtimeMs, uptimeMs,
+ HistoryItem.STATE2_PHONE_IN_CALL_FLAG);
mPhoneOn = true;
mPhoneOnTimer.startRunningLocked(elapsedRealtimeMs);
}
@@ -6193,10 +5685,8 @@
@GuardedBy("this")
public void notePhoneOffLocked(long elapsedRealtimeMs, long uptimeMs) {
if (mPhoneOn) {
- mHistoryCur.states2 &= ~HistoryItem.STATE2_PHONE_IN_CALL_FLAG;
- if (DEBUG_HISTORY) Slog.v(TAG, "Phone off to: "
- + Integer.toHexString(mHistoryCur.states));
- addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
+ mHistory.recordState2StopEvent(elapsedRealtimeMs, uptimeMs,
+ HistoryItem.STATE2_PHONE_IN_CALL_FLAG);
mPhoneOn = false;
mPhoneOnTimer.stopRunningLocked(elapsedRealtimeMs);
}
@@ -6234,11 +5724,12 @@
if (mUsbDataState != newState) {
mUsbDataState = newState;
if (connected) {
- mHistoryCur.states2 |= HistoryItem.STATE2_USB_DATA_LINK_FLAG;
+ mHistory.recordState2StartEvent(elapsedRealtimeMs, uptimeMs,
+ HistoryItem.STATE2_USB_DATA_LINK_FLAG);
} else {
- mHistoryCur.states2 &= ~HistoryItem.STATE2_USB_DATA_LINK_FLAG;
+ mHistory.recordState2StopEvent(elapsedRealtimeMs, uptimeMs,
+ HistoryItem.STATE2_USB_DATA_LINK_FLAG);
}
- addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
}
}
@@ -6259,6 +5750,10 @@
long elapsedRealtimeMs, long uptimeMs) {
boolean scanning = false;
boolean newHistory = false;
+ int addStateFlag = 0;
+ int removeStateFlag = 0;
+ int newState = -1;
+ int newSignalStrength = -1;
mPhoneServiceStateRaw = state;
mPhoneSimStateRaw = simState;
@@ -6287,10 +5782,8 @@
scanning = true;
strengthBin = CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN;
if (!mPhoneSignalScanningTimer.isRunningLocked()) {
- mHistoryCur.states |= HistoryItem.STATE_PHONE_SCANNING_FLAG;
+ addStateFlag = HistoryItem.STATE_PHONE_SCANNING_FLAG;
newHistory = true;
- if (DEBUG_HISTORY) Slog.v(TAG, "Phone started scanning to: "
- + Integer.toHexString(mHistoryCur.states));
mPhoneSignalScanningTimer.startRunningLocked(elapsedRealtimeMs);
FrameworkStatsLog.write(FrameworkStatsLog.PHONE_SERVICE_STATE_CHANGED, state,
simState, strengthBin);
@@ -6300,9 +5793,7 @@
if (!scanning) {
// If we are no longer scanning, then stop the scanning timer.
if (mPhoneSignalScanningTimer.isRunningLocked()) {
- mHistoryCur.states &= ~HistoryItem.STATE_PHONE_SCANNING_FLAG;
- if (DEBUG_HISTORY) Slog.v(TAG, "Phone stopped scanning to: "
- + Integer.toHexString(mHistoryCur.states));
+ removeStateFlag = HistoryItem.STATE_PHONE_SCANNING_FLAG;
newHistory = true;
mPhoneSignalScanningTimer.stopRunningLocked(elapsedRealtimeMs);
FrameworkStatsLog.write(FrameworkStatsLog.PHONE_SERVICE_STATE_CHANGED, state,
@@ -6311,10 +5802,7 @@
}
if (mPhoneServiceState != state) {
- mHistoryCur.states = (mHistoryCur.states&~HistoryItem.STATE_PHONE_STATE_MASK)
- | (state << HistoryItem.STATE_PHONE_STATE_SHIFT);
- if (DEBUG_HISTORY) Slog.v(TAG, "Phone state " + state + " to: "
- + Integer.toHexString(mHistoryCur.states));
+ newState = state;
newHistory = true;
mPhoneServiceState = state;
}
@@ -6328,11 +5816,7 @@
if (!mPhoneSignalStrengthsTimer[strengthBin].isRunningLocked()) {
mPhoneSignalStrengthsTimer[strengthBin].startRunningLocked(elapsedRealtimeMs);
}
- mHistoryCur.states =
- (mHistoryCur.states & ~HistoryItem.STATE_PHONE_SIGNAL_STRENGTH_MASK)
- | (strengthBin << HistoryItem.STATE_PHONE_SIGNAL_STRENGTH_SHIFT);
- if (DEBUG_HISTORY) Slog.v(TAG, "Signal strength " + strengthBin + " to: "
- + Integer.toHexString(mHistoryCur.states));
+ newSignalStrength = strengthBin;
newHistory = true;
FrameworkStatsLog.write(
FrameworkStatsLog.PHONE_SIGNAL_STRENGTH_CHANGED, strengthBin);
@@ -6343,7 +5827,8 @@
}
if (newHistory) {
- addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
+ mHistory.recordPhoneStateChangeEvent(elapsedRealtimeMs, uptimeMs,
+ addStateFlag, removeStateFlag, newState, newSignalStrength);
}
}
@@ -6467,11 +5952,7 @@
if (DEBUG) Log.i(TAG, "Phone Data Connection -> " + dataType + " = " + hasData);
if (mPhoneDataConnectionType != bin) {
- mHistoryCur.states = (mHistoryCur.states&~HistoryItem.STATE_DATA_CONNECTION_MASK)
- | (bin << HistoryItem.STATE_DATA_CONNECTION_SHIFT);
- if (DEBUG_HISTORY) Slog.v(TAG, "Data connection " + bin + " to: "
- + Integer.toHexString(mHistoryCur.states));
- addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
+ mHistory.recordDataConnectionTypeChangeEvent(elapsedRealtimeMs, uptimeMs, bin);
if (mPhoneDataConnectionType >= 0) {
mPhoneDataConnectionsTimer[mPhoneDataConnectionType].stopRunningLocked(
elapsedRealtimeMs);
@@ -6544,10 +6025,8 @@
@GuardedBy("this")
public void noteWifiOnLocked(long elapsedRealtimeMs, long uptimeMs) {
if (!mWifiOn) {
- mHistoryCur.states2 |= HistoryItem.STATE2_WIFI_ON_FLAG;
- if (DEBUG_HISTORY) Slog.v(TAG, "WIFI on to: "
- + Integer.toHexString(mHistoryCur.states));
- addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
+ mHistory.recordState2StartEvent(elapsedRealtimeMs, uptimeMs,
+ HistoryItem.STATE2_WIFI_ON_FLAG);
mWifiOn = true;
mWifiOnTimer.startRunningLocked(elapsedRealtimeMs);
scheduleSyncExternalStatsLocked("wifi-off", ExternalStatsSync.UPDATE_WIFI);
@@ -6557,10 +6036,8 @@
@GuardedBy("this")
public void noteWifiOffLocked(long elapsedRealtimeMs, long uptimeMs) {
if (mWifiOn) {
- mHistoryCur.states2 &= ~HistoryItem.STATE2_WIFI_ON_FLAG;
- if (DEBUG_HISTORY) Slog.v(TAG, "WIFI off to: "
- + Integer.toHexString(mHistoryCur.states));
- addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
+ mHistory.recordState2StopEvent(elapsedRealtimeMs, uptimeMs,
+ HistoryItem.STATE2_WIFI_ON_FLAG);
mWifiOn = false;
mWifiOnTimer.stopRunningLocked(elapsedRealtimeMs);
scheduleSyncExternalStatsLocked("wifi-on", ExternalStatsSync.UPDATE_WIFI);
@@ -6571,10 +6048,8 @@
public void noteAudioOnLocked(int uid, long elapsedRealtimeMs, long uptimeMs) {
uid = mapUid(uid);
if (mAudioOnNesting == 0) {
- mHistoryCur.states |= HistoryItem.STATE_AUDIO_ON_FLAG;
- if (DEBUG_HISTORY) Slog.v(TAG, "Audio on to: "
- + Integer.toHexString(mHistoryCur.states));
- addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
+ mHistory.recordStateStartEvent(elapsedRealtimeMs, uptimeMs,
+ HistoryItem.STATE_AUDIO_ON_FLAG);
mAudioOnTimer.startRunningLocked(elapsedRealtimeMs);
}
mAudioOnNesting++;
@@ -6589,10 +6064,8 @@
}
uid = mapUid(uid);
if (--mAudioOnNesting == 0) {
- mHistoryCur.states &= ~HistoryItem.STATE_AUDIO_ON_FLAG;
- if (DEBUG_HISTORY) Slog.v(TAG, "Audio off to: "
- + Integer.toHexString(mHistoryCur.states));
- addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
+ mHistory.recordStateStopEvent(elapsedRealtimeMs, uptimeMs,
+ HistoryItem.STATE_AUDIO_ON_FLAG);
mAudioOnTimer.stopRunningLocked(elapsedRealtimeMs);
}
getUidStatsLocked(uid, elapsedRealtimeMs, uptimeMs)
@@ -6603,10 +6076,8 @@
public void noteVideoOnLocked(int uid, long elapsedRealtimeMs, long uptimeMs) {
uid = mapUid(uid);
if (mVideoOnNesting == 0) {
- mHistoryCur.states2 |= HistoryItem.STATE2_VIDEO_ON_FLAG;
- if (DEBUG_HISTORY) Slog.v(TAG, "Video on to: "
- + Integer.toHexString(mHistoryCur.states));
- addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
+ mHistory.recordState2StartEvent(elapsedRealtimeMs, uptimeMs,
+ HistoryItem.STATE2_VIDEO_ON_FLAG);
mVideoOnTimer.startRunningLocked(elapsedRealtimeMs);
}
mVideoOnNesting++;
@@ -6621,10 +6092,8 @@
}
uid = mapUid(uid);
if (--mVideoOnNesting == 0) {
- mHistoryCur.states2 &= ~HistoryItem.STATE2_VIDEO_ON_FLAG;
- if (DEBUG_HISTORY) Slog.v(TAG, "Video off to: "
- + Integer.toHexString(mHistoryCur.states));
- addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
+ mHistory.recordState2StopEvent(elapsedRealtimeMs, uptimeMs,
+ HistoryItem.STATE2_VIDEO_ON_FLAG);
mVideoOnTimer.stopRunningLocked(elapsedRealtimeMs);
}
getUidStatsLocked(uid, elapsedRealtimeMs, uptimeMs)
@@ -6635,10 +6104,8 @@
public void noteResetAudioLocked(long elapsedRealtimeMs, long uptimeMs) {
if (mAudioOnNesting > 0) {
mAudioOnNesting = 0;
- mHistoryCur.states &= ~HistoryItem.STATE_AUDIO_ON_FLAG;
- if (DEBUG_HISTORY) Slog.v(TAG, "Audio off to: "
- + Integer.toHexString(mHistoryCur.states));
- addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
+ mHistory.recordStateStopEvent(elapsedRealtimeMs, uptimeMs,
+ HistoryItem.STATE_AUDIO_ON_FLAG);
mAudioOnTimer.stopAllRunningLocked(elapsedRealtimeMs);
for (int i=0; i<mUidStats.size(); i++) {
BatteryStatsImpl.Uid uid = mUidStats.valueAt(i);
@@ -6651,10 +6118,8 @@
public void noteResetVideoLocked(long elapsedRealtimeMs, long uptimeMs) {
if (mVideoOnNesting > 0) {
mVideoOnNesting = 0;
- mHistoryCur.states2 &= ~HistoryItem.STATE2_VIDEO_ON_FLAG;
- if (DEBUG_HISTORY) Slog.v(TAG, "Video off to: "
- + Integer.toHexString(mHistoryCur.states));
- addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
+ mHistory.recordState2StopEvent(elapsedRealtimeMs, uptimeMs,
+ HistoryItem.STATE2_VIDEO_ON_FLAG);
mVideoOnTimer.stopAllRunningLocked(elapsedRealtimeMs);
for (int i=0; i<mUidStats.size(); i++) {
BatteryStatsImpl.Uid uid = mUidStats.valueAt(i);
@@ -6706,10 +6171,8 @@
public void noteFlashlightOnLocked(int uid, long elapsedRealtimeMs, long uptimeMs) {
uid = mapUid(uid);
if (mFlashlightOnNesting++ == 0) {
- mHistoryCur.states2 |= HistoryItem.STATE2_FLASHLIGHT_FLAG;
- if (DEBUG_HISTORY) Slog.v(TAG, "Flashlight on to: "
- + Integer.toHexString(mHistoryCur.states2));
- addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
+ mHistory.recordState2StartEvent(elapsedRealtimeMs, uptimeMs,
+ HistoryItem.STATE2_FLASHLIGHT_FLAG);
mFlashlightOnTimer.startRunningLocked(elapsedRealtimeMs);
}
getUidStatsLocked(uid, elapsedRealtimeMs, uptimeMs)
@@ -6723,10 +6186,8 @@
}
uid = mapUid(uid);
if (--mFlashlightOnNesting == 0) {
- mHistoryCur.states2 &= ~HistoryItem.STATE2_FLASHLIGHT_FLAG;
- if (DEBUG_HISTORY) Slog.v(TAG, "Flashlight off to: "
- + Integer.toHexString(mHistoryCur.states2));
- addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
+ mHistory.recordState2StopEvent(elapsedRealtimeMs, uptimeMs,
+ HistoryItem.STATE2_FLASHLIGHT_FLAG);
mFlashlightOnTimer.stopRunningLocked(elapsedRealtimeMs);
}
getUidStatsLocked(uid, elapsedRealtimeMs, uptimeMs)
@@ -6737,10 +6198,8 @@
public void noteCameraOnLocked(int uid, long elapsedRealtimeMs, long uptimeMs) {
uid = mapUid(uid);
if (mCameraOnNesting++ == 0) {
- mHistoryCur.states2 |= HistoryItem.STATE2_CAMERA_FLAG;
- if (DEBUG_HISTORY) Slog.v(TAG, "Camera on to: "
- + Integer.toHexString(mHistoryCur.states2));
- addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
+ mHistory.recordState2StartEvent(elapsedRealtimeMs, uptimeMs,
+ HistoryItem.STATE2_CAMERA_FLAG);
mCameraOnTimer.startRunningLocked(elapsedRealtimeMs);
}
getUidStatsLocked(uid, elapsedRealtimeMs, uptimeMs)
@@ -6754,10 +6213,8 @@
}
uid = mapUid(uid);
if (--mCameraOnNesting == 0) {
- mHistoryCur.states2 &= ~HistoryItem.STATE2_CAMERA_FLAG;
- if (DEBUG_HISTORY) Slog.v(TAG, "Camera off to: "
- + Integer.toHexString(mHistoryCur.states2));
- addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
+ mHistory.recordState2StopEvent(elapsedRealtimeMs, uptimeMs,
+ HistoryItem.STATE2_CAMERA_FLAG);
mCameraOnTimer.stopRunningLocked(elapsedRealtimeMs);
}
getUidStatsLocked(uid, elapsedRealtimeMs, uptimeMs)
@@ -6768,10 +6225,8 @@
public void noteResetCameraLocked(long elapsedRealtimeMs, long uptimeMs) {
if (mCameraOnNesting > 0) {
mCameraOnNesting = 0;
- mHistoryCur.states2 &= ~HistoryItem.STATE2_CAMERA_FLAG;
- if (DEBUG_HISTORY) Slog.v(TAG, "Camera off to: "
- + Integer.toHexString(mHistoryCur.states2));
- addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
+ mHistory.recordState2StopEvent(elapsedRealtimeMs, uptimeMs,
+ HistoryItem.STATE2_CAMERA_FLAG);
mCameraOnTimer.stopAllRunningLocked(elapsedRealtimeMs);
for (int i=0; i<mUidStats.size(); i++) {
BatteryStatsImpl.Uid uid = mUidStats.valueAt(i);
@@ -6784,10 +6239,8 @@
public void noteResetFlashlightLocked(long elapsedRealtimeMs, long uptimeMs) {
if (mFlashlightOnNesting > 0) {
mFlashlightOnNesting = 0;
- mHistoryCur.states2 &= ~HistoryItem.STATE2_FLASHLIGHT_FLAG;
- if (DEBUG_HISTORY) Slog.v(TAG, "Flashlight off to: "
- + Integer.toHexString(mHistoryCur.states2));
- addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
+ mHistory.recordState2StopEvent(elapsedRealtimeMs, uptimeMs,
+ HistoryItem.STATE2_FLASHLIGHT_FLAG);
mFlashlightOnTimer.stopAllRunningLocked(elapsedRealtimeMs);
for (int i=0; i<mUidStats.size(); i++) {
BatteryStatsImpl.Uid uid = mUidStats.valueAt(i);
@@ -6804,10 +6257,8 @@
}
uid = mapUid(uid);
if (mBluetoothScanNesting == 0) {
- mHistoryCur.states2 |= HistoryItem.STATE2_BLUETOOTH_SCAN_FLAG;
- if (DEBUG_HISTORY) Slog.v(TAG, "BLE scan started for: "
- + Integer.toHexString(mHistoryCur.states2));
- addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
+ mHistory.recordState2StartEvent(elapsedRealtimeMs, uptimeMs,
+ HistoryItem.STATE2_BLUETOOTH_SCAN_FLAG);
mBluetoothScanTimer.startRunningLocked(elapsedRealtimeMs);
}
mBluetoothScanNesting++;
@@ -6848,10 +6299,8 @@
uid = mapUid(uid);
mBluetoothScanNesting--;
if (mBluetoothScanNesting == 0) {
- mHistoryCur.states2 &= ~HistoryItem.STATE2_BLUETOOTH_SCAN_FLAG;
- if (DEBUG_HISTORY) Slog.v(TAG, "BLE scan stopped for: "
- + Integer.toHexString(mHistoryCur.states2));
- addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
+ mHistory.recordState2StopEvent(elapsedRealtimeMs, uptimeMs,
+ HistoryItem.STATE2_BLUETOOTH_SCAN_FLAG);
mBluetoothScanTimer.stopRunningLocked(elapsedRealtimeMs);
}
getUidStatsLocked(uid, elapsedRealtimeMs, uptimeMs)
@@ -6886,10 +6335,8 @@
public void noteResetBluetoothScanLocked(long elapsedRealtimeMs, long uptimeMs) {
if (mBluetoothScanNesting > 0) {
mBluetoothScanNesting = 0;
- mHistoryCur.states2 &= ~HistoryItem.STATE2_BLUETOOTH_SCAN_FLAG;
- if (DEBUG_HISTORY) Slog.v(TAG, "BLE can stopped for: "
- + Integer.toHexString(mHistoryCur.states2));
- addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
+ mHistory.recordState2StopEvent(elapsedRealtimeMs, uptimeMs,
+ HistoryItem.STATE2_BLUETOOTH_SCAN_FLAG);
mBluetoothScanTimer.stopAllRunningLocked(elapsedRealtimeMs);
for (int i=0; i<mUidStats.size(); i++) {
BatteryStatsImpl.Uid uid = mUidStats.valueAt(i);
@@ -6929,7 +6376,7 @@
private void noteWifiRadioApWakeupLocked(final long elapsedRealtimeMillis,
final long uptimeMillis, int uid) {
uid = mapUid(uid);
- addHistoryEventLocked(elapsedRealtimeMillis, uptimeMillis, HistoryItem.EVENT_WAKEUP_AP, "",
+ mHistory.recordEvent(elapsedRealtimeMillis, uptimeMillis, HistoryItem.EVENT_WAKEUP_AP, "",
uid);
getUidStatsLocked(uid, elapsedRealtimeMillis, uptimeMillis).noteWifiRadioApWakeupLocked();
}
@@ -6945,15 +6392,14 @@
if (uid > 0) {
noteWifiRadioApWakeupLocked(elapsedRealtimeMs, uptimeMs, uid);
}
- mHistoryCur.states |= HistoryItem.STATE_WIFI_RADIO_ACTIVE_FLAG;
+ mHistory.recordStateStartEvent(elapsedRealtimeMs, uptimeMs,
+ HistoryItem.STATE_WIFI_RADIO_ACTIVE_FLAG);
mWifiActiveTimer.startRunningLocked(elapsedRealtimeMs);
} else {
- mHistoryCur.states &= ~HistoryItem.STATE_WIFI_RADIO_ACTIVE_FLAG;
+ mHistory.recordStateStopEvent(elapsedRealtimeMs, uptimeMs,
+ HistoryItem.STATE_WIFI_RADIO_ACTIVE_FLAG);
mWifiActiveTimer.stopRunningLocked(timestampNs / (1000 * 1000));
}
- if (DEBUG_HISTORY) Slog.v(TAG, "Wifi network active " + active + " to: "
- + Integer.toHexString(mHistoryCur.states));
- addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
mWifiRadioPowerState = powerState;
}
}
@@ -6961,10 +6407,8 @@
@GuardedBy("this")
public void noteWifiRunningLocked(WorkSource ws, long elapsedRealtimeMs, long uptimeMs) {
if (!mGlobalWifiRunning) {
- mHistoryCur.states2 |= HistoryItem.STATE2_WIFI_RUNNING_FLAG;
- if (DEBUG_HISTORY) Slog.v(TAG, "WIFI running to: "
- + Integer.toHexString(mHistoryCur.states));
- addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
+ mHistory.recordState2StartEvent(elapsedRealtimeMs, uptimeMs,
+ HistoryItem.STATE2_WIFI_RUNNING_FLAG);
mGlobalWifiRunning = true;
mGlobalWifiRunningTimer.startRunningLocked(elapsedRealtimeMs);
int N = ws.size();
@@ -7032,10 +6476,8 @@
@GuardedBy("this")
public void noteWifiStoppedLocked(WorkSource ws, long elapsedRealtimeMs, long uptimeMs) {
if (mGlobalWifiRunning) {
- mHistoryCur.states2 &= ~HistoryItem.STATE2_WIFI_RUNNING_FLAG;
- if (DEBUG_HISTORY) Slog.v(TAG, "WIFI stopped to: "
- + Integer.toHexString(mHistoryCur.states));
- addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
+ mHistory.recordState2StopEvent(elapsedRealtimeMs, uptimeMs,
+ HistoryItem.STATE2_WIFI_RUNNING_FLAG);
mGlobalWifiRunning = false;
mGlobalWifiRunningTimer.stopRunningLocked(elapsedRealtimeMs);
int N = ws.size();
@@ -7083,12 +6525,7 @@
}
mWifiSupplState = supplState;
mWifiSupplStateTimer[supplState].startRunningLocked(elapsedRealtimeMs);
- mHistoryCur.states2 =
- (mHistoryCur.states2&~HistoryItem.STATE2_WIFI_SUPPL_STATE_MASK)
- | (supplState << HistoryItem.STATE2_WIFI_SUPPL_STATE_SHIFT);
- if (DEBUG_HISTORY) Slog.v(TAG, "Wifi suppl state " + supplState + " to: "
- + Integer.toHexString(mHistoryCur.states2));
- addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
+ mHistory.recordWifiSupplicantStateChangeEvent(elapsedRealtimeMs, uptimeMs, supplState);
}
}
@@ -7117,12 +6554,8 @@
if (!mWifiSignalStrengthsTimer[strengthBin].isRunningLocked()) {
mWifiSignalStrengthsTimer[strengthBin].startRunningLocked(elapsedRealtimeMs);
}
- mHistoryCur.states2 =
- (mHistoryCur.states2&~HistoryItem.STATE2_WIFI_SIGNAL_STRENGTH_MASK)
- | (strengthBin << HistoryItem.STATE2_WIFI_SIGNAL_STRENGTH_SHIFT);
- if (DEBUG_HISTORY) Slog.v(TAG, "Wifi signal strength " + strengthBin + " to: "
- + Integer.toHexString(mHistoryCur.states2));
- addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
+ mHistory.recordWifiSignalStrengthChangeEvent(elapsedRealtimeMs, uptimeMs,
+ strengthBin);
} else {
stopAllWifiSignalStrengthTimersLocked(-1, elapsedRealtimeMs);
}
@@ -7135,10 +6568,8 @@
@GuardedBy("this")
public void noteFullWifiLockAcquiredLocked(int uid, long elapsedRealtimeMs, long uptimeMs) {
if (mWifiFullLockNesting == 0) {
- mHistoryCur.states |= HistoryItem.STATE_WIFI_FULL_LOCK_FLAG;
- if (DEBUG_HISTORY) Slog.v(TAG, "WIFI full lock on to: "
- + Integer.toHexString(mHistoryCur.states));
- addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
+ mHistory.recordStateStartEvent(elapsedRealtimeMs, uptimeMs,
+ HistoryItem.STATE_WIFI_FULL_LOCK_FLAG);
}
mWifiFullLockNesting++;
getUidStatsLocked(uid, elapsedRealtimeMs, uptimeMs)
@@ -7149,10 +6580,8 @@
public void noteFullWifiLockReleasedLocked(int uid, long elapsedRealtimeMs, long uptimeMs) {
mWifiFullLockNesting--;
if (mWifiFullLockNesting == 0) {
- mHistoryCur.states &= ~HistoryItem.STATE_WIFI_FULL_LOCK_FLAG;
- if (DEBUG_HISTORY) Slog.v(TAG, "WIFI full lock off to: "
- + Integer.toHexString(mHistoryCur.states));
- addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
+ mHistory.recordStateStopEvent(elapsedRealtimeMs, uptimeMs,
+ HistoryItem.STATE_WIFI_FULL_LOCK_FLAG);
}
getUidStatsLocked(uid, elapsedRealtimeMs, uptimeMs)
.noteFullWifiLockReleasedLocked(elapsedRealtimeMs);
@@ -7168,10 +6597,8 @@
@GuardedBy("this")
public void noteWifiScanStartedLocked(int uid, long elapsedRealtimeMs, long uptimeMs) {
if (mWifiScanNesting == 0) {
- mHistoryCur.states |= HistoryItem.STATE_WIFI_SCAN_FLAG;
- if (DEBUG_HISTORY) Slog.v(TAG, "WIFI scan started for: "
- + Integer.toHexString(mHistoryCur.states));
- addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
+ mHistory.recordStateStartEvent(elapsedRealtimeMs, uptimeMs,
+ HistoryItem.STATE_WIFI_SCAN_FLAG);
}
mWifiScanNesting++;
getUidStatsLocked(uid, elapsedRealtimeMs, uptimeMs)
@@ -7187,10 +6614,8 @@
public void noteWifiScanStoppedLocked(int uid, long elapsedRealtimeMs, long uptimeMs) {
mWifiScanNesting--;
if (mWifiScanNesting == 0) {
- mHistoryCur.states &= ~HistoryItem.STATE_WIFI_SCAN_FLAG;
- if (DEBUG_HISTORY) Slog.v(TAG, "WIFI scan stopped for: "
- + Integer.toHexString(mHistoryCur.states));
- addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
+ mHistory.recordStateStopEvent(elapsedRealtimeMs, uptimeMs,
+ HistoryItem.STATE_WIFI_SCAN_FLAG);
}
getUidStatsLocked(uid, elapsedRealtimeMs, uptimeMs)
.noteWifiScanStoppedLocked(elapsedRealtimeMs);
@@ -7215,14 +6640,10 @@
public void noteWifiMulticastEnabledLocked(int uid, long elapsedRealtimeMs, long uptimeMs) {
uid = mapUid(uid);
if (mWifiMulticastNesting == 0) {
- mHistoryCur.states |= HistoryItem.STATE_WIFI_MULTICAST_ON_FLAG;
- if (DEBUG_HISTORY) Slog.v(TAG, "WIFI multicast on to: "
- + Integer.toHexString(mHistoryCur.states));
- addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
-
+ mHistory.recordStateStartEvent(elapsedRealtimeMs, uptimeMs,
+ HistoryItem.STATE_WIFI_MULTICAST_ON_FLAG);
// Start Wifi Multicast overall timer
if (!mWifiMulticastWakelockTimer.isRunningLocked()) {
- if (DEBUG_HISTORY) Slog.v(TAG, "WiFi Multicast Overall Timer Started");
mWifiMulticastWakelockTimer.startRunningLocked(elapsedRealtimeMs);
}
}
@@ -7236,14 +6657,12 @@
uid = mapUid(uid);
mWifiMulticastNesting--;
if (mWifiMulticastNesting == 0) {
- mHistoryCur.states &= ~HistoryItem.STATE_WIFI_MULTICAST_ON_FLAG;
- if (DEBUG_HISTORY) Slog.v(TAG, "WIFI multicast off to: "
- + Integer.toHexString(mHistoryCur.states));
- addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
+ mHistory.recordStateStopEvent(elapsedRealtimeMs, uptimeMs,
+ HistoryItem.STATE_WIFI_MULTICAST_ON_FLAG);
// Stop Wifi Multicast overall timer
if (mWifiMulticastWakelockTimer.isRunningLocked()) {
- if (DEBUG_HISTORY) Slog.v(TAG, "Multicast Overall Timer Stopped");
+ if (DEBUG) Slog.v(TAG, "Multicast Overall Timer Stopped");
mWifiMulticastWakelockTimer.stopRunningLocked(elapsedRealtimeMs);
}
}
@@ -7995,8 +7414,9 @@
// If the start clock time has changed by more than a year, then presumably
// the previous time was completely bogus. So we are going to figure out a
// new time based on how much time has elapsed since we started counting.
- recordCurrentTimeChangeLocked(currentTimeMs, mClock.elapsedRealtime(),
- mClock.uptimeMillis());
+ mHistory.recordCurrentTimeChange(mClock.elapsedRealtime(), mClock.uptimeMillis(),
+ currentTimeMs
+ );
return currentTimeMs - (mClock.elapsedRealtime() - (mRealtimeStartUs / 1000));
}
return mStartClockTimeMs;
@@ -11228,18 +10648,19 @@
UserInfoProvider userInfoProvider) {
init(clock);
+ mHandler = new MyHandler(handler.getLooper());
+ mConstants = new Constants(mHandler);
+
if (systemDir == null) {
mStatsFile = null;
- mBatteryStatsHistory = new BatteryStatsHistory(mHistoryBuffer);
+ mHistory = new BatteryStatsHistory(mStepDetailsCalculator, mClock);
} else {
mStatsFile = new AtomicFile(new File(systemDir, "batterystats.bin"));
- mBatteryStatsHistory = new BatteryStatsHistory(mHistoryBuffer, systemDir,
- this::getMaxHistoryFiles);
+ mHistory = new BatteryStatsHistory(systemDir, mConstants.MAX_HISTORY_FILES,
+ mConstants.MAX_HISTORY_BUFFER, mStepDetailsCalculator, mClock);
}
mCheckinFile = new AtomicFile(new File(systemDir, "batterystats-checkin.bin"));
mDailyFile = new AtomicFile(new File(systemDir, "batterystats-daily.xml"));
- mHandler = new MyHandler(handler.getLooper());
- mConstants = new Constants(mHandler);
mStartCount++;
initTimersAndCounters();
mOnBattery = mOnBatteryInternal = false;
@@ -11248,7 +10669,6 @@
initTimes(uptimeUs, realtimeUs);
mStartPlatformVersion = mEndPlatformVersion = Build.ID;
initDischarge(realtimeUs);
- clearHistoryLocked();
updateDailyDeadlineLocked();
mPlatformIdleStateCallback = cb;
mMeasuredEnergyRetriever = energyStatsCb;
@@ -11259,12 +10679,6 @@
FrameworkStatsLog.write(FrameworkStatsLog.DEVICE_IDLE_MODE_STATE_CHANGED, mDeviceIdleMode);
}
- private int getMaxHistoryFiles() {
- synchronized (this) {
- return mConstants.MAX_HISTORY_FILES;
- }
- }
-
@VisibleForTesting
protected void initTimersAndCounters() {
mScreenOnTimer = new StopwatchTimer(mClock, null, -1, null, mOnBatteryTimeBase);
@@ -11346,7 +10760,7 @@
mDischargeUnplugLevel = 0;
mDischargePlugLevel = -1;
mDischargeCurrentLevel = 0;
- mCurrentBatteryLevel = 0;
+ mBatteryLevel = 0;
}
public void setPowerProfileLocked(PowerProfile profile) {
@@ -11733,7 +11147,7 @@
}
public int getHistoryUsedSize() {
- return mBatteryStatsHistory.getHistoryUsedSize();
+ return mHistory.getHistoryUsedSize();
}
@Override
@@ -11747,43 +11161,27 @@
*/
@VisibleForTesting
public BatteryStatsHistoryIterator createBatteryStatsHistoryIterator() {
- return new BatteryStatsHistoryIterator(mBatteryStatsHistory);
+ return mHistory.iterate();
}
@Override
public int getHistoryStringPoolSize() {
- return mHistoryTagPool.size();
+ return mHistory.getHistoryStringPoolSize();
}
@Override
public int getHistoryStringPoolBytes() {
- return mNumHistoryTagChars;
+ return mHistory.getHistoryStringPoolBytes();
}
@Override
public String getHistoryTagPoolString(int index) {
- ensureHistoryTagArray();
- HistoryTag historyTag = mHistoryTags.get(index);
- return historyTag != null ? historyTag.string : null;
+ return mHistory.getHistoryTagPoolString(index);
}
@Override
public int getHistoryTagPoolUid(int index) {
- ensureHistoryTagArray();
- HistoryTag historyTag = mHistoryTags.get(index);
- return historyTag != null ? historyTag.uid : Process.INVALID_UID;
- }
-
- private void ensureHistoryTagArray() {
- if (mHistoryTags != null) {
- return;
- }
-
- mHistoryTags = new SparseArray<>(mHistoryTagPool.size());
- for (Map.Entry<HistoryTag, Integer> entry: mHistoryTagPool.entrySet()) {
- mHistoryTags.put(entry.getValue() & ~BatteryStatsHistory.TAG_FIRST_OCCURRENCE_FLAG,
- entry.getKey());
- }
+ return mHistory.getHistoryTagPoolUid(index);
}
@Override
@@ -11793,15 +11191,11 @@
@Override
public void finishIteratingHistoryLocked() {
+ mBatteryStatsHistoryIterator.close();
mBatteryStatsHistoryIterator = null;
}
@Override
- public long getHistoryBaseTime() {
- return mHistoryBaseTimeMs;
- }
-
- @Override
public int getStartCount() {
return mStartCount;
}
@@ -11854,24 +11248,23 @@
long realtimeUs = mSecRealtime * 1000;
resetAllStatsLocked(mSecUptime, mSecRealtime, RESET_REASON_ADB_COMMAND);
pullPendingStateUpdatesLocked();
- addHistoryRecordLocked(mSecRealtime, mSecUptime);
- mDischargeCurrentLevel = mDischargeUnplugLevel = mDischargePlugLevel
- = mCurrentBatteryLevel = mHistoryCur.batteryLevel;
+ mHistory.writeHistoryItem(mSecRealtime, mSecUptime);
+ mDischargeCurrentLevel = mDischargeUnplugLevel = mDischargePlugLevel = mBatteryLevel;
mOnBatteryTimeBase.reset(uptimeUs, realtimeUs);
mOnBatteryScreenOffTimeBase.reset(uptimeUs, realtimeUs);
- if ((mHistoryCur.states&HistoryItem.STATE_BATTERY_PLUGGED_FLAG) == 0) {
+ if (!mBatteryPluggedIn) {
if (Display.isOnState(mScreenState)) {
- mDischargeScreenOnUnplugLevel = mHistoryCur.batteryLevel;
+ mDischargeScreenOnUnplugLevel = mBatteryLevel;
mDischargeScreenDozeUnplugLevel = 0;
mDischargeScreenOffUnplugLevel = 0;
} else if (Display.isDozeState(mScreenState)) {
mDischargeScreenOnUnplugLevel = 0;
- mDischargeScreenDozeUnplugLevel = mHistoryCur.batteryLevel;
+ mDischargeScreenDozeUnplugLevel = mBatteryLevel;
mDischargeScreenOffUnplugLevel = 0;
} else {
mDischargeScreenOnUnplugLevel = 0;
mDischargeScreenDozeUnplugLevel = 0;
- mDischargeScreenOffUnplugLevel = mHistoryCur.batteryLevel;
+ mDischargeScreenOffUnplugLevel = mBatteryLevel;
}
mDischargeAmountScreenOn = 0;
mDischargeAmountScreenOff = 0;
@@ -12015,27 +11408,12 @@
resetIfNotNull(mBinderThreadCpuTimesUs, false, elapsedRealtimeUs);
- mLastHistoryStepDetails = null;
- mLastStepCpuUserTimeMs = mLastStepCpuSystemTimeMs = 0;
- mCurStepCpuUserTimeMs = mCurStepCpuSystemTimeMs = 0;
- mLastStepCpuUserTimeMs = mCurStepCpuUserTimeMs = 0;
- mLastStepCpuSystemTimeMs = mCurStepCpuSystemTimeMs = 0;
- mLastStepStatUserTimeMs = mCurStepStatUserTimeMs = 0;
- mLastStepStatSystemTimeMs = mCurStepStatSystemTimeMs = 0;
- mLastStepStatIOWaitTimeMs = mCurStepStatIOWaitTimeMs = 0;
- mLastStepStatIrqTimeMs = mCurStepStatIrqTimeMs = 0;
- mLastStepStatSoftIrqTimeMs = mCurStepStatSoftIrqTimeMs = 0;
- mLastStepStatIdleTimeMs = mCurStepStatIdleTimeMs = 0;
-
mNumAllUidCpuTimeReads = 0;
mNumUidsRemoved = 0;
initDischarge(elapsedRealtimeUs);
- clearHistoryLocked();
- if (mBatteryStatsHistory != null) {
- mBatteryStatsHistory.resetAllFiles();
- }
+ mHistory.reset();
// Flush external data, gathering snapshots, but don't process it since it is pre-reset data
mIgnoreNextExternalStats = true;
@@ -12058,7 +11436,7 @@
for (HashMap.Entry<String, SparseIntArray> ent : active.entrySet()) {
SparseIntArray uids = ent.getValue();
for (int j=0; j<uids.size(); j++) {
- addHistoryEventLocked(elapsedRealtimeMs, uptimeMs, i, ent.getKey(),
+ mHistory.recordEvent(elapsedRealtimeMs, uptimeMs, i, ent.getKey(),
uids.keyAt(j));
}
}
@@ -12483,9 +11861,8 @@
(long) (mTmpRailStats.getWifiTotalEnergyUseduWs() / opVolt);
mWifiActivity.getMonitoredRailChargeConsumedMaMs().addCountLocked(
monitoredRailChargeConsumedMaMs);
- mHistoryCur.wifiRailChargeMah +=
- (monitoredRailChargeConsumedMaMs / MILLISECONDS_IN_HOUR);
- addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
+ mHistory.recordWifiConsumedCharge(elapsedRealtimeMs, uptimeMs,
+ (monitoredRailChargeConsumedMaMs / MILLISECONDS_IN_HOUR));
mTmpRailStats.resetWifiTotalEnergyUsed();
if (uidEstimatedConsumptionMah != null) {
@@ -12598,9 +11975,8 @@
(long) (mTmpRailStats.getCellularTotalEnergyUseduWs() / opVolt);
mModemActivity.getMonitoredRailChargeConsumedMaMs().addCountLocked(
monitoredRailChargeConsumedMaMs);
- mHistoryCur.modemRailChargeMah +=
- (monitoredRailChargeConsumedMaMs / MILLISECONDS_IN_HOUR);
- addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
+ mHistory.recordWifiConsumedCharge(elapsedRealtimeMs, uptimeMs,
+ (monitoredRailChargeConsumedMaMs / MILLISECONDS_IN_HOUR));
mTmpRailStats.resetCellularTotalEnergyUsed();
}
@@ -12868,8 +12244,8 @@
}
}
if (levelMaxTimeSpent == ModemActivityInfo.getNumTxPowerLevels() - 1) {
- mHistoryCur.states2 |= HistoryItem.STATE2_CELLULAR_HIGH_TX_POWER_FLAG;
- addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
+ mHistory.recordState2StartEvent(elapsedRealtimeMs, uptimeMs,
+ HistoryItem.STATE2_CELLULAR_HIGH_TX_POWER_FLAG);
}
}
@@ -14302,11 +13678,7 @@
mHandler.removeCallbacks(mDeferSetCharging);
if (mCharging != charging) {
mCharging = charging;
- if (charging) {
- mHistoryCur.states2 |= HistoryItem.STATE2_CHARGING_FLAG;
- } else {
- mHistoryCur.states2 &= ~HistoryItem.STATE2_CHARGING_FLAG;
- }
+ mHistory.setChargingState(charging);
mHandler.sendEmptyMessage(MSG_REPORT_CHARGING);
return true;
}
@@ -14320,6 +13692,15 @@
mSystemReady = true;
}
+ /**
+ * Force recording of all history events regardless of the "charging" state.
+ */
+ @VisibleForTesting
+ public void forceRecordAllHistory() {
+ mHistory.forceRecordAllHistory();
+ mRecordAllHistory = true;
+ }
+
@GuardedBy("this")
protected void setOnBatteryLocked(final long mSecRealtime, final long mSecUptime,
final boolean onBattery, final int oldStatus, final int level, final int chargeUah) {
@@ -14403,15 +13784,12 @@
mInitStepMode = mCurStepMode;
mModStepMode = 0;
pullPendingStateUpdatesLocked();
- mHistoryCur.batteryLevel = (byte)level;
- mHistoryCur.states &= ~HistoryItem.STATE_BATTERY_PLUGGED_FLAG;
- if (DEBUG_HISTORY) Slog.v(TAG, "Battery unplugged to: "
- + Integer.toHexString(mHistoryCur.states));
if (reset) {
- mRecordingHistory = true;
- startRecordingHistory(mSecRealtime, mSecUptime, reset);
+ mHistory.startRecordingHistory(mSecRealtime, mSecUptime, reset);
+ initActiveHistoryEventsLocked(mSecRealtime, mSecUptime);
}
- addHistoryRecordLocked(mSecRealtime, mSecUptime);
+ mBatteryPluggedIn = false;
+ mHistory.recordBatteryState(mSecRealtime, mSecUptime, level, mBatteryPluggedIn);
mDischargeCurrentLevel = mDischargeUnplugLevel = level;
if (Display.isOnState(screenState)) {
mDischargeScreenOnUnplugLevel = level;
@@ -14433,11 +13811,8 @@
} else {
mOnBattery = mOnBatteryInternal = false;
pullPendingStateUpdatesLocked();
- mHistoryCur.batteryLevel = (byte)level;
- mHistoryCur.states |= HistoryItem.STATE_BATTERY_PLUGGED_FLAG;
- if (DEBUG_HISTORY) Slog.v(TAG, "Battery plugged to: "
- + Integer.toHexString(mHistoryCur.states));
- addHistoryRecordLocked(mSecRealtime, mSecUptime);
+ mBatteryPluggedIn = true;
+ mHistory.recordBatteryState(mSecRealtime, mSecUptime, level, mBatteryPluggedIn);
mDischargeCurrentLevel = mDischargePlugLevel = level;
if (level < mDischargeUnplugLevel) {
mLowDischargeAmountSinceCharge += mDischargeUnplugLevel-level-1;
@@ -14452,45 +13827,12 @@
mModStepMode = 0;
}
if (doWrite || (mLastWriteTimeMs + (60 * 1000)) < mSecRealtime) {
- if (mStatsFile != null && mBatteryStatsHistory.getActiveFile() != null) {
+ if (mStatsFile != null && !mHistory.isReadOnly()) {
writeAsyncLocked();
}
}
}
- @GuardedBy("this")
- private void startRecordingHistory(final long elapsedRealtimeMs, final long uptimeMs,
- boolean reset) {
- mRecordingHistory = true;
- mHistoryCur.currentTime = mClock.currentTimeMillis();
- addHistoryBufferLocked(elapsedRealtimeMs,
- reset ? HistoryItem.CMD_RESET : HistoryItem.CMD_CURRENT_TIME,
- mHistoryCur);
- mHistoryCur.currentTime = 0;
- if (reset) {
- initActiveHistoryEventsLocked(elapsedRealtimeMs, uptimeMs);
- }
- }
-
- @GuardedBy("this")
- private void recordCurrentTimeChangeLocked(final long currentTimeMs,
- final long elapsedRealtimeMs, final long uptimeMs) {
- if (mRecordingHistory) {
- mHistoryCur.currentTime = currentTimeMs;
- addHistoryBufferLocked(elapsedRealtimeMs, HistoryItem.CMD_CURRENT_TIME, mHistoryCur);
- mHistoryCur.currentTime = 0;
- }
- }
-
- @GuardedBy("this")
- private void recordShutdownLocked(final long currentTimeMs, final long elapsedRealtimeMs) {
- if (mRecordingHistory) {
- mHistoryCur.currentTime = currentTimeMs;
- addHistoryBufferLocked(elapsedRealtimeMs, HistoryItem.CMD_SHUTDOWN, mHistoryCur);
- mHistoryCur.currentTime = 0;
- }
- }
-
private void scheduleSyncExternalStatsLocked(String reason, int updateFlags) {
if (mExternalSync != null) {
mExternalSync.scheduleSync(reason, updateFlags);
@@ -14508,8 +13850,7 @@
// Temperature is encoded without the signed bit, so clamp any negative temperatures to 0.
temp = Math.max(0, temp);
- reportChangesToStatsLog(mHaveBatteryLevel ? mHistoryCur : null,
- status, plugType, level);
+ reportChangesToStatsLog(status, plugType, level);
final boolean onBattery = isOnBattery(plugType, status);
if (!mHaveBatteryLevel) {
@@ -14519,52 +13860,47 @@
// plugged in, then twiddle our state to correctly reflect that
// since we won't be going through the full setOnBattery().
if (onBattery == mOnBattery) {
- if (onBattery) {
- mHistoryCur.states &= ~HistoryItem.STATE_BATTERY_PLUGGED_FLAG;
- } else {
- mHistoryCur.states |= HistoryItem.STATE_BATTERY_PLUGGED_FLAG;
- }
+ mHistory.setPluggedInState(!onBattery);
}
+ mBatteryStatus = status;
+ mBatteryLevel = level;
+ mBatteryChargeUah = chargeUah;
+
// Always start out assuming charging, that will be updated later.
- mHistoryCur.states2 |= HistoryItem.STATE2_CHARGING_FLAG;
- mHistoryCur.batteryStatus = (byte)status;
- mHistoryCur.batteryLevel = (byte)level;
- mHistoryCur.batteryChargeUah = chargeUah;
+ mHistory.setBatteryState(true /* charging */, status, level, chargeUah);
+
mMaxChargeStepLevel = mMinDischargeStepLevel =
mLastChargeStepLevel = mLastDischargeStepLevel = level;
- } else if (mCurrentBatteryLevel != level || mOnBattery != onBattery) {
+ } else if (mBatteryLevel != level || mOnBattery != onBattery) {
recordDailyStatsIfNeededLocked(level >= 100 && onBattery, currentTimeMs);
}
- int oldStatus = mHistoryCur.batteryStatus;
+ int oldStatus = mBatteryStatus;
if (onBattery) {
mDischargeCurrentLevel = level;
- if (!mRecordingHistory) {
- mRecordingHistory = true;
- startRecordingHistory(elapsedRealtimeMs, uptimeMs, true);
+ if (!mHistory.isRecordingHistory()) {
+ mHistory.startRecordingHistory(elapsedRealtimeMs, uptimeMs, true);
}
} else if (level < 96 &&
status != BatteryManager.BATTERY_STATUS_UNKNOWN) {
- if (!mRecordingHistory) {
- mRecordingHistory = true;
- startRecordingHistory(elapsedRealtimeMs, uptimeMs, true);
+ if (!mHistory.isRecordingHistory()) {
+ mHistory.startRecordingHistory(elapsedRealtimeMs, uptimeMs, true);
}
}
- mBatteryVoltageMv = voltageMv;
- mCurrentBatteryLevel = level;
if (mDischargePlugLevel < 0) {
mDischargePlugLevel = level;
}
if (onBattery != mOnBattery) {
- mHistoryCur.batteryLevel = (byte)level;
- mHistoryCur.batteryStatus = (byte)status;
- mHistoryCur.batteryHealth = (byte)health;
- mHistoryCur.batteryPlugType = (byte)plugType;
- mHistoryCur.batteryTemperature = (short)temp;
- mHistoryCur.batteryVoltage = (char) voltageMv;
- if (chargeUah < mHistoryCur.batteryChargeUah) {
+ mBatteryLevel = level;
+ mBatteryStatus = status;
+ mBatteryHealth = health;
+ mBatteryPlugType = plugType;
+ mBatteryTemperature = temp;
+ mBatteryVoltageMv = voltageMv;
+ mHistory.setBatteryState(status, level, health, plugType, temp, voltageMv, chargeUah);
+ if (chargeUah < mBatteryChargeUah) {
// Only record discharges
- final long chargeDiff = mHistoryCur.batteryChargeUah - chargeUah;
+ final long chargeDiff = (long) mBatteryChargeUah - chargeUah;
mDischargeCounter.addCountLocked(chargeDiff);
mDischargeScreenOffCounter.addCountLocked(chargeDiff);
if (Display.isDozeState(mScreenState)) {
@@ -14576,12 +13912,12 @@
mDischargeDeepDozeCounter.addCountLocked(chargeDiff);
}
}
- mHistoryCur.batteryChargeUah = chargeUah;
+ mBatteryChargeUah = chargeUah;
setOnBatteryLocked(elapsedRealtimeMs, uptimeMs, onBattery, oldStatus, level, chargeUah);
} else {
boolean changed = false;
- if (mHistoryCur.batteryLevel != level) {
- mHistoryCur.batteryLevel = (byte)level;
+ if (mBatteryLevel != level) {
+ mBatteryLevel = level;
changed = true;
// TODO(adamlesinski): Schedule the creation of a HistoryStepDetails record
@@ -14589,33 +13925,33 @@
mExternalSync.scheduleSyncDueToBatteryLevelChange(
mConstants.BATTERY_LEVEL_COLLECTION_DELAY_MS);
}
- if (mHistoryCur.batteryStatus != status) {
- mHistoryCur.batteryStatus = (byte)status;
+ if (mBatteryStatus != status) {
+ mBatteryStatus = status;
changed = true;
}
- if (mHistoryCur.batteryHealth != health) {
- mHistoryCur.batteryHealth = (byte)health;
+ if (mBatteryHealth != health) {
+ mBatteryHealth = health;
changed = true;
}
- if (mHistoryCur.batteryPlugType != plugType) {
- mHistoryCur.batteryPlugType = (byte)plugType;
+ if (mBatteryPlugType != plugType) {
+ mBatteryPlugType = plugType;
changed = true;
}
- if (temp >= (mHistoryCur.batteryTemperature+10)
- || temp <= (mHistoryCur.batteryTemperature-10)) {
- mHistoryCur.batteryTemperature = (short)temp;
+ if (temp >= (mBatteryTemperature + 10)
+ || temp <= (mBatteryTemperature - 10)) {
+ mBatteryTemperature = temp;
changed = true;
}
- if (voltageMv > (mHistoryCur.batteryVoltage + 20)
- || voltageMv < (mHistoryCur.batteryVoltage - 20)) {
- mHistoryCur.batteryVoltage = (char) voltageMv;
+ if (voltageMv > (mBatteryVoltageMv + 20)
+ || voltageMv < (mBatteryVoltageMv - 20)) {
+ mBatteryVoltageMv = voltageMv;
changed = true;
}
- if (chargeUah >= (mHistoryCur.batteryChargeUah + 10)
- || chargeUah <= (mHistoryCur.batteryChargeUah - 10)) {
- if (chargeUah < mHistoryCur.batteryChargeUah) {
+ if (chargeUah >= (mBatteryChargeUah + 10)
+ || chargeUah <= (mBatteryChargeUah - 10)) {
+ if (chargeUah < mBatteryChargeUah) {
// Only record discharges
- final long chargeDiff = mHistoryCur.batteryChargeUah - chargeUah;
+ final long chargeDiff = (long) mBatteryChargeUah - chargeUah;
mDischargeCounter.addCountLocked(chargeDiff);
mDischargeScreenOffCounter.addCountLocked(chargeDiff);
if (Display.isDozeState(mScreenState)) {
@@ -14627,9 +13963,10 @@
mDischargeDeepDozeCounter.addCountLocked(chargeDiff);
}
}
- mHistoryCur.batteryChargeUah = chargeUah;
+ mBatteryChargeUah = chargeUah;
changed = true;
}
+
long modeBits = (((long)mInitStepMode) << STEP_LEVEL_INITIAL_MODE_SHIFT)
| (((long)mModStepMode) << STEP_LEVEL_MODIFIED_MODE_SHIFT)
| (((long)(level&0xff)) << STEP_LEVEL_LEVEL_SHIFT);
@@ -14687,7 +14024,10 @@
mLastChargeStepLevel = level;
}
if (changed) {
- addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
+ mHistory.setBatteryState(mBatteryStatus, mBatteryLevel, mBatteryHealth,
+ mBatteryPlugType, mBatteryTemperature, mBatteryVoltageMv,
+ mBatteryChargeUah);
+ mHistory.writeHistoryItem(elapsedRealtimeMs, uptimeMs);
}
}
if (!onBattery &&
@@ -14696,7 +14036,7 @@
// We don't record history while we are plugged in and fully charged
// (or when battery is not present). The next time we are
// unplugged, history will be cleared.
- mRecordingHistory = DEBUG;
+ mHistory.setHistoryRecordingEnabled(DEBUG);
}
mLastLearnedBatteryCapacityUah = chargeFullUah;
@@ -14715,17 +14055,18 @@
}
// Inform StatsLog of setBatteryState changes.
- // If this is the first reporting, pass in recentPast == null.
- private void reportChangesToStatsLog(HistoryItem recentPast,
- final int status, final int plugType, final int level) {
+ private void reportChangesToStatsLog(final int status, final int plugType, final int level) {
+ if (!mHaveBatteryLevel) {
+ return;
+ }
- if (recentPast == null || recentPast.batteryStatus != status) {
+ if (mBatteryStatus != status) {
FrameworkStatsLog.write(FrameworkStatsLog.CHARGING_STATE_CHANGED, status);
}
- if (recentPast == null || recentPast.batteryPlugType != plugType) {
+ if (mBatteryPlugType != plugType) {
FrameworkStatsLog.write(FrameworkStatsLog.PLUGGED_STATE_CHANGED, plugType);
}
- if (recentPast == null || recentPast.batteryLevel != level) {
+ if (mBatteryLevel != level) {
FrameworkStatsLog.write(FrameworkStatsLog.BATTERY_LEVEL_CHANGED, level);
}
}
@@ -14795,7 +14136,7 @@
if (msPerLevel <= 0) {
return -1;
}
- return (msPerLevel * mCurrentBatteryLevel) * 1000;
+ return (msPerLevel * mBatteryLevel) * 1000;
}
@Override
@@ -14825,7 +14166,7 @@
if (msPerLevel <= 0) {
return -1;
}
- return (msPerLevel * (100 - mCurrentBatteryLevel)) * 1000;
+ return (msPerLevel * (100 - mBatteryLevel)) * 1000;
}
/*@hide */
@@ -15256,7 +14597,8 @@
@GuardedBy("this")
public void shutdownLocked() {
- recordShutdownLocked(mClock.currentTimeMillis(), mClock.elapsedRealtime());
+ mHistory.recordShutdownEvent(mClock.elapsedRealtime(), mClock.uptimeMillis(),
+ mClock.currentTimeMillis());
writeSyncLocked();
mShuttingDown = true;
}
@@ -15464,7 +14806,6 @@
PROC_STATE_CHANGE_COLLECTION_DELAY_MS = mParser.getLong(
KEY_PROC_STATE_CHANGE_COLLECTION_DELAY_MS,
DEFAULT_PROC_STATE_CHANGE_COLLECTION_DELAY_MS);
-
MAX_HISTORY_FILES = mParser.getInt(KEY_MAX_HISTORY_FILES,
ActivityManager.isLowRamDeviceStatic() ?
DEFAULT_MAX_HISTORY_FILES_LOW_RAM_DEVICE
@@ -15475,9 +14816,20 @@
: DEFAULT_MAX_HISTORY_BUFFER_KB)
* 1024;
updateBatteryChargedDelayMsLocked();
+
+ onChange();
}
}
+ /**
+ * Propagates changes in constant values.
+ */
+ @VisibleForTesting
+ public void onChange() {
+ mHistory.setMaxHistoryFiles(MAX_HISTORY_FILES);
+ mHistory.setMaxHistoryBufferSize(MAX_HISTORY_BUFFER);
+ }
+
private void updateBatteryChargedDelayMsLocked() {
// a negative value indicates that we should ignore this override
final int delay = Settings.Global.getInt(mResolver,
@@ -15698,27 +15050,11 @@
}
private void writeHistoryLocked() {
- if (mBatteryStatsHistory.getActiveFile() == null) {
- Slog.w(TAG, "writeHistoryLocked: no history file associated with this instance");
- return;
- }
-
if (mShuttingDown) {
return;
}
- Parcel p = Parcel.obtain();
- try {
- final long start = SystemClock.uptimeMillis();
- writeHistoryBuffer(p, true);
- if (DEBUG) {
- Slog.d(TAG, "writeHistoryBuffer duration ms:"
- + (SystemClock.uptimeMillis() - start) + " bytes:" + p.dataSize());
- }
- writeParcelToFileLocked(p, mBatteryStatsHistory.getActiveFile());
- } finally {
- p.recycle();
- }
+ mHistory.writeHistory();
}
private final ReentrantLock mWriteLock = new ReentrantLock();
@@ -15757,13 +15093,6 @@
return;
}
- final AtomicFile activeHistoryFile = mBatteryStatsHistory.getActiveFile();
- if (activeHistoryFile == null) {
- Slog.w(TAG,
- "readLocked: no history file associated with this instance");
- return;
- }
-
mUidStats.clear();
Parcel stats = Parcel.obtain();
@@ -15776,7 +15105,7 @@
readSummaryFromParcel(stats);
if (DEBUG) {
Slog.d(TAG, "readLocked stats file:" + mStatsFile.getBaseFile().getPath()
- + " bytes:" + raw.length + " takes ms:" + (SystemClock.uptimeMillis()
+ + " bytes:" + raw.length + " took ms:" + (SystemClock.uptimeMillis()
- start));
}
}
@@ -15788,126 +15117,19 @@
stats.recycle();
}
- Parcel history = Parcel.obtain();
- try {
- final long start = SystemClock.uptimeMillis();
- if (activeHistoryFile.exists()) {
- byte[] raw = activeHistoryFile.readFully();
- if (raw.length > 0) {
- history.unmarshall(raw, 0, raw.length);
- history.setDataPosition(0);
- readHistoryBuffer(history);
- }
- if (DEBUG) {
- Slog.d(TAG, "readLocked history file::"
- + activeHistoryFile.getBaseFile().getPath()
- + " bytes:" + raw.length + " takes ms:" + (SystemClock.uptimeMillis()
- - start));
- }
- }
- } catch (Exception e) {
- Slog.e(TAG, "Error reading battery history", e);
- clearHistoryLocked();
- mBatteryStatsHistory.resetAllFiles();
- } finally {
- history.recycle();
+ if (!mHistory.readSummary()) {
+ resetAllStatsLocked(SystemClock.uptimeMillis(), SystemClock.elapsedRealtime(),
+ RESET_REASON_CORRUPT_FILE);
}
mEndPlatformVersion = Build.ID;
- if (mHistoryBuffer.dataPosition() > 0
- || mBatteryStatsHistory.getFilesNumbers().size() > 1) {
- mRecordingHistory = true;
- final long elapsedRealtimeMs = mClock.elapsedRealtime();
- final long uptimeMs = mClock.uptimeMillis();
- addHistoryBufferLocked(elapsedRealtimeMs, HistoryItem.CMD_START, mHistoryCur);
- startRecordingHistory(elapsedRealtimeMs, uptimeMs, false);
- }
+ mHistory.continueRecordingHistory();
recordDailyStatsIfNeededLocked(false, mClock.currentTimeMillis());
}
@GuardedBy("this")
- void readHistoryBuffer(Parcel in) throws ParcelFormatException {
- final int version = in.readInt();
- if (version != BatteryStatsHistory.VERSION) {
- Slog.w("BatteryStats", "readHistoryBuffer: version got " + version
- + ", expected " + BatteryStatsHistory.VERSION + "; erasing old stats");
- return;
- }
-
- final long historyBaseTime = in.readLong();
-
- mHistoryBuffer.setDataSize(0);
- mHistoryBuffer.setDataPosition(0);
-
- int bufSize = in.readInt();
- int curPos = in.dataPosition();
- if (bufSize >= (mConstants.MAX_HISTORY_BUFFER*100)) {
- throw new ParcelFormatException("File corrupt: history data buffer too large " +
- bufSize);
- } else if ((bufSize&~3) != bufSize) {
- throw new ParcelFormatException("File corrupt: history data buffer not aligned " +
- bufSize);
- } else {
- if (DEBUG_HISTORY) Slog.i(TAG, "***************** READING NEW HISTORY: " + bufSize
- + " bytes at " + curPos);
- mHistoryBuffer.appendFrom(in, curPos, bufSize);
- in.setDataPosition(curPos + bufSize);
- }
-
- if (DEBUG_HISTORY) {
- StringBuilder sb = new StringBuilder(128);
- sb.append("****************** OLD mHistoryBaseTimeMs: ");
- TimeUtils.formatDuration(mHistoryBaseTimeMs, sb);
- Slog.i(TAG, sb.toString());
- }
- mHistoryBaseTimeMs = historyBaseTime;
- if (DEBUG_HISTORY) {
- StringBuilder sb = new StringBuilder(128);
- sb.append("****************** NEW mHistoryBaseTimeMs: ");
- TimeUtils.formatDuration(mHistoryBaseTimeMs, sb);
- Slog.i(TAG, sb.toString());
- }
-
- // We are just arbitrarily going to insert 1 minute from the sample of
- // the last run until samples in this run.
- if (mHistoryBaseTimeMs > 0) {
- long oldnow = mClock.elapsedRealtime();
- mHistoryBaseTimeMs = mHistoryBaseTimeMs - oldnow + 1;
- if (DEBUG_HISTORY) {
- StringBuilder sb = new StringBuilder(128);
- sb.append("****************** ADJUSTED mHistoryBaseTimeMs: ");
- TimeUtils.formatDuration(mHistoryBaseTimeMs, sb);
- Slog.i(TAG, sb.toString());
- }
- }
- }
-
- void writeHistoryBuffer(Parcel out, boolean inclData) {
- if (DEBUG_HISTORY) {
- StringBuilder sb = new StringBuilder(128);
- sb.append("****************** WRITING mHistoryBaseTimeMs: ");
- TimeUtils.formatDuration(mHistoryBaseTimeMs, sb);
- sb.append(" mLastHistoryElapsedRealtimeMs: ");
- TimeUtils.formatDuration(mLastHistoryElapsedRealtimeMs, sb);
- Slog.i(TAG, sb.toString());
- }
- out.writeInt(BatteryStatsHistory.VERSION);
- out.writeLong(mHistoryBaseTimeMs + mLastHistoryElapsedRealtimeMs);
- if (!inclData) {
- out.writeInt(0);
- out.writeInt(0);
- return;
- }
-
- out.writeInt(mHistoryBuffer.dataSize());
- if (DEBUG_HISTORY) Slog.i(TAG, "***************** WRITING HISTORY: "
- + mHistoryBuffer.dataSize() + " bytes at " + out.dataPosition());
- out.appendFrom(mHistoryBuffer, 0, mHistoryBuffer.dataSize());
- }
-
- @GuardedBy("this")
public void readSummaryFromParcel(Parcel in) throws ParcelFormatException {
final int version = in.readInt();
@@ -15917,31 +15139,7 @@
return;
}
- boolean inclHistory = in.readBoolean();
- if (inclHistory) {
- readHistoryBuffer(in);
- mBatteryStatsHistory.readFromParcel(in);
- }
-
- mHistoryTagPool.clear();
- mNextHistoryTagIdx = 0;
- mNumHistoryTagChars = 0;
-
- int numTags = in.readInt();
- for (int i=0; i<numTags; i++) {
- int idx = in.readInt();
- String str = in.readString();
- int uid = in.readInt();
- HistoryTag tag = new HistoryTag();
- tag.string = str;
- tag.uid = uid;
- tag.poolIdx = idx;
- mHistoryTagPool.put(tag, idx);
- if (idx >= mNextHistoryTagIdx) {
- mNextHistoryTagIdx = idx+1;
- }
- mNumHistoryTagChars += tag.string.length() + 1;
- }
+ mHistory.readSummaryFromParcel(in);
mStartCount = in.readInt();
mUptimeUs = in.readLong();
@@ -15954,7 +15152,7 @@
mDischargeUnplugLevel = in.readInt();
mDischargePlugLevel = in.readInt();
mDischargeCurrentLevel = in.readInt();
- mCurrentBatteryLevel = in.readInt();
+ mBatteryLevel = in.readInt();
mEstimatedBatteryCapacityMah = in.readInt();
mLastLearnedBatteryCapacityUah = in.readInt();
mMinLearnedBatteryCapacityUah = in.readInt();
@@ -16457,19 +15655,7 @@
out.writeInt(VERSION);
- out.writeBoolean(inclHistory);
- if (inclHistory) {
- writeHistoryBuffer(out, true);
- mBatteryStatsHistory.writeToParcel(out);
- }
-
- out.writeInt(mHistoryTagPool.size());
- for (HashMap.Entry<HistoryTag, Integer> ent : mHistoryTagPool.entrySet()) {
- HistoryTag tag = ent.getKey();
- out.writeInt(ent.getValue());
- out.writeString(tag.string);
- out.writeInt(tag.uid);
- }
+ mHistory.writeSummaryToParcel(out, inclHistory);
out.writeInt(mStartCount);
out.writeLong(computeUptime(nowUptime, STATS_SINCE_CHARGED));
@@ -16482,7 +15668,7 @@
out.writeInt(mDischargeUnplugLevel);
out.writeInt(mDischargePlugLevel);
out.writeInt(mDischargeCurrentLevel);
- out.writeInt(mCurrentBatteryLevel);
+ out.writeInt(mBatteryLevel);
out.writeInt(mEstimatedBatteryCapacityMah);
out.writeInt(mLastLearnedBatteryCapacityUah);
out.writeInt(mMinLearnedBatteryCapacityUah);
diff --git a/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java b/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java
index 0cdd4d1..c36d950 100644
--- a/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java
+++ b/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java
@@ -22,7 +22,6 @@
import android.os.BatteryStats;
import android.os.BatteryUsageStats;
import android.os.BatteryUsageStatsQuery;
-import android.os.Parcel;
import android.os.Process;
import android.os.SystemClock;
import android.os.UidBatteryConsumer;
@@ -32,10 +31,8 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.os.BatteryStatsHistory;
import com.android.internal.os.PowerProfile;
-import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -220,18 +217,7 @@
}
BatteryStatsImpl batteryStatsImpl = (BatteryStatsImpl) mStats;
-
- // Make a copy of battery history to avoid concurrent modification.
- Parcel historyBuffer = Parcel.obtain();
- historyBuffer.appendFrom(batteryStatsImpl.mHistoryBuffer, 0,
- batteryStatsImpl.mHistoryBuffer.dataSize());
-
- final File systemDir =
- batteryStatsImpl.mBatteryStatsHistory.getHistoryDirectory().getParentFile();
- final BatteryStatsHistory batteryStatsHistory =
- new BatteryStatsHistory(historyBuffer, systemDir, null);
-
- batteryUsageStatsBuilder.setBatteryHistory(batteryStatsHistory);
+ batteryUsageStatsBuilder.setBatteryHistory(batteryStatsImpl.copyHistory());
}
BatteryUsageStats stats = batteryUsageStatsBuilder.build();
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index d11adc1..55175c5 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -1566,7 +1566,7 @@
if (task == mLastParentBeforePip && task != null) {
// Notify the TaskFragmentOrganizer that the activity is reparented back from pip.
mAtmService.mWindowOrganizerController.mTaskFragmentOrganizerController
- .onActivityReparentToTask(this);
+ .onActivityReparentedToTask(this);
// Activity's reparented back from pip, clear the links once established
clearLastParentBeforePip();
}
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 5221072..096ebe2 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -2371,18 +2371,19 @@
return;
}
- // The immersive mode confirmation should never affect the system bar visibility, otherwise
+ // Immersive mode confirmation should never affect the system bar visibility, otherwise
// it will unhide the navigation bar and hide itself.
if (winCandidate.getAttrs().token == mImmersiveModeConfirmation.getWindowToken()) {
-
- // The immersive mode confirmation took the focus from mLastFocusedWindow which was
- // controlling the system ui visibility. So if mLastFocusedWindow can still receive
- // keys, we let it keep controlling the visibility.
- final boolean lastFocusCanReceiveKeys =
- (mLastFocusedWindow != null && mLastFocusedWindow.canReceiveKeys());
- winCandidate = isKeyguardShowing() && !isKeyguardOccluded() ? mNotificationShade
- : lastFocusCanReceiveKeys ? mLastFocusedWindow
- : mTopFullscreenOpaqueWindowState;
+ if (mNotificationShade != null && mNotificationShade.canReceiveKeys()) {
+ // Let notification shade control the system bar visibility.
+ winCandidate = mNotificationShade;
+ } else if (mLastFocusedWindow != null && mLastFocusedWindow.canReceiveKeys()) {
+ // Immersive mode confirmation took the focus from mLastFocusedWindow which was
+ // controlling the system bar visibility. Let it keep controlling the visibility.
+ winCandidate = mLastFocusedWindow;
+ } else {
+ winCandidate = mTopFullscreenOpaqueWindowState;
+ }
if (winCandidate == null) {
return;
}
diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java
index 7a9e6a9..f3bd1a1 100644
--- a/services/core/java/com/android/server/wm/InsetsPolicy.java
+++ b/services/core/java/com/android/server/wm/InsetsPolicy.java
@@ -761,7 +761,7 @@
InsetsPolicyAnimationControlListener(boolean show, Runnable finishCallback, int types) {
super(show, false /* hasCallbacks */, types, BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE,
- false /* disable */, 0 /* floatingImeBottomInsets */);
+ false /* disable */, 0 /* floatingImeBottomInsets */, null);
mFinishCallback = finishCallback;
mControlCallbacks = new InsetsPolicyAnimationControlCallbacks(this);
}
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index c1f06e4..44b5b88 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -2340,7 +2340,10 @@
return false;
}
- return !startBounds.equals(getBounds());
+ // Only take snapshot if the bounds are resized.
+ final Rect endBounds = getConfiguration().windowConfiguration.getBounds();
+ return endBounds.width() != startBounds.width()
+ || endBounds.height() != startBounds.height();
}
boolean canHaveEmbeddingActivityTransition(@NonNull ActivityRecord child) {
diff --git a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
index 6ca5648..88059e1 100644
--- a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
@@ -18,7 +18,7 @@
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.window.TaskFragmentOrganizer.putErrorInfoInBundle;
-import static android.window.TaskFragmentTransaction.TYPE_ACTIVITY_REPARENT_TO_TASK;
+import static android.window.TaskFragmentTransaction.TYPE_ACTIVITY_REPARENTED_TO_TASK;
import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_APPEARED;
import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_ERROR;
import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_INFO_CHANGED;
@@ -277,7 +277,7 @@
}
@Nullable
- TaskFragmentTransaction.Change prepareActivityReparentToTask(
+ TaskFragmentTransaction.Change prepareActivityReparentedToTask(
@NonNull ActivityRecord activity) {
if (activity.finishing) {
Slog.d(TAG, "Reparent activity=" + activity.token + " is finishing");
@@ -315,7 +315,7 @@
}
ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER, "Activity=%s reparent to taskId=%d",
activity.token, task.mTaskId);
- return new TaskFragmentTransaction.Change(TYPE_ACTIVITY_REPARENT_TO_TASK)
+ return new TaskFragmentTransaction.Change(TYPE_ACTIVITY_REPARENTED_TO_TASK)
.setTaskId(task.mTaskId)
.setActivityIntent(activity.intent)
.setActivityToken(activityToken);
@@ -521,7 +521,7 @@
mAtmService.mWindowManager.mWindowPlacerLocked.requestTraversal();
}
- void onActivityReparentToTask(@NonNull ActivityRecord activity) {
+ void onActivityReparentedToTask(@NonNull ActivityRecord activity) {
final ITaskFragmentOrganizer organizer;
if (activity.mLastTaskFragmentOrganizerBeforePip != null) {
// If the activity is previously embedded in an organized TaskFragment.
@@ -547,7 +547,7 @@
return;
}
addPendingEvent(new PendingTaskFragmentEvent.Builder(
- PendingTaskFragmentEvent.EVENT_ACTIVITY_REPARENT_TO_TASK, organizer)
+ PendingTaskFragmentEvent.EVENT_ACTIVITY_REPARENTED_TO_TASK, organizer)
.setActivity(activity)
.build());
}
@@ -601,7 +601,7 @@
static final int EVENT_INFO_CHANGED = 2;
static final int EVENT_PARENT_INFO_CHANGED = 3;
static final int EVENT_ERROR = 4;
- static final int EVENT_ACTIVITY_REPARENT_TO_TASK = 5;
+ static final int EVENT_ACTIVITY_REPARENTED_TO_TASK = 5;
@IntDef(prefix = "EVENT_", value = {
EVENT_APPEARED,
@@ -609,7 +609,7 @@
EVENT_INFO_CHANGED,
EVENT_PARENT_INFO_CHANGED,
EVENT_ERROR,
- EVENT_ACTIVITY_REPARENT_TO_TASK
+ EVENT_ACTIVITY_REPARENTED_TO_TASK
})
@Retention(RetentionPolicy.SOURCE)
public @interface EventType {}
@@ -900,8 +900,8 @@
case PendingTaskFragmentEvent.EVENT_ERROR:
return state.prepareTaskFragmentError(event.mErrorCallbackToken, taskFragment,
event.mOpType, event.mException);
- case PendingTaskFragmentEvent.EVENT_ACTIVITY_REPARENT_TO_TASK:
- return state.prepareActivityReparentToTask(event.mActivity);
+ case PendingTaskFragmentEvent.EVENT_ACTIVITY_REPARENTED_TO_TASK:
+ return state.prepareActivityReparentedToTask(event.mActivity);
default:
throw new IllegalArgumentException("Unknown TaskFragmentEvent=" + event.mEventType);
}
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 584a40e..fa2ab31 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -1205,7 +1205,14 @@
return false;
}
- final @TransitionInfo.TransitionMode int mode = changes.get(target).getTransitMode(target);
+ final ChangeInfo change = changes.get(target);
+ if (change.mStartParent != null && target.getParent() != change.mStartParent) {
+ // When a window is reparented, the state change won't fit into any of the parents.
+ // Don't promote such change so that we can animate the reparent if needed.
+ return false;
+ }
+
+ final @TransitionInfo.TransitionMode int mode = change.getTransitMode(target);
for (int i = parent.getChildCount() - 1; i >= 0; --i) {
final WindowContainer<?> sibling = parent.getChildAt(i);
if (target == sibling) continue;
@@ -1345,14 +1352,14 @@
// Intermediate parents must be those that has window to be managed by Shell.
continue;
}
- if (parentChange.mParent != null && !skipIntermediateReports) {
- changes.get(wc).mParent = p;
+ if (parentChange.mEndParent != null && !skipIntermediateReports) {
+ changes.get(wc).mEndParent = p;
// The chain above the parent was processed.
break;
}
if (targetList.contains(p)) {
if (skipIntermediateReports) {
- changes.get(wc).mParent = p;
+ changes.get(wc).mEndParent = p;
} else {
intermediates.add(p);
}
@@ -1364,10 +1371,10 @@
}
if (!foundParentInTargets || intermediates.isEmpty()) continue;
// Add any always-report parents along the way.
- changes.get(wc).mParent = intermediates.get(0);
+ changes.get(wc).mEndParent = intermediates.get(0);
for (int j = 0; j < intermediates.size() - 1; j++) {
final WindowContainer<?> intermediate = intermediates.get(j);
- changes.get(intermediate).mParent = intermediates.get(j + 1);
+ changes.get(intermediate).mEndParent = intermediates.get(j + 1);
targets.add(intermediate);
}
}
@@ -1480,8 +1487,8 @@
target.mRemoteToken != null ? target.mRemoteToken.toWindowContainerToken()
: null, getLeashSurface(target, startT));
// TODO(shell-transitions): Use leash for non-organized windows.
- if (info.mParent != null) {
- change.setParent(info.mParent.mRemoteToken.toWindowContainerToken());
+ if (info.mEndParent != null) {
+ change.setParent(info.mEndParent.mRemoteToken.toWindowContainerToken());
}
change.setMode(info.getTransitMode(target));
change.setStartAbsBounds(info.mAbsoluteBounds);
@@ -1664,7 +1671,9 @@
@interface Flag {}
// Usually "post" change state.
- WindowContainer mParent;
+ WindowContainer mEndParent;
+ // Parent before change state.
+ WindowContainer mStartParent;
// State tracking
boolean mExistenceChanged = false;
@@ -1685,6 +1694,7 @@
mAbsoluteBounds.set(origState.getBounds());
mShowWallpaper = origState.showWallpaper();
mRotation = origState.getWindowConfiguration().getRotation();
+ mStartParent = origState.getParent();
}
@VisibleForTesting
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 3ee4be0..22411bb 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -88,6 +88,7 @@
import static android.view.WindowManager.REMOVE_CONTENT_MODE_UNDEFINED;
import static android.view.WindowManager.TRANSIT_NONE;
import static android.view.WindowManager.TRANSIT_RELAUNCH;
+import static android.view.WindowManager.fixScale;
import static android.view.WindowManagerGlobal.ADD_OKAY;
import static android.view.WindowManagerGlobal.RELAYOUT_RES_CANCEL_AND_REDRAW;
import static android.view.WindowManagerGlobal.RELAYOUT_RES_SURFACE_CHANGED;
@@ -1326,15 +1327,10 @@
}, UserHandle.ALL, suspendPackagesFilter, null, null);
// Get persisted window scale setting
- mWindowAnimationScaleSetting = Settings.Global.getFloat(resolver,
- Settings.Global.WINDOW_ANIMATION_SCALE, mWindowAnimationScaleSetting);
- mTransitionAnimationScaleSetting = Settings.Global.getFloat(resolver,
- Settings.Global.TRANSITION_ANIMATION_SCALE,
- context.getResources().getFloat(
- R.dimen.config_appTransitionAnimationDurationScaleDefault));
+ mWindowAnimationScaleSetting = getWindowAnimationScaleSetting();
+ mTransitionAnimationScaleSetting = getTransitionAnimationScaleSetting();
- setAnimatorDurationScale(Settings.Global.getFloat(resolver,
- Settings.Global.ANIMATOR_DURATION_SCALE, mAnimatorDurationScaleSetting));
+ setAnimatorDurationScale(getAnimatorDurationScaleSetting());
mForceDesktopModeOnExternalDisplays = Settings.Global.getInt(resolver,
DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS, 0) != 0;
@@ -1408,6 +1404,22 @@
lightRadius);
}
+ private float getTransitionAnimationScaleSetting() {
+ return fixScale(Settings.Global.getFloat(mContext.getContentResolver(),
+ Settings.Global.TRANSITION_ANIMATION_SCALE, mContext.getResources().getFloat(
+ R.dimen.config_appTransitionAnimationDurationScaleDefault)));
+ }
+
+ private float getAnimatorDurationScaleSetting() {
+ return fixScale(Settings.Global.getFloat(mContext.getContentResolver(),
+ Settings.Global.ANIMATOR_DURATION_SCALE, mAnimatorDurationScaleSetting));
+ }
+
+ private float getWindowAnimationScaleSetting() {
+ return fixScale(Settings.Global.getFloat(mContext.getContentResolver(),
+ Settings.Global.WINDOW_ANIMATION_SCALE, mWindowAnimationScaleSetting));
+ }
+
/**
* Called after all entities (such as the {@link ActivityManagerService}) have been set up and
* associated with the {@link WindowManagerService}.
@@ -3407,11 +3419,6 @@
}
}
- static float fixScale(float scale) {
- if (scale < 0) scale = 0;
- else if (scale > 20) scale = 20;
- return Math.abs(scale);
- }
@Override
public void setAnimationScale(int which, float scale) {
@@ -5339,24 +5346,16 @@
final int mode = msg.arg1;
switch (mode) {
case WINDOW_ANIMATION_SCALE: {
- mWindowAnimationScaleSetting = Settings.Global.getFloat(
- mContext.getContentResolver(),
- Settings.Global.WINDOW_ANIMATION_SCALE,
- mWindowAnimationScaleSetting);
+ mWindowAnimationScaleSetting = getWindowAnimationScaleSetting();
break;
}
case TRANSITION_ANIMATION_SCALE: {
- mTransitionAnimationScaleSetting = Settings.Global.getFloat(
- mContext.getContentResolver(),
- Settings.Global.TRANSITION_ANIMATION_SCALE,
- mTransitionAnimationScaleSetting);
+ mTransitionAnimationScaleSetting =
+ getTransitionAnimationScaleSetting();
break;
}
case ANIMATION_DURATION_SCALE: {
- mAnimatorDurationScaleSetting = Settings.Global.getFloat(
- mContext.getContentResolver(),
- Settings.Global.ANIMATOR_DURATION_SCALE,
- mAnimatorDurationScaleSetting);
+ mAnimatorDurationScaleSetting = getAnimatorDurationScaleSetting();
dispatchNewAnimatorScaleLocked(null);
break;
}
diff --git a/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp b/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp
index d3d69ae..1d56078 100644
--- a/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp
+++ b/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp
@@ -491,10 +491,10 @@
compactProcessOrFallback(pid, compactionFlags);
}
-static jint com_android_server_am_CachedAppOptimizer_freezeBinder(JNIEnv* env, jobject clazz,
- jint pid, jboolean freeze,
- jint timeout_ms) {
- jint retVal = IPCThreadState::freeze(pid, freeze, timeout_ms);
+static jint com_android_server_am_CachedAppOptimizer_freezeBinder(
+ JNIEnv *env, jobject clazz, jint pid, jboolean freeze) {
+
+ jint retVal = IPCThreadState::freeze(pid, freeze, 100 /* timeout [ms] */);
if (retVal != 0 && retVal != -EAGAIN) {
jniThrowException(env, "java/lang/RuntimeException", "Unable to freeze/unfreeze binder");
}
@@ -548,7 +548,7 @@
(void*)com_android_server_am_CachedAppOptimizer_getMemoryFreedCompaction},
{"compactSystem", "()V", (void*)com_android_server_am_CachedAppOptimizer_compactSystem},
{"compactProcess", "(II)V", (void*)com_android_server_am_CachedAppOptimizer_compactProcess},
- {"freezeBinder", "(IZI)I", (void*)com_android_server_am_CachedAppOptimizer_freezeBinder},
+ {"freezeBinder", "(IZ)I", (void*)com_android_server_am_CachedAppOptimizer_freezeBinder},
{"getBinderFreezeInfo", "(I)I",
(void*)com_android_server_am_CachedAppOptimizer_getBinderFreezeInfo},
{"getFreezerCheckPath", "()Ljava/lang/String;",
diff --git a/services/core/xsd/display-device-config/autobrightness.xsd b/services/core/xsd/display-device-config/autobrightness.xsd
new file mode 100644
index 0000000..477625a
--- /dev/null
+++ b/services/core/xsd/display-device-config/autobrightness.xsd
@@ -0,0 +1,33 @@
+<!--
+ Copyright (C) 2022 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.
+-->
+<xs:schema version="2.0"
+ elementFormDefault="qualified"
+ xmlns:xs="http://www.w3.org/2001/XMLSchema">
+ <xs:complexType name="autoBrightness">
+ <xs:sequence>
+ <!-- Sets the debounce for autoBrightness brightening in millis-->
+ <xs:element name="brighteningLightDebounceMillis" type="xs:nonNegativeInteger"
+ minOccurs="0" maxOccurs="1">
+ <xs:annotation name="final"/>
+ </xs:element>
+ <!-- Sets the debounce for autoBrightness darkening in millis-->
+ <xs:element name="darkeningLightDebounceMillis" type="xs:nonNegativeInteger"
+ minOccurs="0" maxOccurs="1">
+ <xs:annotation name="final"/>
+ </xs:element>
+ </xs:sequence>
+ </xs:complexType>
+</xs:schema>
\ No newline at end of file
diff --git a/services/core/xsd/display-device-config/display-device-config.xsd b/services/core/xsd/display-device-config/display-device-config.xsd
index 073b131c..bea5e2c 100644
--- a/services/core/xsd/display-device-config/display-device-config.xsd
+++ b/services/core/xsd/display-device-config/display-device-config.xsd
@@ -23,6 +23,7 @@
<xs:schema version="2.0"
elementFormDefault="qualified"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
+ <xs:include schemaLocation="autobrightness.xsd" />
<xs:element name="displayConfiguration">
<xs:complexType>
<xs:sequence>
@@ -102,22 +103,6 @@
</xs:element>
<!-- Type definitions -->
-
- <xs:complexType name="autoBrightness">
- <xs:sequence>
- <!-- Sets the debounce for autoBrightness brightening in millis-->
- <xs:element name="brighteningLightDebounceMillis" type="xs:nonNegativeInteger"
- minOccurs="0" maxOccurs="1">
- <xs:annotation name="final"/>
- </xs:element>
- <!-- Sets the debounce for autoBrightness darkening in millis-->
- <xs:element name="darkeningLightDebounceMillis" type="xs:nonNegativeInteger"
- minOccurs="0" maxOccurs="1">
- <xs:annotation name="final"/>
- </xs:element>
- </xs:sequence>
- </xs:complexType>
-
<xs:complexType name="displayQuirks">
<xs:sequence>
<xs:element name="quirk" type="xs:string" minOccurs="0" maxOccurs="unbounded" />
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/listeners/ListenerMultiplexerTest.java b/services/tests/mockingservicestests/src/com/android/server/location/listeners/ListenerMultiplexerTest.java
index d7fef60..5b92706 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/listeners/ListenerMultiplexerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/listeners/ListenerMultiplexerTest.java
@@ -58,8 +58,10 @@
void onRegistrationAdded(Consumer<TestListenerRegistration> consumer,
TestListenerRegistration registration);
- void onRegistrationReplaced(Consumer<TestListenerRegistration> consumer,
- TestListenerRegistration oldRegistration, TestListenerRegistration newRegistration);
+ void onRegistrationReplaced(Consumer<TestListenerRegistration> oldConsumer,
+ TestListenerRegistration oldRegistration,
+ Consumer<TestListenerRegistration> newConsumer,
+ TestListenerRegistration newRegistration);
void onRegistrationRemoved(Consumer<TestListenerRegistration> consumer,
TestListenerRegistration registration);
@@ -93,10 +95,10 @@
assertThat(mMultiplexer.mMergedRequest).isEqualTo(0);
mMultiplexer.addListener(1, consumer);
- mInOrder.verify(mCallbacks).onRegistrationRemoved(eq(consumer),
- any(TestListenerRegistration.class));
mInOrder.verify(mCallbacks).onRegistrationReplaced(eq(consumer),
- any(TestListenerRegistration.class), any(TestListenerRegistration.class));
+ any(TestListenerRegistration.class),
+ eq(consumer),
+ any(TestListenerRegistration.class));
assertThat(mMultiplexer.mRegistered).isTrue();
assertThat(mMultiplexer.mMergedRequest).isEqualTo(1);
@@ -115,10 +117,10 @@
any(TestListenerRegistration.class));
mInOrder.verify(mCallbacks).onActive();
mMultiplexer.replaceListener(1, oldConsumer, consumer);
- mInOrder.verify(mCallbacks).onRegistrationRemoved(eq(oldConsumer),
+ mInOrder.verify(mCallbacks).onRegistrationReplaced(eq(oldConsumer),
+ any(TestListenerRegistration.class),
+ eq(consumer),
any(TestListenerRegistration.class));
- mInOrder.verify(mCallbacks).onRegistrationReplaced(eq(consumer),
- any(TestListenerRegistration.class), any(TestListenerRegistration.class));
assertThat(mMultiplexer.mRegistered).isTrue();
assertThat(mMultiplexer.mMergedRequest).isEqualTo(1);
@@ -352,13 +354,19 @@
}
private static class TestListenerRegistration extends
- RequestListenerRegistration<Integer, Consumer<TestListenerRegistration>> {
+ ListenerRegistration<Consumer<TestListenerRegistration>> {
+ private final Integer mInteger;
boolean mActive = true;
protected TestListenerRegistration(Integer integer,
Consumer<TestListenerRegistration> consumer) {
- super(DIRECT_EXECUTOR, integer, consumer);
+ super(DIRECT_EXECUTOR, consumer);
+ mInteger = integer;
+ }
+
+ public Integer getInteger() {
+ return mInteger;
}
}
@@ -375,11 +383,6 @@
mCallbacks = callbacks;
}
- @Override
- public String getTag() {
- return "TestMultiplexer";
- }
-
public void addListener(Integer request, Consumer<TestListenerRegistration> consumer) {
putRegistration(consumer, new TestListenerRegistration(request, consumer));
}
@@ -399,9 +402,9 @@
removeRegistration(consumer, registration);
}
- public void setActive(Integer request, boolean active) {
+ public void setActive(Integer integer, boolean active) {
updateRegistrations(testRegistration -> {
- if (testRegistration.getRequest().equals(request)) {
+ if (testRegistration.getInteger().equals(integer)) {
testRegistration.mActive = active;
return true;
}
@@ -458,10 +461,11 @@
}
@Override
- protected void onRegistrationReplaced(Consumer<TestListenerRegistration> consumer,
+ protected void onRegistrationReplaced(Consumer<TestListenerRegistration> oldKey,
TestListenerRegistration oldRegistration,
+ Consumer<TestListenerRegistration> newKey,
TestListenerRegistration newRegistration) {
- mCallbacks.onRegistrationReplaced(consumer, oldRegistration, newRegistration);
+ mCallbacks.onRegistrationReplaced(oldKey, oldRegistration, newKey, newRegistration);
}
@Override
@@ -475,8 +479,8 @@
Collection<TestListenerRegistration> testRegistrations) {
int max = Integer.MIN_VALUE;
for (TestListenerRegistration registration : testRegistrations) {
- if (registration.getRequest() > max) {
- max = registration.getRequest();
+ if (registration.getInteger() > max) {
+ max = registration.getInteger();
}
}
mMergeCount++;
@@ -493,7 +497,7 @@
@Override
protected void onRegistrationAdded(Consumer<TestListenerRegistration> consumer,
TestListenerRegistration registration) {
- addListener(registration.getRequest(), consumer);
+ addListener(registration.getInteger(), consumer);
}
}
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java
index 71cc65b..0ac1443 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java
@@ -32,7 +32,6 @@
import static com.android.server.location.LocationPermissions.PERMISSION_FINE;
import static com.android.server.location.LocationUtils.createLocation;
import static com.android.server.location.LocationUtils.createLocationResult;
-import static com.android.server.location.listeners.RemoteListenerRegistration.IN_PROCESS_EXECUTOR;
import static com.google.common.truth.Truth.assertThat;
@@ -534,7 +533,7 @@
listener);
CountDownLatch blocker = new CountDownLatch(1);
- IN_PROCESS_EXECUTOR.execute(() -> {
+ FgThread.getExecutor().execute(() -> {
try {
blocker.await();
} catch (InterruptedException e) {
@@ -661,7 +660,7 @@
listener);
CountDownLatch blocker = new CountDownLatch(1);
- IN_PROCESS_EXECUTOR.execute(() -> {
+ FgThread.getExecutor().execute(() -> {
try {
blocker.await();
} catch (InterruptedException e) {
diff --git a/services/tests/servicestests/res/xml/usertypes_test_profile.xml b/services/tests/servicestests/res/xml/usertypes_test_profile.xml
index daa7d7b..b27f49d 100644
--- a/services/tests/servicestests/res/xml/usertypes_test_profile.xml
+++ b/services/tests/servicestests/res/xml/usertypes_test_profile.xml
@@ -30,6 +30,10 @@
<item res='@*android:color/profile_badge_2' />
</badge-colors>
<default-restrictions no_remove_user='true' no_bluetooth='true' />
+ <user-properties
+ showInLauncher='2020'
+ startWithParent='false'
+ />
</profile-type>
<profile-type name='custom.test.1' max-allowed-per-parent='14' />
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java
index a4cccb3..3477288 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java
@@ -369,6 +369,8 @@
throws Exception {
final LockscreenCredential parentPassword = newPassword("parentPassword");
final LockscreenCredential profilePassword = newPattern("12345");
+ mService.setSeparateProfileChallengeEnabled(
+ MANAGED_PROFILE_USER_ID, true, profilePassword);
initializeStorageWithCredential(PRIMARY_USER_ID, parentPassword);
// Create and verify separate profile credentials.
testCreateCredential(MANAGED_PROFILE_USER_ID, profilePassword);
@@ -550,11 +552,12 @@
throws RemoteException {
assertEquals(0, mGateKeeperService.getSecureUserId(userId));
synchronized (mService.mSpManager) {
- mService.initializeSyntheticPasswordLocked(credential, userId);
+ mService.initializeSyntheticPasswordLocked(userId);
}
if (credential.isNone()) {
assertEquals(0, mGateKeeperService.getSecureUserId(userId));
} else {
+ assertTrue(mService.setLockCredential(credential, nonePassword(), userId));
assertNotEquals(0, mGateKeeperService.getSecureUserId(userId));
}
}
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java
new file mode 100644
index 0000000..13a7a3e
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java
@@ -0,0 +1,185 @@
+/*
+ * Copyright (C) 2022 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.pm;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.assertThrows;
+
+import android.content.pm.UserProperties;
+import android.os.Parcel;
+import android.platform.test.annotations.Presubmit;
+import android.util.TypedXmlPullParser;
+import android.util.TypedXmlSerializer;
+import android.util.Xml;
+
+import androidx.test.filters.MediumTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.function.Supplier;
+
+/**
+ * Tests for UserManager's {@link UserProperties}.
+ *
+ * Additional test coverage (that actually exercises the functionality) can be found in
+ * {@link UserManagerTest} and
+ * {@link UserManagerServiceUserTypeTest} (for {@link UserProperties#updateFromXml}).
+ *
+ * <p>Run with: atest UserManagerServiceUserPropertiesTest
+ */
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+@MediumTest
+public class UserManagerServiceUserPropertiesTest {
+
+ /** Test that UserProperties can properly read the xml information that it writes. */
+ @Test
+ public void testWriteReadXml() throws Exception {
+ final UserProperties defaultProps = new UserProperties.Builder()
+ .setShowInLauncher(21)
+ .setStartWithParent(false)
+ .build();
+ final UserProperties actualProps = new UserProperties(defaultProps);
+ actualProps.setShowInLauncher(14);
+
+ // Write the properties to xml.
+ final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ final TypedXmlSerializer out = Xml.newFastSerializer();
+ out.setOutput(baos, StandardCharsets.UTF_8.name());
+ out.startDocument(null, true);
+ out.startTag(null, "testTag");
+ actualProps.writeToXml(out);
+ out.endTag(null, "testTag");
+ out.endDocument();
+
+ // Now read those properties from xml.
+ final ByteArrayInputStream input = new ByteArrayInputStream(baos.toByteArray());
+ final TypedXmlPullParser parser = Xml.newFastPullParser();
+ parser.setInput(input, StandardCharsets.UTF_8.name());
+ parser.nextTag();
+ final UserProperties readProps = new UserProperties(parser, defaultProps);
+
+ assertUserPropertiesEquals(actualProps, readProps);
+ }
+
+ /** Tests parcelling an object in which all properties are present. */
+ @Test
+ public void testParcelUnparcel() throws Exception {
+ final UserProperties originalProps = new UserProperties.Builder()
+ .setShowInLauncher(2145)
+ .build();
+ final UserProperties readProps = parcelThenUnparcel(originalProps);
+ assertUserPropertiesEquals(originalProps, readProps);
+ }
+
+ /** Tests copying a UserProperties object varying permissions. */
+ @Test
+ public void testCopyLacksPermissions() throws Exception {
+ final UserProperties defaultProps = new UserProperties.Builder()
+ .setShowInLauncher(2145)
+ .setStartWithParent(true)
+ .build();
+ final UserProperties orig = new UserProperties(defaultProps);
+ orig.setShowInLauncher(2841);
+ orig.setStartWithParent(false);
+
+ // Test every permission level. (Currently, it's linear so it's easy.)
+ for (int permLevel = 0; permLevel < 4; permLevel++) {
+ final boolean exposeAll = permLevel >= 3;
+ final boolean hasManage = permLevel >= 2;
+ final boolean hasQuery = permLevel >= 1;
+
+ // Make a possibly-not-full-permission (i.e. partial) copy and check that it is correct.
+ final UserProperties copy = new UserProperties(orig, exposeAll, hasManage, hasQuery);
+ verifyTestCopyLacksPermissions(orig, copy, exposeAll, hasManage, hasQuery);
+ if (permLevel < 1) {
+ // PropertiesPresent should definitely be different since not all items were copied.
+ assertThat(orig.getPropertiesPresent()).isNotEqualTo(copy.getPropertiesPresent());
+ }
+
+ // Now, just like in the SystemServer, parcel/unparcel the copy and make sure that the
+ // unparcelled version behaves just like the partial copy did.
+ final UserProperties readProps = parcelThenUnparcel(copy);
+ verifyTestCopyLacksPermissions(orig, readProps, exposeAll, hasManage, hasQuery);
+ }
+ }
+
+ /**
+ * Verifies that the copy of orig has the expected properties
+ * for the test {@link #testCopyLacksPermissions}.
+ */
+ private void verifyTestCopyLacksPermissions(
+ UserProperties orig,
+ UserProperties copy,
+ boolean exposeAll,
+ boolean hasManagePermission,
+ boolean hasQueryPermission) {
+
+ // Items requiring exposeAll.
+ assertEqualGetterOrThrows(orig::getStartWithParent, copy::getStartWithParent, exposeAll);
+
+ // Items requiring hasManagePermission - put them here using hasManagePermission.
+ // Items requiring hasQueryPermission - put them here using hasQueryPermission.
+
+ // Items with no permission requirements.
+ assertEqualGetterOrThrows(orig::getShowInLauncher, copy::getShowInLauncher, true);
+ }
+
+ /**
+ * If hasPerm, then asserts that value of actualGetter equals value of expectedGetter.
+ * If !hasPerm, then asserts that actualGetter throws a SecurityException.
+ */
+ @SuppressWarnings("ReturnValueIgnored")
+ private void assertEqualGetterOrThrows(
+ Supplier expectedGetter,
+ Supplier actualGetter,
+ boolean hasPerm) {
+ if (hasPerm) {
+ assertThat(expectedGetter.get()).isEqualTo(actualGetter.get());
+ } else {
+ assertThrows(SecurityException.class, actualGetter::get);
+ }
+ }
+
+ private UserProperties parcelThenUnparcel(UserProperties originalProps) {
+ final Parcel out = Parcel.obtain();
+ originalProps.writeToParcel(out, 0);
+ final byte[] data = out.marshall();
+ out.recycle();
+
+ final Parcel in = Parcel.obtain();
+ in.unmarshall(data, 0, data.length);
+ in.setDataPosition(0);
+ final UserProperties readProps = UserProperties.CREATOR.createFromParcel(in);
+ in.recycle();
+
+ return readProps;
+ }
+
+ /** Checks that two UserProperties get the same values. */
+ private void assertUserPropertiesEquals(UserProperties expected, UserProperties actual) {
+ assertThat(expected.getPropertiesPresent()).isEqualTo(actual.getPropertiesPresent());
+ assertThat(expected.getShowInLauncher()).isEqualTo(actual.getShowInLauncher());
+ assertThat(expected.getStartWithParent()).isEqualTo(actual.getStartWithParent());
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java
index 971b036..5f48004 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java
@@ -35,6 +35,7 @@
import static org.testng.Assert.assertThrows;
import android.content.pm.UserInfo;
+import android.content.pm.UserProperties;
import android.content.res.Resources;
import android.content.res.XmlResourceParser;
import android.os.Bundle;
@@ -81,6 +82,8 @@
DefaultCrossProfileIntentFilter.Direction.TO_PARENT,
/* flags= */0,
/* letsPersonalDataIntoProfile= */false).build());
+ final UserProperties.Builder userProps = new UserProperties.Builder()
+ .setShowInLauncher(17);
final UserTypeDetails type = new UserTypeDetails.Builder()
.setName("a.name")
.setEnabled(1)
@@ -98,6 +101,7 @@
.setDefaultSystemSettings(systemSettings)
.setDefaultSecureSettings(secureSettings)
.setDefaultCrossProfileIntentFilters(filters)
+ .setDefaultUserProperties(userProps)
.createUserTypeDetails();
assertEquals("a.name", type.getName());
@@ -135,6 +139,8 @@
assertEquals(filters.get(i), type.getDefaultCrossProfileIntentFilters().get(i));
}
+ assertEquals(17, type.getDefaultUserPropertiesReference().getShowInLauncher());
+
assertEquals(23, type.getBadgeLabel(0));
assertEquals(24, type.getBadgeLabel(1));
assertEquals(25, type.getBadgeLabel(2));
@@ -173,6 +179,11 @@
assertTrue(type.getDefaultSecureSettings().isEmpty());
assertTrue(type.getDefaultCrossProfileIntentFilters().isEmpty());
+ final UserProperties props = type.getDefaultUserPropertiesReference();
+ assertNotNull(props);
+ assertFalse(props.getStartWithParent());
+ assertEquals(UserProperties.SHOW_IN_LAUNCHER_WITH_PARENT, props.getShowInLauncher());
+
assertFalse(type.hasBadge());
}
@@ -250,19 +261,24 @@
// Mock some "AOSP defaults".
final Bundle restrictions = makeRestrictionsBundle("no_config_vpn", "no_config_tethering");
+ final UserProperties.Builder props = new UserProperties.Builder()
+ .setShowInLauncher(19)
+ .setStartWithParent(true);
final ArrayMap<String, UserTypeDetails.Builder> builders = new ArrayMap<>();
builders.put(userTypeAosp1, new UserTypeDetails.Builder()
.setName(userTypeAosp1)
.setBaseType(FLAG_PROFILE)
.setMaxAllowedPerParent(31)
- .setDefaultRestrictions(restrictions));
+ .setDefaultRestrictions(restrictions)
+ .setDefaultUserProperties(props));
builders.put(userTypeAosp2, new UserTypeDetails.Builder()
.setName(userTypeAosp1)
.setBaseType(FLAG_PROFILE)
.setMaxAllowedPerParent(32)
.setIconBadge(401)
.setBadgeColors(402, 403, 404)
- .setDefaultRestrictions(restrictions));
+ .setDefaultRestrictions(restrictions)
+ .setDefaultUserProperties(props));
final XmlResourceParser parser = mResources.getXml(R.xml.usertypes_test_profile);
UserTypeFactory.customizeBuilders(builders, parser);
@@ -272,6 +288,8 @@
assertEquals(31, aospType.getMaxAllowedPerParent());
assertEquals(Resources.ID_NULL, aospType.getIconBadge());
assertTrue(UserRestrictionsUtils.areEqual(restrictions, aospType.getDefaultRestrictions()));
+ assertEquals(19, aospType.getDefaultUserPropertiesReference().getShowInLauncher());
+ assertEquals(true, aospType.getDefaultUserPropertiesReference().getStartWithParent());
// userTypeAosp2 should be modified.
aospType = builders.get(userTypeAosp2).createUserTypeDetails();
@@ -300,6 +318,8 @@
assertTrue(UserRestrictionsUtils.areEqual(
makeRestrictionsBundle("no_remove_user", "no_bluetooth"),
aospType.getDefaultRestrictions()));
+ assertEquals(2020, aospType.getDefaultUserPropertiesReference().getShowInLauncher());
+ assertEquals(false, aospType.getDefaultUserPropertiesReference().getStartWithParent());
// userTypeOem1 should be created.
UserTypeDetails.Builder customType = builders.get(userTypeOem1);
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
index 5d48501..f567e80 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
@@ -31,6 +31,7 @@
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
+import android.content.pm.UserProperties;
import android.content.res.Resources;
import android.os.Bundle;
import android.os.UserHandle;
@@ -564,6 +565,33 @@
assertThat(userManagerForUser.isProfile()).isEqualTo(userTypeDetails.isProfile());
}
+ /** Test that UserManager returns the correct UserProperties for a new managed profile. */
+ @MediumTest
+ @Test
+ public void testUserProperties() throws Exception {
+ assumeManagedUsersSupported();
+
+ // Get the default properties for a user type.
+ final UserTypeDetails userTypeDetails =
+ UserTypeFactory.getUserTypes().get(UserManager.USER_TYPE_PROFILE_MANAGED);
+ assertWithMessage("No %s type on device", UserManager.USER_TYPE_PROFILE_MANAGED)
+ .that(userTypeDetails).isNotNull();
+ final UserProperties typeProps = userTypeDetails.getDefaultUserPropertiesReference();
+
+ // Create an actual user (of this user type) and get its properties.
+ final int primaryUserId = mUserManager.getPrimaryUser().id;
+ final UserInfo userInfo = createProfileForUser("Managed",
+ UserManager.USER_TYPE_PROFILE_MANAGED, primaryUserId);
+ assertThat(userInfo).isNotNull();
+ final int userId = userInfo.id;
+ final UserProperties userProps = mUserManager.getUserProperties(UserHandle.of(userId));
+
+ // Check that this new user has the expected properties (relative to the defaults)
+ // provided that the test caller has the necessary permissions.
+ assertThat(userProps.getShowInLauncher()).isEqualTo(typeProps.getShowInLauncher());
+ assertThrows(SecurityException.class, userProps::getStartWithParent);
+ }
+
// Make sure only max managed profiles can be created
@MediumTest
@Test
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java b/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java
index 61a7f38..5c934852 100644
--- a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java
@@ -28,6 +28,7 @@
import androidx.test.runner.AndroidJUnit4;
import com.android.internal.os.BatteryStatsHistory;
+import com.android.internal.os.Clock;
import org.junit.Before;
import org.junit.Test;
@@ -49,13 +50,14 @@
private final Parcel mHistoryBuffer = Parcel.obtain();
private File mSystemDir;
private File mHistoryDir;
+ private final Clock mClock = new MockClock();
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
Context context = InstrumentationRegistry.getContext();
mSystemDir = context.getDataDir();
- mHistoryDir = new File(mSystemDir, BatteryStatsHistory.HISTORY_DIR);
+ mHistoryDir = new File(mSystemDir, "battery-history");
String[] files = mHistoryDir.list();
if (files != null) {
for (int i = 0; i < files.length; i++) {
@@ -67,8 +69,8 @@
@Test
public void testConstruct() {
- BatteryStatsHistory history =
- new BatteryStatsHistory(mHistoryBuffer, mSystemDir, () -> 32);
+ BatteryStatsHistory history = new BatteryStatsHistory(mHistoryBuffer, mSystemDir, 32, 1024,
+ null, mClock);
createActiveFile(history);
verifyFileNumbers(history, Arrays.asList(0));
verifyActiveFile(history, "0.bin");
@@ -76,8 +78,8 @@
@Test
public void testStartNextFile() {
- BatteryStatsHistory history =
- new BatteryStatsHistory(mHistoryBuffer, mSystemDir, () -> 32);
+ BatteryStatsHistory history = new BatteryStatsHistory(mHistoryBuffer, mSystemDir, 32, 1024,
+ null, mClock);
List<Integer> fileList = new ArrayList<>();
fileList.add(0);
createActiveFile(history);
@@ -114,13 +116,13 @@
assertEquals(0, history.getHistoryUsedSize());
// create a new BatteryStatsHistory object, it will pick up existing history files.
- BatteryStatsHistory history2 =
- new BatteryStatsHistory(mHistoryBuffer, mSystemDir, () -> 32);
- // verify construct can pick up all files from file system.
+ BatteryStatsHistory history2 = new BatteryStatsHistory(mHistoryBuffer, mSystemDir, 32, 1024,
+ null, mClock);
+ // verify constructor can pick up all files from file system.
verifyFileNumbers(history2, fileList);
verifyActiveFile(history2, "33.bin");
- history2.resetAllFiles();
+ history2.reset();
createActiveFile(history2);
// verify all existing files are deleted.
for (int i = 2; i < 33; ++i) {
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/MockBatteryStatsImpl.java b/services/tests/servicestests/src/com/android/server/power/stats/MockBatteryStatsImpl.java
index 713e786..570b2ee 100644
--- a/services/tests/servicestests/src/com/android/server/power/stats/MockBatteryStatsImpl.java
+++ b/services/tests/servicestests/src/com/android/server/power/stats/MockBatteryStatsImpl.java
@@ -63,6 +63,7 @@
MockBatteryStatsImpl(Clock clock, File historyDirectory) {
super(clock, historyDirectory);
initTimersAndCounters();
+ setMaxHistoryBuffer(128 * 1024);
setExternalStatsSyncLocked(mExternalStatsSync);
informThatAllExternalStatsAreFlushed();
@@ -104,12 +105,6 @@
return mForceOnBattery ? true : super.isOnBattery();
}
- public void forceRecordAllHistory() {
- mHaveBatteryLevel = true;
- mRecordingHistory = true;
- mRecordAllHistory = true;
- }
-
public TimeBase getOnBatteryBackgroundTimeBase(int uid) {
return getUidStatsLocked(uid).mOnBatteryBackgroundTimeBase;
}
@@ -201,12 +196,14 @@
@GuardedBy("this")
public MockBatteryStatsImpl setMaxHistoryFiles(int maxHistoryFiles) {
mConstants.MAX_HISTORY_FILES = maxHistoryFiles;
+ mConstants.onChange();
return this;
}
@GuardedBy("this")
public MockBatteryStatsImpl setMaxHistoryBuffer(int maxHistoryBuffer) {
mConstants.MAX_HISTORY_BUFFER = maxHistoryBuffer;
+ mConstants.onChange();
return this;
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java b/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java
index 911fb6a..08c2c6e 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java
@@ -1301,6 +1301,21 @@
}
@Test
+ public void testA11yCrossUserEventNotSent() throws Exception {
+ final Notification n = new Builder(getContext(), "test")
+ .setSmallIcon(android.R.drawable.sym_def_app_icon).build();
+ int userId = mUser.getIdentifier() + 1;
+ StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 0, mTag, mUid,
+ mPid, n, UserHandle.of(userId), null, System.currentTimeMillis());
+ NotificationRecord r = new NotificationRecord(getContext(), sbn,
+ new NotificationChannel("test", "test", IMPORTANCE_HIGH));
+
+ mService.buzzBeepBlinkLocked(r);
+
+ verify(mAccessibilityService, never()).sendAccessibilityEvent(any(), anyInt());
+ }
+
+ @Test
public void testLightsScreenOn() {
mService.mScreenOn = true;
NotificationRecord r = getLightsNotification();
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
index 7415460..f61effa 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
@@ -474,7 +474,7 @@
}
@Test
- public void testActivityRecordReparentToTaskFragment() {
+ public void testActivityRecordReparentedToTaskFragment() {
final ActivityRecord activity = createActivityRecord(mDc);
final SurfaceControl activityLeash = mock(SurfaceControl.class);
doNothing().when(activity).setDropInputMode(anyInt());
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
index 8b3cff8..8332cb4 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
@@ -204,7 +204,7 @@
mTaskFragment);
mController.dispatchPendingEvents();
- verify(mOrganizer).onTaskFragmentInfoChanged(mTaskFragmentInfo);
+ verify(mOrganizer).onTaskFragmentInfoChanged(eq(mTaskFragmentInfo));
}
@Test
@@ -231,7 +231,7 @@
verify(mOrganizer, never()).onTaskFragmentAppeared(any());
verify(mOrganizer, never()).onTaskFragmentInfoChanged(any());
verify(mOrganizer, never()).onTaskFragmentParentInfoChanged(anyInt(), any());
- verify(mOrganizer).onTaskFragmentVanished(mTaskFragmentInfo);
+ verify(mOrganizer).onTaskFragmentVanished(eq(mTaskFragmentInfo));
// Not trigger onTaskFragmentInfoChanged.
// Call onTaskFragmentAppeared before calling onTaskFragmentInfoChanged.
@@ -247,7 +247,7 @@
verify(mOrganizer, never()).onTaskFragmentAppeared(any());
verify(mOrganizer, never()).onTaskFragmentInfoChanged(any());
verify(mOrganizer, never()).onTaskFragmentParentInfoChanged(anyInt(), any());
- verify(mOrganizer).onTaskFragmentVanished(mTaskFragmentInfo);
+ verify(mOrganizer).onTaskFragmentVanished(eq(mTaskFragmentInfo));
}
@Test
@@ -298,11 +298,12 @@
mErrorToken, null /* taskFragment */, -1 /* opType */, exception);
mController.dispatchPendingEvents();
- verify(mOrganizer).onTaskFragmentError(eq(mErrorToken), eq(null), eq(-1), eq(exception));
+ verify(mOrganizer).onTaskFragmentError(eq(mErrorToken), eq(null), eq(-1),
+ eq(exception));
}
@Test
- public void testOnActivityReparentToTask_activityInOrganizerProcess_useActivityToken() {
+ public void testOnActivityReparentedToTask_activityInOrganizerProcess_useActivityToken() {
// Make sure the activity pid/uid is the same as the organizer caller.
final int pid = Binder.getCallingPid();
final int uid = Binder.getCallingUid();
@@ -314,17 +315,18 @@
task.effectiveUid = uid;
// No need to notify organizer if it is not embedded.
- mController.onActivityReparentToTask(activity);
+ mController.onActivityReparentedToTask(activity);
mController.dispatchPendingEvents();
- verify(mOrganizer, never()).onActivityReparentToTask(anyInt(), any(), any());
+ verify(mOrganizer, never()).onActivityReparentedToTask(anyInt(), any(), any());
// Notify organizer if it was embedded before entered Pip.
activity.mLastTaskFragmentOrganizerBeforePip = mIOrganizer;
- mController.onActivityReparentToTask(activity);
+ mController.onActivityReparentedToTask(activity);
mController.dispatchPendingEvents();
- verify(mOrganizer).onActivityReparentToTask(task.mTaskId, activity.intent, activity.token);
+ verify(mOrganizer).onActivityReparentedToTask(eq(task.mTaskId), eq(activity.intent),
+ eq(activity.token));
// Notify organizer if there is any embedded in the Task.
final TaskFragment taskFragment = new TaskFragmentBuilder(mAtm)
@@ -335,15 +337,16 @@
DEFAULT_TASK_FRAGMENT_ORGANIZER_PROCESS_NAME);
activity.reparent(taskFragment, POSITION_TOP);
activity.mLastTaskFragmentOrganizerBeforePip = null;
- mController.onActivityReparentToTask(activity);
+ mController.onActivityReparentedToTask(activity);
mController.dispatchPendingEvents();
verify(mOrganizer, times(2))
- .onActivityReparentToTask(task.mTaskId, activity.intent, activity.token);
+ .onActivityReparentedToTask(eq(task.mTaskId), eq(activity.intent),
+ eq(activity.token));
}
@Test
- public void testOnActivityReparentToTask_activityNotInOrganizerProcess_useTemporaryToken() {
+ public void testOnActivityReparentedToTask_activityNotInOrganizerProcess_useTemporaryToken() {
final int pid = Binder.getCallingPid();
final int uid = Binder.getCallingUid();
mTaskFragment.setTaskFragmentOrganizer(mOrganizer.getOrganizerToken(), uid,
@@ -364,11 +367,11 @@
// Notify organizer if it was embedded before entered Pip.
// Create a temporary token since the activity doesn't belong to the same process.
activity.mLastTaskFragmentOrganizerBeforePip = mIOrganizer;
- mController.onActivityReparentToTask(activity);
+ mController.onActivityReparentedToTask(activity);
mController.dispatchPendingEvents();
// Allow organizer to reparent activity in other process using the temporary token.
- verify(mOrganizer).onActivityReparentToTask(eq(task.mTaskId), eq(activity.intent),
+ verify(mOrganizer).onActivityReparentedToTask(eq(task.mTaskId), eq(activity.intent),
token.capture());
final IBinder temporaryToken = token.getValue();
assertNotEquals(activity.token, temporaryToken);
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
index 88eadfc..83f1789 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
@@ -134,6 +134,23 @@
}
@Test
+ public void testStartChangeTransition_doNotFreezeWhenOnlyMoved() {
+ final Rect startBounds = new Rect(0, 0, 1000, 1000);
+ final Rect endBounds = new Rect(startBounds);
+ endBounds.offset(500, 0);
+ mTaskFragment.setBounds(startBounds);
+ doReturn(true).when(mTaskFragment).isVisible();
+ doReturn(true).when(mTaskFragment).isVisibleRequested();
+
+ clearInvocations(mTransaction);
+ mTaskFragment.setBounds(endBounds);
+
+ // No change transition, but update the organized surface position.
+ verify(mTaskFragment, never()).initializeChangeTransition(any(), any());
+ verify(mTransaction).setPosition(mLeash, endBounds.left, endBounds.top);
+ }
+
+ @Test
public void testNotOkToAnimate_doNotStartChangeTransition() {
mockSurfaceFreezerSnapshot(mTaskFragment.mSurfaceFreezer);
final Rect startBounds = new Rect(0, 0, 1000, 1000);
@@ -323,7 +340,7 @@
activity.reparent(task, POSITION_TOP);
// Notify the organizer about the reparent.
- verify(mAtm.mTaskFragmentOrganizerController).onActivityReparentToTask(activity);
+ verify(mAtm.mTaskFragmentOrganizerController).onActivityReparentedToTask(activity);
assertNull(activity.mLastTaskFragmentOrganizerBeforePip);
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
index 5a2d456..85ac7bd 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -36,8 +36,10 @@
import static android.window.TransitionInfo.FLAG_TRANSLUCENT;
import static android.window.TransitionInfo.isIndependent;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static com.android.server.wm.WindowContainer.POSITION_TOP;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -1082,6 +1084,39 @@
assertTrue((info.getChanges().get(1).getFlags() & FLAG_IS_EMBEDDED) != 0);
}
+ @Test
+ public void testIncludeEmbeddedActivityReparent() {
+ final Transition transition = createTestTransition(TRANSIT_OPEN);
+ final Task task = createTask(mDisplayContent);
+ task.setBounds(new Rect(0, 0, 2000, 1000));
+ final ActivityRecord activity = createActivityRecord(task);
+ activity.mVisibleRequested = true;
+ // Skip manipulate the SurfaceControl.
+ doNothing().when(activity).setDropInputMode(anyInt());
+ final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
+ mAtm.mTaskFragmentOrganizerController.registerOrganizer(
+ ITaskFragmentOrganizer.Stub.asInterface(organizer.getOrganizerToken().asBinder()));
+ final TaskFragment embeddedTf = new TaskFragmentBuilder(mAtm)
+ .setParentTask(task)
+ .setOrganizer(organizer)
+ .build();
+ // TaskFragment with different bounds from Task.
+ embeddedTf.setBounds(new Rect(0, 0, 1000, 1000));
+
+ // Start states.
+ transition.collect(activity);
+ transition.collectExistenceChange(embeddedTf);
+
+ // End states.
+ activity.reparent(embeddedTf, POSITION_TOP);
+
+ // Verify that both activity and TaskFragment are included.
+ final ArrayList<WindowContainer> targets = Transition.calculateTargets(
+ transition.mParticipants, transition.mChanges);
+ assertTrue(targets.contains(embeddedTf));
+ assertTrue(targets.contains(activity));
+ }
+
private static void makeTaskOrganized(Task... tasks) {
final ITaskOrganizer organizer = mock(ITaskOrganizer.class);
for (Task t : tasks) {
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index f28408e1a..4fb6587 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -139,24 +139,19 @@
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public static final Uri CONTENT_URI = SimInfo.CONTENT_URI;
- /** @hide */
- public static final String CACHE_KEY_DEFAULT_SUB_ID_PROPERTY =
+ private static final String CACHE_KEY_DEFAULT_SUB_ID_PROPERTY =
"cache_key.telephony.get_default_sub_id";
- /** @hide */
- public static final String CACHE_KEY_DEFAULT_DATA_SUB_ID_PROPERTY =
+ private static final String CACHE_KEY_DEFAULT_DATA_SUB_ID_PROPERTY =
"cache_key.telephony.get_default_data_sub_id";
- /** @hide */
- public static final String CACHE_KEY_DEFAULT_SMS_SUB_ID_PROPERTY =
+ private static final String CACHE_KEY_DEFAULT_SMS_SUB_ID_PROPERTY =
"cache_key.telephony.get_default_sms_sub_id";
- /** @hide */
- public static final String CACHE_KEY_ACTIVE_DATA_SUB_ID_PROPERTY =
+ private static final String CACHE_KEY_ACTIVE_DATA_SUB_ID_PROPERTY =
"cache_key.telephony.get_active_data_sub_id";
- /** @hide */
- public static final String CACHE_KEY_SLOT_INDEX_PROPERTY =
+ private static final String CACHE_KEY_SLOT_INDEX_PROPERTY =
"cache_key.telephony.get_slot_index";
/** @hide */