Merge "Update CreateCredentialRequest and GetCredentialOption with recent changes."
diff --git a/cmds/idmap2/libidmap2/ResourceMapping.cpp b/cmds/idmap2/libidmap2/ResourceMapping.cpp
index bb31c11..b2300ce 100644
--- a/cmds/idmap2/libidmap2/ResourceMapping.cpp
+++ b/cmds/idmap2/libidmap2/ResourceMapping.cpp
@@ -89,7 +89,7 @@
// If the overlay supplies a target overlayable name, the resource must belong to the
// overlayable defined with the specified name to be overlaid.
return Error(R"(<overlay> android:targetName "%s" does not match overlayable name "%s")",
- overlay_info.target_name.c_str(), (*overlayable_info)->name.c_str());
+ overlay_info.target_name.c_str(), (*overlayable_info)->name.data());
}
// Enforce policy restrictions if the resource is declared as overlayable.
diff --git a/cmds/svc/src/com/android/commands/svc/UsbCommand.java b/cmds/svc/src/com/android/commands/svc/UsbCommand.java
index 7d80493..26e20f6 100644
--- a/cmds/svc/src/com/android/commands/svc/UsbCommand.java
+++ b/cmds/svc/src/com/android/commands/svc/UsbCommand.java
@@ -29,12 +29,18 @@
import java.util.function.Consumer;
import java.util.concurrent.Executor;
import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
public class UsbCommand extends Svc.Command {
public UsbCommand() {
super("usb");
}
+ /**
+ * Counter for tracking UsbOperation operations.
+ */
+ private static final AtomicInteger sUsbOperationCount = new AtomicInteger();
+
@Override
public String shortHelp() {
return "Control Usb state";
@@ -92,8 +98,10 @@
if ("setFunctions".equals(args[1])) {
try {
+ int operationId = sUsbOperationCount.incrementAndGet();
+ System.out.println("setCurrentFunctions opId:" + operationId);
usbMgr.setCurrentFunctions(UsbManager.usbFunctionsFromString(
- args.length >= 3 ? args[2] : ""));
+ args.length >= 3 ? args[2] : ""), operationId);
} catch (RemoteException e) {
System.err.println("Error communicating with UsbManager: " + e);
}
diff --git a/core/api/current.txt b/core/api/current.txt
index 14f8d37..17b0d9e 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -6969,6 +6969,7 @@
method @Deprecated public void onStart(android.content.Intent, int);
method public int onStartCommand(android.content.Intent, int, int);
method public void onTaskRemoved(android.content.Intent);
+ method public void onTimeout(int);
method public void onTrimMemory(int);
method public boolean onUnbind(android.content.Intent);
method public final void startForeground(int, android.app.Notification);
@@ -12475,6 +12476,7 @@
field @Deprecated public static final int FOREGROUND_SERVICE_TYPE_NONE = 0; // 0x0
field @RequiresPermission(allOf={android.Manifest.permission.FOREGROUND_SERVICE_PHONE_CALL}, anyOf={android.Manifest.permission.MANAGE_OWN_CALLS}, conditional=true) public static final int FOREGROUND_SERVICE_TYPE_PHONE_CALL = 4; // 0x4
field @RequiresPermission(value=android.Manifest.permission.FOREGROUND_SERVICE_REMOTE_MESSAGING, conditional=true) public static final int FOREGROUND_SERVICE_TYPE_REMOTE_MESSAGING = 512; // 0x200
+ field public static final int FOREGROUND_SERVICE_TYPE_SHORT_SERVICE = 2048; // 0x800
field @RequiresPermission(value=android.Manifest.permission.FOREGROUND_SERVICE_SPECIAL_USE, conditional=true) public static final int FOREGROUND_SERVICE_TYPE_SPECIAL_USE = 1073741824; // 0x40000000
field @RequiresPermission(value=android.Manifest.permission.FOREGROUND_SERVICE_SYSTEM_EXEMPTED, conditional=true) public static final int FOREGROUND_SERVICE_TYPE_SYSTEM_EXEMPTED = 1024; // 0x400
field public int flags;
@@ -15484,7 +15486,6 @@
}
public static class PathIterator.Segment {
- ctor public PathIterator.Segment(@NonNull int, @NonNull float[], float);
method public float getConicWeight();
method @NonNull public float[] getPoints();
method @NonNull public int getVerb();
@@ -48696,6 +48697,7 @@
method public float getRefreshRate();
method public int getRotation();
method @Nullable public android.view.RoundedCorner getRoundedCorner(int);
+ method @NonNull public android.view.DisplayShape getShape();
method @Deprecated public void getSize(android.graphics.Point);
method public int getState();
method public android.view.Display.Mode[] getSupportedModes();
@@ -48776,6 +48778,13 @@
method @NonNull public android.view.DisplayCutout.Builder setWaterfallInsets(@NonNull android.graphics.Insets);
}
+ public final class DisplayShape implements android.os.Parcelable {
+ method public int describeContents();
+ method @NonNull public android.graphics.Path getPath();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.view.DisplayShape> CREATOR;
+ }
+
public final class DragAndDropPermissions implements android.os.Parcelable {
method public int describeContents();
method public void release();
@@ -52157,6 +52166,7 @@
method @Deprecated @NonNull public android.view.WindowInsets consumeStableInsets();
method @Deprecated @NonNull public android.view.WindowInsets consumeSystemWindowInsets();
method @Nullable public android.view.DisplayCutout getDisplayCutout();
+ method @Nullable public android.view.DisplayShape getDisplayShape();
method @NonNull public android.graphics.Insets getInsets(int);
method @NonNull public android.graphics.Insets getInsetsIgnoringVisibility(int);
method @Deprecated @NonNull public android.graphics.Insets getMandatorySystemGestureInsets();
@@ -52192,6 +52202,7 @@
ctor public WindowInsets.Builder(@NonNull android.view.WindowInsets);
method @NonNull public android.view.WindowInsets build();
method @NonNull public android.view.WindowInsets.Builder setDisplayCutout(@Nullable android.view.DisplayCutout);
+ method @NonNull public android.view.WindowInsets.Builder setDisplayShape(@NonNull android.view.DisplayShape);
method @NonNull public android.view.WindowInsets.Builder setInsets(int, @NonNull android.graphics.Insets);
method @NonNull public android.view.WindowInsets.Builder setInsetsIgnoringVisibility(int, @NonNull android.graphics.Insets) throws java.lang.IllegalArgumentException;
method @Deprecated @NonNull public android.view.WindowInsets.Builder setMandatorySystemGestureInsets(@NonNull android.graphics.Insets);
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index b6e2d2a..3228ce6 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -121,6 +121,7 @@
field public static final int GADGET_HAL_V1_0 = 10; // 0xa
field public static final int GADGET_HAL_V1_1 = 11; // 0xb
field public static final int GADGET_HAL_V1_2 = 12; // 0xc
+ field public static final int GADGET_HAL_V2_0 = 20; // 0x14
field public static final int USB_DATA_TRANSFER_RATE_10G = 10240; // 0x2800
field public static final int USB_DATA_TRANSFER_RATE_20G = 20480; // 0x5000
field public static final int USB_DATA_TRANSFER_RATE_40G = 40960; // 0xa000
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index c10504d..c3e9887 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -10097,6 +10097,7 @@
method @NonNull public java.util.List<android.os.UserHandle> getAllProfiles();
method @NonNull public java.util.List<android.os.UserHandle> getEnabledProfiles();
method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS, android.Manifest.permission.QUERY_USERS}) public android.os.UserHandle getMainUser();
+ method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS, android.Manifest.permission.QUERY_USERS}) public android.os.UserHandle getPreviousForegroundUser();
method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}) public android.os.UserHandle getProfileParent(@NonNull android.os.UserHandle);
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS, android.Manifest.permission.QUERY_USERS}) public int getRemainingCreatableProfileCount(@NonNull String);
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS, android.Manifest.permission.QUERY_USERS}) public int getRemainingCreatableUserCount(@NonNull String);
@@ -12873,14 +12874,14 @@
method @NonNull public android.telephony.BarringInfo createLocationInfoSanitizedCopy();
}
- public final class CallAttributes implements android.os.Parcelable {
- ctor public CallAttributes(@NonNull android.telephony.PreciseCallState, int, @NonNull android.telephony.CallQuality);
- method public int describeContents();
- method @NonNull public android.telephony.CallQuality getCallQuality();
- method public int getNetworkType();
- method @NonNull public android.telephony.PreciseCallState getPreciseCallState();
- method public void writeToParcel(android.os.Parcel, int);
- field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CallAttributes> CREATOR;
+ @Deprecated public final class CallAttributes implements android.os.Parcelable {
+ ctor @Deprecated public CallAttributes(@NonNull android.telephony.PreciseCallState, int, @NonNull android.telephony.CallQuality);
+ method @Deprecated public int describeContents();
+ method @Deprecated @NonNull public android.telephony.CallQuality getCallQuality();
+ method @Deprecated public int getNetworkType();
+ method @Deprecated @NonNull public android.telephony.PreciseCallState getPreciseCallState();
+ method @Deprecated public void writeToParcel(android.os.Parcel, int);
+ field @Deprecated @NonNull public static final android.os.Parcelable.Creator<android.telephony.CallAttributes> CREATOR;
}
public final class CallForwardingInfo implements android.os.Parcelable {
@@ -12960,6 +12961,28 @@
method @NonNull public android.telephony.CallQuality.Builder setUplinkCallQualityLevel(int);
}
+ public final class CallState implements android.os.Parcelable {
+ method public int describeContents();
+ method @Nullable public android.telephony.CallQuality getCallQuality();
+ method public int getCallState();
+ method public int getImsCallServiceType();
+ method @Nullable public String getImsCallSessionId();
+ method public int getImsCallType();
+ method public int getNetworkType();
+ method public void writeToParcel(@Nullable android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CallState> CREATOR;
+ }
+
+ public static final class CallState.Builder {
+ ctor public CallState.Builder(int);
+ method @NonNull public android.telephony.CallState build();
+ method @NonNull public android.telephony.CallState.Builder setCallQuality(@Nullable android.telephony.CallQuality);
+ method @NonNull public android.telephony.CallState.Builder setImsCallServiceType(int);
+ method @NonNull public android.telephony.CallState.Builder setImsCallSessionId(@Nullable String);
+ method @NonNull public android.telephony.CallState.Builder setImsCallType(int);
+ method @NonNull public android.telephony.CallState.Builder setNetworkType(int);
+ }
+
public class CarrierConfigManager {
method @NonNull @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String getDefaultCarrierServicePackageName();
method @NonNull public static android.os.PersistableBundle getDefaultConfig();
@@ -13710,7 +13733,8 @@
}
public static interface TelephonyCallback.CallAttributesListener {
- method @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public void onCallAttributesChanged(@NonNull android.telephony.CallAttributes);
+ method @Deprecated @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public default void onCallAttributesChanged(@NonNull android.telephony.CallAttributes);
+ method @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public default void onCallStatesChanged(@NonNull java.util.List<android.telephony.CallState>);
}
public static interface TelephonyCallback.DataEnabledListener {
@@ -14811,6 +14835,7 @@
field public static final int CALL_RESTRICT_CAUSE_HD = 3; // 0x3
field public static final int CALL_RESTRICT_CAUSE_NONE = 0; // 0x0
field public static final int CALL_RESTRICT_CAUSE_RAT = 1; // 0x1
+ field public static final int CALL_TYPE_NONE = 0; // 0x0
field public static final int CALL_TYPE_VIDEO_N_VOICE = 3; // 0x3
field public static final int CALL_TYPE_VOICE = 2; // 0x2
field public static final int CALL_TYPE_VOICE_N_VIDEO = 1; // 0x1
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 1370575..984e822 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -2888,6 +2888,10 @@
method @NonNull public android.view.Display.Mode.Builder setResolution(int, int);
}
+ public final class DisplayShape implements android.os.Parcelable {
+ method @NonNull public static android.view.DisplayShape fromSpecString(@NonNull String, float, int, int);
+ }
+
public class FocusFinder {
method public static void sort(android.view.View[], int, int, android.view.ViewGroup, boolean);
}
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 884870b..96ced41 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -502,6 +502,7 @@
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
static volatile Handler sMainThreadHandler; // set once in main()
+ private long mStartSeq; // Only accesssed from the main thread
Bundle mCoreSettings = null;
@@ -6809,6 +6810,14 @@
Application app;
final StrictMode.ThreadPolicy savedPolicy = StrictMode.allowThreadDiskWrites();
final StrictMode.ThreadPolicy writesAllowedPolicy = StrictMode.getThreadPolicy();
+
+ final IActivityManager mgr = ActivityManager.getService();
+ try {
+ mgr.finishAttachApplication(mStartSeq);
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
+ }
+
try {
// If the app is being launched for full backup or restore, bring it up in
// a restricted environment with the base application class.
@@ -7649,6 +7658,8 @@
sCurrentActivityThread = this;
mConfigurationController = new ConfigurationController(this);
mSystemThread = system;
+ mStartSeq = startSeq;
+
if (!system) {
android.ddm.DdmHandleAppName.setAppName("<pre-initialized>",
UserHandle.myUserId());
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index 902f172..3edaabd 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -147,6 +147,7 @@
oneway void finishReceiver(in IBinder who, int resultCode, in String resultData, in Bundle map,
boolean abortBroadcast, int flags);
void attachApplication(in IApplicationThread app, long startSeq);
+ void finishAttachApplication(long startSeq);
List<ActivityManager.RunningTaskInfo> getTasks(int maxNum);
@UnsupportedAppUsage
void moveTaskToFront(in IApplicationThread caller, in String callingPackage, int task,
@@ -718,8 +719,8 @@
/**
* Control the app freezer state. Returns true in case of success, false if the operation
- * didn't succeed (for example, when the app freezer isn't supported).
- * Handling the freezer state via this method is reentrant, that is it can be
+ * didn't succeed (for example, when the app freezer isn't supported).
+ * Handling the freezer state via this method is reentrant, that is it can be
* disabled and re-enabled multiple times in parallel. As long as there's a 1:1 disable to
* enable match, the freezer is re-enabled at last enable only.
* @param enable set it to true to enable the app freezer, false to disable it.
diff --git a/core/java/android/app/Service.java b/core/java/android/app/Service.java
index 6d7a161..3a7d483 100644
--- a/core/java/android/app/Service.java
+++ b/core/java/android/app/Service.java
@@ -1128,12 +1128,10 @@
/**
* Callback called on timeout for {@link ServiceInfo#FOREGROUND_SERVICE_TYPE_SHORT_SERVICE}.
+ * See {@link ServiceInfo#FOREGROUND_SERVICE_TYPE_SHORT_SERVICE} for more details.
*
- * TODO Implement it
- * TODO Javadoc
- *
- * @param startId
- * @hide
+ * @param startId the startId passed to {@link #onStartCommand(Intent, int, int)} when
+ * the service started.
*/
public void onTimeout(int startId) {
}
diff --git a/core/java/android/content/pm/ServiceInfo.java b/core/java/android/content/pm/ServiceInfo.java
index 9e6cf62..7ea6733 100644
--- a/core/java/android/content/pm/ServiceInfo.java
+++ b/core/java/android/content/pm/ServiceInfo.java
@@ -366,24 +366,48 @@
public static final int FOREGROUND_SERVICE_TYPE_SYSTEM_EXEMPTED = 1 << 10;
/**
- * Foreground service type corresponding to {@code shortService} in
- * the {@link android.R.attr#foregroundServiceType} attribute.
+ * A foreground service type for "short-lived" services, which corresponds to
+ * {@code shortService} in the {@link android.R.attr#foregroundServiceType} attribute in the
+ * manifest.
*
- * TODO Implement it
+ * <p>Unlike other foreground service types, this type is not associated with a specific use
+ * case, and it will not require any special permissions
+ * (besides {@link Manifest.permission#FOREGROUND_SERVICE}).
*
- * TODO Expand the javadoc
+ * However, this type has the following restrictions.
*
- * This type is not associated with specific use cases unlike other types, but this has
- * unique restrictions.
* <ul>
- * <li>Has a timeout
- * <li>Cannot start other foreground services from this
* <li>
+ * The type has a 1 minute timeout.
+ * A foreground service of this type must be stopped within the timeout by
+ * {@link android.app.Service#stopSelf),
+ * or {@link android.content.Context#stopService).
+ * {@link android.app.Service#stopForeground) will also work, which will demote the
+ * service to a "background" service, which will soon be stopped by the system.
+ *
+ * <p>The system will <em>not</em> automatically stop it.
+ *
+ * <p>If the service isn't stopped within the timeout,
+ * {@link android.app.Service#onTimeout(int)} will be called.
+ * If the service is still not stopped after the callback,
+ * the app will be declared an ANR.
+ *
+ * <li>
+ * A foreground service of this type cannot be made "sticky"
+ * (see {@link android.app.Service#START_STICKY}). That is, if an app is killed
+ * due to a crash or out-of memory while it's running a short foregorund-service,
+ * the system will not restart the service.
+ * <li>
+ * Other foreground services cannot be started from short foreground services.
+ * Unlike other foreground service types, when an app is running in the background
+ * while only having a "short" foreground service, it's not allowed to start
+ * other foreground services, due to the restriction describe here:
+ * <a href="/guide/components/foreground-services#background-start-restrictions>
+ * Restrictions on background starts
+ * </a>
* </ul>
*
- * @see Service#onTimeout
- *
- * @hide
+ * @see android.app.Service#onTimeout(int)
*/
public static final int FOREGROUND_SERVICE_TYPE_SHORT_SERVICE = 1 << 11;
diff --git a/core/java/android/content/res/loader/ResourcesProvider.java b/core/java/android/content/res/loader/ResourcesProvider.java
index 463dcac..a5a1fa689 100644
--- a/core/java/android/content/res/loader/ResourcesProvider.java
+++ b/core/java/android/content/res/loader/ResourcesProvider.java
@@ -18,7 +18,10 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SuppressLint;
import android.content.Context;
+import android.content.om.OverlayInfo;
+import android.content.om.OverlayManager;
import android.content.pm.ApplicationInfo;
import android.content.res.ApkAssets;
import android.content.res.AssetFileDescriptor;
@@ -27,11 +30,17 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.content.om.OverlayManagerImpl;
import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.Preconditions;
import java.io.Closeable;
import java.io.File;
+import java.io.FileNotFoundException;
import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Objects;
/**
* Provides methods to load resources data from APKs ({@code .apk}) and resources tables
@@ -63,6 +72,48 @@
}
/**
+ * Creates a ResourcesProvider instance from the specified overlay information.
+ *
+ * <p>In order to enable the registered overlays, an application can create a {@link
+ * ResourcesProvider} instance according to the specified {@link OverlayInfo} instance and put
+ * them into a {@link ResourcesLoader} instance. The application calls {@link
+ * android.content.res.Resources#addLoaders(ResourcesLoader...)} to load the overlays.
+ *
+ * @param overlayInfo is the information about the specified overlay
+ * @return the resources provider instance for the {@code overlayInfo}
+ * @throws IOException when the files can't be loaded.
+ * @see OverlayManager#getOverlayInfosForTarget(String) to get the list of overlay info.
+ * @hide
+ */
+ @SuppressLint("WrongConstant") // TODO(b/238713267): ApkAssets blocks PROPERTY_LOADER
+ @NonNull
+ public static ResourcesProvider loadOverlay(@NonNull OverlayInfo overlayInfo)
+ throws IOException {
+ Objects.requireNonNull(overlayInfo);
+ Preconditions.checkArgument(overlayInfo.isFabricated(), "Not accepted overlay");
+ Preconditions.checkStringNotEmpty(
+ overlayInfo.getTargetOverlayableName(), "Without overlayable name");
+ final String overlayName =
+ OverlayManagerImpl.checkOverlayNameValid(overlayInfo.getOverlayName());
+ final String path =
+ Preconditions.checkStringNotEmpty(
+ overlayInfo.getBaseCodePath(), "Invalid base path");
+
+ final Path frroPath = Path.of(path);
+ if (!Files.isRegularFile(frroPath)) {
+ throw new FileNotFoundException("The frro file not found");
+ }
+ final Path idmapPath = frroPath.getParent().resolve(overlayName + ".idmap");
+ if (!Files.isRegularFile(idmapPath)) {
+ throw new FileNotFoundException("The idmap file not found");
+ }
+
+ return new ResourcesProvider(
+ ApkAssets.loadOverlayFromPath(
+ idmapPath.toString(), 0 /* flags: self targeting overlay */));
+ }
+
+ /**
* Creates a ResourcesProvider from an APK ({@code .apk}) file descriptor.
*
* <p>The file descriptor is duplicated and the original may be closed by the application at any
diff --git a/core/java/android/hardware/usb/IUsbManager.aidl b/core/java/android/hardware/usb/IUsbManager.aidl
index 51236fe3..248b5d0 100644
--- a/core/java/android/hardware/usb/IUsbManager.aidl
+++ b/core/java/android/hardware/usb/IUsbManager.aidl
@@ -122,10 +122,10 @@
boolean isFunctionEnabled(String function);
/* Sets the current USB function. */
- void setCurrentFunctions(long functions);
+ void setCurrentFunctions(long functions, int operationId);
/* Compatibility version of setCurrentFunctions(long). */
- void setCurrentFunction(String function, boolean usbDataUnlocked);
+ void setCurrentFunction(String function, boolean usbDataUnlocked, int operationId);
/* Gets the current USB functions. */
long getCurrentFunctions();
diff --git a/core/java/android/hardware/usb/UsbManager.java b/core/java/android/hardware/usb/UsbManager.java
index 342c336..7a8117c 100644
--- a/core/java/android/hardware/usb/UsbManager.java
+++ b/core/java/android/hardware/usb/UsbManager.java
@@ -38,6 +38,7 @@
import android.content.pm.PackageManager.NameNotFoundException;
import android.hardware.usb.gadget.V1_0.GadgetFunction;
import android.hardware.usb.gadget.V1_2.UsbSpeed;
+import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
import android.os.ParcelFileDescriptor;
@@ -52,6 +53,7 @@
import java.util.Map;
import java.util.Objects;
import java.util.StringJoiner;
+import java.util.concurrent.atomic.AtomicInteger;
/**
* This class allows you to access the state of USB and communicate with USB devices.
@@ -95,7 +97,7 @@
* If the sticky intent has not been found, that indicates USB is disconnected,
* USB is not configued, MTP function is enabled, and all the other functions are disabled.
*
- * {@hide}
+ * @hide
*/
@SystemApi
public static final String ACTION_USB_STATE =
@@ -185,7 +187,7 @@
* <p>For more information about communicating with USB accessory handshake, refer to
* <a href="https://source.android.com/devices/accessories/aoa">AOA</a> developer guide.</p>
*
- * {@hide}
+ * @hide
*/
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
@SystemApi
@@ -197,7 +199,7 @@
* Boolean extra indicating whether USB is connected or disconnected.
* Used in extras for the {@link #ACTION_USB_STATE} broadcast.
*
- * {@hide}
+ * @hide
*/
@SystemApi
public static final String USB_CONNECTED = "connected";
@@ -206,7 +208,7 @@
* Boolean extra indicating whether USB is connected or disconnected as host.
* Used in extras for the {@link #ACTION_USB_STATE} broadcast.
*
- * {@hide}
+ * @hide
*/
public static final String USB_HOST_CONNECTED = "host_connected";
@@ -214,7 +216,7 @@
* Boolean extra indicating whether USB is configured.
* Used in extras for the {@link #ACTION_USB_STATE} broadcast.
*
- * {@hide}
+ * @hide
*/
@SystemApi
public static final String USB_CONFIGURED = "configured";
@@ -225,7 +227,7 @@
* has explicitly asked for this data to be unlocked.
* Used in extras for the {@link #ACTION_USB_STATE} broadcast.
*
- * {@hide}
+ * @hide
*/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public static final String USB_DATA_UNLOCKED = "unlocked";
@@ -234,7 +236,7 @@
* A placeholder indicating that no USB function is being specified.
* Used for compatibility with old init scripts to indicate no functions vs. charging function.
*
- * {@hide}
+ * @hide
*/
@UnsupportedAppUsage
public static final String USB_FUNCTION_NONE = "none";
@@ -243,7 +245,7 @@
* Name of the adb USB function.
* Used in extras for the {@link #ACTION_USB_STATE} broadcast
*
- * {@hide}
+ * @hide
*/
public static final String USB_FUNCTION_ADB = "adb";
@@ -251,7 +253,7 @@
* Name of the RNDIS ethernet USB function.
* Used in extras for the {@link #ACTION_USB_STATE} broadcast
*
- * {@hide}
+ * @hide
*/
@SystemApi
public static final String USB_FUNCTION_RNDIS = "rndis";
@@ -260,7 +262,7 @@
* Name of the MTP USB function.
* Used in extras for the {@link #ACTION_USB_STATE} broadcast
*
- * {@hide}
+ * @hide
*/
public static final String USB_FUNCTION_MTP = "mtp";
@@ -268,7 +270,7 @@
* Name of the PTP USB function.
* Used in extras for the {@link #ACTION_USB_STATE} broadcast
*
- * {@hide}
+ * @hide
*/
public static final String USB_FUNCTION_PTP = "ptp";
@@ -276,7 +278,7 @@
* Name of the audio source USB function.
* Used in extras for the {@link #ACTION_USB_STATE} broadcast
*
- * {@hide}
+ * @hide
*/
public static final String USB_FUNCTION_AUDIO_SOURCE = "audio_source";
@@ -284,7 +286,7 @@
* Name of the MIDI USB function.
* Used in extras for the {@link #ACTION_USB_STATE} broadcast
*
- * {@hide}
+ * @hide
*/
public static final String USB_FUNCTION_MIDI = "midi";
@@ -292,7 +294,7 @@
* Name of the Accessory USB function.
* Used in extras for the {@link #ACTION_USB_STATE} broadcast
*
- * {@hide}
+ * @hide
*/
public static final String USB_FUNCTION_ACCESSORY = "accessory";
@@ -300,7 +302,7 @@
* Name of the NCM USB function.
* Used in extras for the {@link #ACTION_USB_STATE} broadcast
*
- * {@hide}
+ * @hide
*/
@SystemApi
public static final String USB_FUNCTION_NCM = "ncm";
@@ -308,32 +310,39 @@
/**
* Name of Gadget Hal Not Present;
*
- * {@hide}
+ * @hide
*/
public static final String GADGET_HAL_UNKNOWN = "unknown";
/**
* Name of the USB Gadget Hal Version v1.0;
*
- * {@hide}
+ * @hide
*/
public static final String GADGET_HAL_VERSION_1_0 = "V1_0";
/**
* Name of the USB Gadget Hal Version v1.1;
*
- * {@hide}
+ * @hide
*/
public static final String GADGET_HAL_VERSION_1_1 = "V1_1";
/**
* Name of the USB Gadget Hal Version v1.2;
*
- * {@hide}
+ * @hide
*/
public static final String GADGET_HAL_VERSION_1_2 = "V1_2";
/**
+ * Name of the USB Gadget Hal Version v2.0;
+ *
+ * @hide
+ */
+ public static final String GADGET_HAL_VERSION_2_0 = "V2_0";
+
+ /**
* Name of extra for {@link #ACTION_USB_PORT_CHANGED}
* containing the {@link UsbPort} object for the port.
*
@@ -369,7 +378,7 @@
* This is obtained with SystemClock.elapsedRealtime()
* Used in extras for {@link #ACTION_USB_ACCESSORY_HANDSHAKE} broadcasts.
*
- * {@hide}
+ * @hide
*/
@SystemApi
public static final String EXTRA_ACCESSORY_UEVENT_TIME =
@@ -383,7 +392,7 @@
* between communicating with USB accessory handshake, refer to
* <a href="https://source.android.com/devices/accessories/aoa">AOA</a> developer guide.</p>
*
- * {@hide}
+ * @hide
*/
@SystemApi
public static final String EXTRA_ACCESSORY_STRING_COUNT =
@@ -393,7 +402,7 @@
* Boolean extra indicating whether got start accessory or not
* Used in extras for {@link #ACTION_USB_ACCESSORY_HANDSHAKE} broadcasts.
*
- * {@hide}
+ * @hide
*/
@SystemApi
public static final String EXTRA_ACCESSORY_START =
@@ -405,7 +414,7 @@
* sending {@link #ACTION_USB_ACCESSORY_HANDSHAKE}.
* Used in extras for {@link #ACTION_USB_ACCESSORY_HANDSHAKE} broadcasts.
*
- * {@hide}
+ * @hide
*/
@SystemApi
public static final String EXTRA_ACCESSORY_HANDSHAKE_END =
@@ -439,7 +448,7 @@
/**
* The Value for USB gadget hal is not presented.
*
- * {@hide}
+ * @hide
*/
@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
public static final int GADGET_HAL_NOT_SUPPORTED = -1;
@@ -447,7 +456,7 @@
/**
* Value for Gadget Hal Version v1.0.
*
- * {@hide}
+ * @hide
*/
@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
public static final int GADGET_HAL_V1_0 = 10;
@@ -455,7 +464,7 @@
/**
* Value for Gadget Hal Version v1.1.
*
- * {@hide}
+ * @hide
*/
@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
public static final int GADGET_HAL_V1_1 = 11;
@@ -463,15 +472,23 @@
/**
* Value for Gadget Hal Version v1.2.
*
- * {@hide}
+ * @hide
*/
@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
public static final int GADGET_HAL_V1_2 = 12;
/**
+ * Value for Gadget Hal Version v2.0.
+ *
+ * @hide
+ */
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ public static final int GADGET_HAL_V2_0 = 20;
+
+ /**
* Value for USB_STATE is not configured.
*
- * {@hide}
+ * @hide
*/
@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
public static final int USB_DATA_TRANSFER_RATE_UNKNOWN = -1;
@@ -479,7 +496,7 @@
/**
* Value for USB Transfer Rate of Low Speed in Mbps (real value is 1.5Mbps).
*
- * {@hide}
+ * @hide
*/
@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
public static final int USB_DATA_TRANSFER_RATE_LOW_SPEED = 2;
@@ -487,7 +504,7 @@
/**
* Value for USB Transfer Rate of Full Speed in Mbps.
*
- * {@hide}
+ * @hide
*/
@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
public static final int USB_DATA_TRANSFER_RATE_FULL_SPEED = 12;
@@ -495,7 +512,7 @@
/**
* Value for USB Transfer Rate of High Speed in Mbps.
*
- * {@hide}
+ * @hide
*/
@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
public static final int USB_DATA_TRANSFER_RATE_HIGH_SPEED = 480;
@@ -503,7 +520,7 @@
/**
* Value for USB Transfer Rate of Super Speed in Mbps.
*
- * {@hide}
+ * @hide
*/
@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
public static final int USB_DATA_TRANSFER_RATE_5G = 5 * 1024;
@@ -511,7 +528,7 @@
/**
* Value for USB Transfer Rate of 10G.
*
- * {@hide}
+ * @hide
*/
@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
public static final int USB_DATA_TRANSFER_RATE_10G = 10 * 1024;
@@ -519,7 +536,7 @@
/**
* Value for USB Transfer Rate of 20G.
*
- * {@hide}
+ * @hide
*/
@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
public static final int USB_DATA_TRANSFER_RATE_20G = 20 * 1024;
@@ -527,7 +544,7 @@
/**
* Value for USB Transfer Rate of 40G.
*
- * {@hide}
+ * @hide
*/
@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
public static final int USB_DATA_TRANSFER_RATE_40G = 40 * 1024;
@@ -543,7 +560,7 @@
/**
* The Value for USB hal is not presented.
*
- * {@hide}
+ * @hide
*/
@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
public static final int USB_HAL_NOT_SUPPORTED = -1;
@@ -551,7 +568,7 @@
/**
* Value for USB Hal Version v1.0.
*
- * {@hide}
+ * @hide
*/
@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
public static final int USB_HAL_V1_0 = 10;
@@ -559,7 +576,7 @@
/**
* Value for USB Hal Version v1.1.
*
- * {@hide}
+ * @hide
*/
@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
public static final int USB_HAL_V1_1 = 11;
@@ -567,7 +584,7 @@
/**
* Value for USB Hal Version v1.2.
*
- * {@hide}
+ * @hide
*/
@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
public static final int USB_HAL_V1_2 = 12;
@@ -575,7 +592,7 @@
/**
* Value for USB Hal Version v1.3.
*
- * {@hide}
+ * @hide
*/
@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
public static final int USB_HAL_V1_3 = 13;
@@ -590,63 +607,63 @@
/**
* Code for the charging usb function. Passed into {@link #setCurrentFunctions(long)}
- * {@hide}
+ * @hide
*/
@SystemApi
public static final long FUNCTION_NONE = 0;
/**
* Code for the mtp usb function. Passed as a mask into {@link #setCurrentFunctions(long)}
- * {@hide}
+ * @hide
*/
@SystemApi
public static final long FUNCTION_MTP = GadgetFunction.MTP;
/**
* Code for the ptp usb function. Passed as a mask into {@link #setCurrentFunctions(long)}
- * {@hide}
+ * @hide
*/
@SystemApi
public static final long FUNCTION_PTP = GadgetFunction.PTP;
/**
* Code for the rndis usb function. Passed as a mask into {@link #setCurrentFunctions(long)}
- * {@hide}
+ * @hide
*/
@SystemApi
public static final long FUNCTION_RNDIS = GadgetFunction.RNDIS;
/**
* Code for the midi usb function. Passed as a mask into {@link #setCurrentFunctions(long)}
- * {@hide}
+ * @hide
*/
@SystemApi
public static final long FUNCTION_MIDI = GadgetFunction.MIDI;
/**
* Code for the accessory usb function.
- * {@hide}
+ * @hide
*/
@SystemApi
public static final long FUNCTION_ACCESSORY = GadgetFunction.ACCESSORY;
/**
* Code for the audio source usb function.
- * {@hide}
+ * @hide
*/
@SystemApi
public static final long FUNCTION_AUDIO_SOURCE = GadgetFunction.AUDIO_SOURCE;
/**
* Code for the adb usb function.
- * {@hide}
+ * @hide
*/
@SystemApi
public static final long FUNCTION_ADB = GadgetFunction.ADB;
/**
* Code for the ncm source usb function.
- * {@hide}
+ * @hide
*/
@SystemApi
public static final long FUNCTION_NCM = 1 << 10;
@@ -656,6 +673,11 @@
private static final Map<String, Long> FUNCTION_NAME_TO_CODE = new HashMap<>();
+ /**
+ * Counter for tracking UsbOperation operations.
+ */
+ private static final AtomicInteger sUsbOperationCount = new AtomicInteger();
+
static {
FUNCTION_NAME_TO_CODE.put(UsbManager.USB_FUNCTION_MTP, FUNCTION_MTP);
FUNCTION_NAME_TO_CODE.put(UsbManager.USB_FUNCTION_PTP, FUNCTION_PTP);
@@ -687,6 +709,7 @@
GADGET_HAL_V1_0,
GADGET_HAL_V1_1,
GADGET_HAL_V1_2,
+ GADGET_HAL_V2_0,
})
public @interface UsbGadgetHalVersion {}
@@ -705,7 +728,7 @@
private final IUsbManager mService;
/**
- * {@hide}
+ * @hide
*/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
public UsbManager(Context context, IUsbManager service) {
@@ -816,7 +839,7 @@
* {@link #FUNCTION_PTP} are supported.
* @return A ParcelFileDescriptor holding the valid fd, or null if the fd was not found.
*
- * {@hide}
+ * @hide
*/
public ParcelFileDescriptor getControlFd(long function) {
try {
@@ -977,7 +1000,7 @@
* Only system components can call this function.
* @param device to request permissions for
*
- * {@hide}
+ * @hide
*/
public void grantPermission(UsbDevice device) {
grantPermission(device, Process.myUid());
@@ -989,7 +1012,7 @@
* @param device to request permissions for
* @uid uid to give permission
*
- * {@hide}
+ * @hide
*/
public void grantPermission(UsbDevice device, int uid) {
try {
@@ -1005,7 +1028,7 @@
* @param device to request permissions for
* @param packageName of package to grant permissions
*
- * {@hide}
+ * @hide
*/
@SystemApi
@RequiresPermission(Manifest.permission.MANAGE_USB)
@@ -1030,7 +1053,7 @@
* @param function name of the USB function
* @return true if the USB function is enabled
*
- * {@hide}
+ * @hide
*/
@Deprecated
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
@@ -1062,14 +1085,17 @@
* @param functions the USB function(s) to set, as a bitwise mask.
* Must satisfy {@link UsbManager#areSettableFunctions}
*
- * {@hide}
+ * @hide
*/
@SystemApi
@RequiresPermission(Manifest.permission.MANAGE_USB)
public void setCurrentFunctions(@UsbFunctionMode long functions) {
+ int operationId = sUsbOperationCount.incrementAndGet() + Binder.getCallingUid();
try {
- mService.setCurrentFunctions(functions);
+ mService.setCurrentFunctions(functions, operationId);
} catch (RemoteException e) {
+ Log.e(TAG, "setCurrentFunctions: failed to call setCurrentFunctions. functions:"
+ + functions + ", opId:" + operationId, e);
throw e.rethrowFromSystemServer();
}
}
@@ -1081,14 +1107,17 @@
* @param functions the USB function(s) to set.
* @param usbDataUnlocked unused
- * {@hide}
+ * @hide
*/
@Deprecated
@UnsupportedAppUsage
public void setCurrentFunction(String functions, boolean usbDataUnlocked) {
+ int operationId = sUsbOperationCount.incrementAndGet() + Binder.getCallingUid();
try {
- mService.setCurrentFunction(functions, usbDataUnlocked);
+ mService.setCurrentFunction(functions, usbDataUnlocked, operationId);
} catch (RemoteException e) {
+ Log.e(TAG, "setCurrentFunction: failed to call setCurrentFunction. functions:"
+ + functions + ", opId:" + operationId, e);
throw e.rethrowFromSystemServer();
}
}
@@ -1103,7 +1132,7 @@
* @return The currently enabled functions, in a bitwise mask.
* A zero mask indicates that the current function is the charging function.
*
- * {@hide}
+ * @hide
*/
@SystemApi
@RequiresPermission(Manifest.permission.MANAGE_USB)
@@ -1129,7 +1158,7 @@
* @param functions functions to set, in a bitwise mask.
* Must satisfy {@link UsbManager#areSettableFunctions}
*
- * {@hide}
+ * @hide
*/
public void setScreenUnlockedFunctions(long functions) {
try {
@@ -1145,7 +1174,7 @@
* @return The currently set screen enabled functions.
* A zero mask indicates that the screen unlocked functions feature is not enabled.
*
- * {@hide}
+ * @hide
*/
public long getScreenUnlockedFunctions() {
try {
@@ -1167,19 +1196,17 @@
*
* @return The value of currently USB Bandwidth.
*
- * {@hide}
+ * @hide
*/
@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
@RequiresPermission(Manifest.permission.MANAGE_USB)
public int getUsbBandwidthMbps() {
int usbSpeed;
-
try {
usbSpeed = mService.getCurrentUsbSpeed();
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
-
return usbSpeedToBandwidth(usbSpeed);
}
@@ -1191,7 +1218,7 @@
*
* @return a integer {@code GADGET_HAL_*} represent hal version.
*
- * {@hide}
+ * @hide
*/
@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
@RequiresPermission(Manifest.permission.MANAGE_USB)
@@ -1211,7 +1238,7 @@
*
* @return a integer {@code USB_HAL_*} represent hal version.
*
- * {@hide}
+ * @hide
*/
@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
@RequiresPermission(Manifest.permission.MANAGE_USB)
@@ -1507,7 +1534,7 @@
* @param usbDeviceConnectionHandler The component to handle usb connections,
* {@code null} to unset.
*
- * {@hide}
+ * @hide
*/
public void setUsbDeviceConnectionHandler(@Nullable ComponentName usbDeviceConnectionHandler) {
try {
@@ -1526,7 +1553,7 @@
*
* @return Whether the mask is settable.
*
- * {@hide}
+ * @hide
*/
public static boolean areSettableFunctions(long functions) {
return functions == FUNCTION_NONE
@@ -1540,7 +1567,7 @@
*
* @return String representation of given mask
*
- * {@hide}
+ * @hide
*/
public static String usbFunctionsToString(long functions) {
StringJoiner joiner = new StringJoiner(",");
@@ -1576,7 +1603,7 @@
*
* @return A mask of all valid functions in the string
*
- * {@hide}
+ * @hide
*/
public static long usbFunctionsFromString(String functions) {
if (functions == null || functions.equals(USB_FUNCTION_NONE)) {
@@ -1598,7 +1625,7 @@
*
* @return a value of USB bandwidth
*
- * {@hide}
+ * @hide
*/
public static int usbSpeedToBandwidth(int speed) {
switch (speed) {
@@ -1632,12 +1659,14 @@
*
* @return String representation of Usb Gadget Hal Version
*
- * {@hide}
+ * @hide
*/
public static @NonNull String usbGadgetHalVersionToString(int version) {
String halVersion;
- if (version == GADGET_HAL_V1_2) {
+ if (version == GADGET_HAL_V2_0) {
+ halVersion = GADGET_HAL_VERSION_2_0;
+ } else if (version == GADGET_HAL_V1_2) {
halVersion = GADGET_HAL_VERSION_1_2;
} else if (version == GADGET_HAL_V1_1) {
halVersion = GADGET_HAL_VERSION_1_1;
diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl
index eae7ce0..d31540a 100644
--- a/core/java/android/os/IUserManager.aidl
+++ b/core/java/android/os/IUserManager.aidl
@@ -59,6 +59,7 @@
ParcelFileDescriptor getUserIcon(int userId);
UserInfo getPrimaryUser();
int getMainUserId();
+ int getPreviousFullUserToEnterForeground();
List<UserInfo> getUsers(boolean excludePartial, boolean excludeDying, boolean excludePreCreated);
List<UserInfo> getProfiles(int userId, boolean enabledOnly);
int[] getProfileIds(int userId, boolean enabledOnly);
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 954d1fc..f76e8cd 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -4300,6 +4300,43 @@
}
/**
+ * Returns the user who was last in the foreground, not including the current user and not
+ * including profiles.
+ *
+ * <p>Returns {@code null} if there is no previous user, for example if there
+ * is only one full user (i.e. only one user which is not a profile) on the device.
+ *
+ * <p>This method may be used for example to find the user to switch back to if the
+ * current user is removed, or if creating a new user is aborted.
+ *
+ * <p>Note that reboots do not interrupt this calculation; the previous user need not have
+ * used the device since it rebooted.
+ *
+ * <p>Note also that on devices that support multiple users on multiple displays, it is possible
+ * that the returned user will be visible on a secondary display, as the foreground user is the
+ * one associated with the main display.
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.MANAGE_USERS,
+ android.Manifest.permission.CREATE_USERS,
+ android.Manifest.permission.QUERY_USERS
+ })
+ public @Nullable UserHandle getPreviousForegroundUser() {
+ try {
+ final int previousUser = mService.getPreviousFullUserToEnterForeground();
+ if (previousUser == UserHandle.USER_NULL) {
+ return null;
+ }
+ return UserHandle.of(previousUser);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Checks whether it's possible to add more users.
*
* @return true if more users can be added, false if limit has been reached.
diff --git a/core/java/android/telephony/PhoneStateListener.java b/core/java/android/telephony/PhoneStateListener.java
index e5c9adb..dded76c 100644
--- a/core/java/android/telephony/PhoneStateListener.java
+++ b/core/java/android/telephony/PhoneStateListener.java
@@ -26,7 +26,6 @@
import android.os.Handler;
import android.os.HandlerExecutor;
import android.os.Looper;
-import android.telephony.Annotation.CallState;
import android.telephony.Annotation.DisconnectCauses;
import android.telephony.Annotation.PreciseDisconnectCauses;
import android.telephony.Annotation.RadioPowerState;
@@ -726,7 +725,7 @@
*/
@Deprecated
@RequiresPermission(value = android.Manifest.permission.READ_PHONE_STATE, conditional = true)
- public void onCallStateChanged(@CallState int state, String phoneNumber) {
+ public void onCallStateChanged(@Annotation.CallState int state, String phoneNumber) {
// default implementation empty
}
@@ -1569,12 +1568,48 @@
() -> mExecutor.execute(() -> psl.onRadioPowerStateChanged(state)));
}
- public void onCallAttributesChanged(CallAttributes callAttributes) {
+ public void onCallStatesChanged(List<CallState> callStateList) {
PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
if (psl == null) return;
+ if (callStateList == null) return;
+ CallAttributes ca;
+ if (callStateList.isEmpty()) {
+ ca = new CallAttributes(
+ new PreciseCallState(PreciseCallState.PRECISE_CALL_STATE_IDLE,
+ PreciseCallState.PRECISE_CALL_STATE_IDLE,
+ PreciseCallState.PRECISE_CALL_STATE_IDLE,
+ DisconnectCause.NOT_VALID, PreciseDisconnectCause.NOT_VALID),
+ TelephonyManager.NETWORK_TYPE_UNKNOWN, new CallQuality());
+ } else {
+ int foregroundCallState = PreciseCallState.PRECISE_CALL_STATE_IDLE;
+ int backgroundCallState = PreciseCallState.PRECISE_CALL_STATE_IDLE;
+ int ringingCallState = PreciseCallState.PRECISE_CALL_STATE_IDLE;
+ for (CallState cs : callStateList) {
+ switch (cs.getCallClassification()) {
+ case CallState.CALL_CLASSIFICATION_FOREGROUND:
+ foregroundCallState = cs.getCallState();
+ break;
+ case CallState.CALL_CLASSIFICATION_BACKGROUND:
+ backgroundCallState = cs.getCallState();
+ break;
+ case CallState.CALL_CLASSIFICATION_RINGING:
+ ringingCallState = cs.getCallState();
+ break;
+ default:
+ break;
+ }
+ }
+ ca = new CallAttributes(
+ new PreciseCallState(
+ ringingCallState, foregroundCallState, backgroundCallState,
+ DisconnectCause.NOT_VALID, PreciseDisconnectCause.NOT_VALID),
+ callStateList.get(0).getNetworkType(),
+ callStateList.get(0).getCallQuality());
+ }
Binder.withCleanCallingIdentity(
- () -> mExecutor.execute(() -> psl.onCallAttributesChanged(callAttributes)));
+ () -> mExecutor.execute(
+ () -> psl.onCallAttributesChanged(ca)));
}
public void onActiveDataSubIdChanged(int subId) {
diff --git a/core/java/android/telephony/TelephonyCallback.java b/core/java/android/telephony/TelephonyCallback.java
index e8960b8..257f3b7 100644
--- a/core/java/android/telephony/TelephonyCallback.java
+++ b/core/java/android/telephony/TelephonyCallback.java
@@ -27,6 +27,7 @@
import android.os.Build;
import android.telephony.emergency.EmergencyNumber;
import android.telephony.ims.ImsReasonInfo;
+import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telephony.IPhoneStateListener;
@@ -62,7 +63,7 @@
* appropriate sub-interfaces.
*/
public class TelephonyCallback {
-
+ private static final String LOG_TAG = "TelephonyCallback";
/**
* Experiment flag to set the per-pid registration limit for TelephonyCallback
*
@@ -1332,7 +1333,9 @@
@SystemApi
public interface CallAttributesListener {
/**
- * Callback invoked when the call attributes changes on the registered subscription.
+ * Callback invoked when the call attributes changes on the active call on the registered
+ * subscription. If the user swaps between a foreground and background call the call
+ * attributes will be reported for the active call only.
* Note, the registration subscription ID comes from {@link TelephonyManager} object
* which registers TelephonyCallback by
* {@link TelephonyManager#registerTelephonyCallback(Executor, TelephonyCallback)}.
@@ -1346,9 +1349,77 @@
* {@link android.Manifest.permission#READ_PRECISE_PHONE_STATE}.
*
* @param callAttributes the call attributes
+ * @deprecated Use onCallStatesChanged({@link List<CallState>}) to get each of call
+ * state for all ongoing calls on the subscription.
*/
@RequiresPermission(Manifest.permission.READ_PRECISE_PHONE_STATE)
- void onCallAttributesChanged(@NonNull CallAttributes callAttributes);
+ @Deprecated
+ default void onCallAttributesChanged(@NonNull CallAttributes callAttributes) {
+ Log.w(LOG_TAG, "onCallAttributesChanged(List<CallState>) should be "
+ + "overridden.");
+ }
+
+ /**
+ * Callback invoked when the call attributes changes on the ongoing calls on the registered
+ * subscription. If there are 1 foreground and 1 background call, Two {@link CallState}
+ * will be passed.
+ * Note, the registration subscription ID comes from {@link TelephonyManager} object
+ * which registers TelephonyCallback by
+ * {@link TelephonyManager#registerTelephonyCallback(Executor, TelephonyCallback)}.
+ * If this TelephonyManager object was created with
+ * {@link TelephonyManager#createForSubscriptionId(int)}, then the callback applies to the
+ * subscription ID. Otherwise, this callback applies to
+ * {@link SubscriptionManager#getDefaultSubscriptionId()}.
+ * In the event that there are no active(state is not
+ * {@link PreciseCallState#PRECISE_CALL_STATE_IDLE}) calls, this API will report empty list.
+ *
+ * The calling app should have carrier privileges
+ * (see {@link TelephonyManager#hasCarrierPrivileges}) if it does not have the
+ * {@link android.Manifest.permission#READ_PRECISE_PHONE_STATE}.
+ *
+ * @param callStateList the list of call states for each ongoing call. If there are
+ * a active call and a holding call, 1 call attributes for
+ * {@link PreciseCallState#PRECISE_CALL_STATE_ACTIVE} and another
+ * for {@link PreciseCallState#PRECISE_CALL_STATE_HOLDING}
+ * will be in this list.
+ */
+ // Added as default for backward compatibility
+ @RequiresPermission(Manifest.permission.READ_PRECISE_PHONE_STATE)
+ default void onCallStatesChanged(@NonNull List<CallState> callStateList) {
+ if (callStateList.size() > 0) {
+ int foregroundCallState = PreciseCallState.PRECISE_CALL_STATE_IDLE;
+ int backgroundCallState = PreciseCallState.PRECISE_CALL_STATE_IDLE;
+ int ringingCallState = PreciseCallState.PRECISE_CALL_STATE_IDLE;
+ for (CallState cs : callStateList) {
+ switch (cs.getCallClassification()) {
+ case CallState.CALL_CLASSIFICATION_FOREGROUND:
+ foregroundCallState = cs.getCallState();
+ break;
+ case CallState.CALL_CLASSIFICATION_BACKGROUND:
+ backgroundCallState = cs.getCallState();
+ break;
+ case CallState.CALL_CLASSIFICATION_RINGING:
+ ringingCallState = cs.getCallState();
+ break;
+ default:
+ break;
+ }
+ }
+ onCallAttributesChanged(new CallAttributes(
+ new PreciseCallState(
+ ringingCallState, foregroundCallState, backgroundCallState,
+ DisconnectCause.NOT_VALID, PreciseDisconnectCause.NOT_VALID),
+ callStateList.get(0).getNetworkType(),
+ callStateList.get(0).getCallQuality()));
+ } else {
+ onCallAttributesChanged(new CallAttributes(
+ new PreciseCallState(PreciseCallState.PRECISE_CALL_STATE_IDLE,
+ PreciseCallState.PRECISE_CALL_STATE_IDLE,
+ PreciseCallState.PRECISE_CALL_STATE_IDLE,
+ DisconnectCause.NOT_VALID, PreciseDisconnectCause.NOT_VALID),
+ TelephonyManager.NETWORK_TYPE_UNKNOWN, new CallQuality()));
+ }
+ }
}
/**
@@ -1702,14 +1773,13 @@
() -> mExecutor.execute(() -> listener.onRadioPowerStateChanged(state)));
}
- public void onCallAttributesChanged(CallAttributes callAttributes) {
+ public void onCallStatesChanged(List<CallState> callStateList) {
CallAttributesListener listener =
(CallAttributesListener) mTelephonyCallbackWeakRef.get();
if (listener == null) return;
Binder.withCleanCallingIdentity(
- () -> mExecutor.execute(() -> listener.onCallAttributesChanged(
- callAttributes)));
+ () -> mExecutor.execute(() -> listener.onCallStatesChanged(callStateList)));
}
public void onActiveDataSubIdChanged(int subId) {
diff --git a/core/java/android/telephony/TelephonyRegistryManager.java b/core/java/android/telephony/TelephonyRegistryManager.java
index a3696e3..0a1538de 100644
--- a/core/java/android/telephony/TelephonyRegistryManager.java
+++ b/core/java/android/telephony/TelephonyRegistryManager.java
@@ -32,13 +32,13 @@
import android.telephony.Annotation.DataActivityType;
import android.telephony.Annotation.DisconnectCauses;
import android.telephony.Annotation.NetworkType;
-import android.telephony.Annotation.PreciseCallStates;
import android.telephony.Annotation.PreciseDisconnectCauses;
import android.telephony.Annotation.RadioPowerState;
import android.telephony.Annotation.SimActivationState;
import android.telephony.Annotation.SrvccState;
import android.telephony.TelephonyManager.CarrierPrivilegesCallback;
import android.telephony.emergency.EmergencyNumber;
+import android.telephony.ims.ImsCallSession;
import android.telephony.ims.ImsReasonInfo;
import android.util.ArraySet;
import android.util.Log;
@@ -741,17 +741,20 @@
* @param slotIndex for which precise call state changed. Can be derived from subId except when
* subId is invalid.
* @param subId for which precise call state changed.
- * @param ringCallPreciseState ringCall state.
- * @param foregroundCallPreciseState foreground call state.
- * @param backgroundCallPreciseState background call state.
+ * @param callStates Array of PreciseCallState of foreground, background & ringing calls.
+ * @param imsCallIds Array of IMS call session ID{@link ImsCallSession#getCallId} for
+ * ringing, foreground & background calls.
+ * @param imsServiceTypes Array of IMS call service type for ringing, foreground &
+ * background calls.
+ * @param imsCallTypes Array of IMS call type for ringing, foreground & background calls.
*/
public void notifyPreciseCallState(int slotIndex, int subId,
- @PreciseCallStates int ringCallPreciseState,
- @PreciseCallStates int foregroundCallPreciseState,
- @PreciseCallStates int backgroundCallPreciseState) {
+ @Annotation.PreciseCallStates int[] callStates, String[] imsCallIds,
+ @Annotation.ImsCallServiceType int[] imsServiceTypes,
+ @Annotation.ImsCallType int[] imsCallTypes) {
try {
- sRegistry.notifyPreciseCallState(slotIndex, subId, ringCallPreciseState,
- foregroundCallPreciseState, backgroundCallPreciseState);
+ sRegistry.notifyPreciseCallState(slotIndex, subId, callStates,
+ imsCallIds, imsServiceTypes, imsCallTypes);
} catch (RemoteException ex) {
// system process is dead
throw ex.rethrowFromSystemServer();
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index 608cbda..4277d01 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -140,6 +140,13 @@
public static final String SETTINGS_ADB_METRICS_WRITER = "settings_adb_metrics_writer";
/**
+ * Flag to show stylus-specific preferences in Connected Devices
+ * @hide
+ */
+ public static final String SETTINGS_SHOW_STYLUS_PREFERENCES =
+ "settings_show_stylus_preferences";
+
+ /**
* Flag to enable/disable biometrics enrollment v2
* @hide
*/
@@ -181,10 +188,12 @@
DEFAULT_FLAGS.put(SETTINGS_NEW_KEYBOARD_TRACKPAD_GESTURE, "false");
DEFAULT_FLAGS.put(SETTINGS_ENABLE_SPA, "false");
DEFAULT_FLAGS.put(SETTINGS_ADB_METRICS_WRITER, "false");
+ DEFAULT_FLAGS.put(SETTINGS_SHOW_STYLUS_PREFERENCES, "false");
DEFAULT_FLAGS.put(SETTINGS_BIOMETRICS2_ENROLLMENT, "false");
}
private static final Set<String> PERSISTENT_FLAGS;
+
static {
PERSISTENT_FLAGS = new HashSet<>();
PERSISTENT_FLAGS.add(SETTINGS_ALLOW_INTENT_REDIRECTION_FOR_CLONE_PROFILE);
diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java
index fbca373..2745858 100644
--- a/core/java/android/view/Display.java
+++ b/core/java/android/view/Display.java
@@ -1009,6 +1009,28 @@
}
/**
+ * Returns the {@link DisplayShape} which is based on display coordinates.
+ *
+ * To get the {@link DisplayShape} based on the window frame, use
+ * {@link WindowInsets#getDisplayShape()} instead.
+ *
+ * @see DisplayShape
+ */
+ @SuppressLint("VisiblySynchronized")
+ @NonNull
+ public DisplayShape getShape() {
+ synchronized (mLock) {
+ updateDisplayInfoLocked();
+ final DisplayShape displayShape = mDisplayInfo.displayShape;
+ final @Surface.Rotation int rotation = getLocalRotation();
+ if (displayShape != null && rotation != mDisplayInfo.rotation) {
+ return displayShape.setRotation(rotation);
+ }
+ return displayShape;
+ }
+ }
+
+ /**
* Gets the pixel format of the display.
* @return One of the constants defined in {@link android.graphics.PixelFormat}.
*
diff --git a/core/java/android/view/DisplayInfo.java b/core/java/android/view/DisplayInfo.java
index 0ba3072..138017c 100644
--- a/core/java/android/view/DisplayInfo.java
+++ b/core/java/android/view/DisplayInfo.java
@@ -323,6 +323,9 @@
@Surface.Rotation
public int installOrientation;
+ @Nullable
+ public DisplayShape displayShape;
+
public static final @android.annotation.NonNull Creator<DisplayInfo> CREATOR = new Creator<DisplayInfo>() {
@Override
public DisplayInfo createFromParcel(Parcel source) {
@@ -395,7 +398,8 @@
&& brightnessMaximum == other.brightnessMaximum
&& brightnessDefault == other.brightnessDefault
&& Objects.equals(roundedCorners, other.roundedCorners)
- && installOrientation == other.installOrientation;
+ && installOrientation == other.installOrientation
+ && Objects.equals(displayShape, other.displayShape);
}
@Override
@@ -448,6 +452,7 @@
brightnessDefault = other.brightnessDefault;
roundedCorners = other.roundedCorners;
installOrientation = other.installOrientation;
+ displayShape = other.displayShape;
}
public void readFromParcel(Parcel source) {
@@ -506,6 +511,7 @@
userDisabledHdrTypes[i] = source.readInt();
}
installOrientation = source.readInt();
+ displayShape = source.readTypedObject(DisplayShape.CREATOR);
}
@Override
@@ -562,6 +568,7 @@
dest.writeInt(userDisabledHdrTypes[i]);
}
dest.writeInt(installOrientation);
+ dest.writeTypedObject(displayShape, flags);
}
@Override
diff --git a/core/java/android/view/DisplayShape.aidl b/core/java/android/view/DisplayShape.aidl
new file mode 100644
index 0000000..af8b417
--- /dev/null
+++ b/core/java/android/view/DisplayShape.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.view;
+
+parcelable DisplayShape;
diff --git a/core/java/android/view/DisplayShape.java b/core/java/android/view/DisplayShape.java
new file mode 100644
index 0000000..43bd773
--- /dev/null
+++ b/core/java/android/view/DisplayShape.java
@@ -0,0 +1,357 @@
+/*
+ * 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.view;
+
+import static android.view.Surface.ROTATION_0;
+
+import android.annotation.Nullable;
+import android.annotation.TestApi;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.Matrix;
+import android.graphics.Path;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.DisplayUtils;
+import android.util.PathParser;
+import android.util.RotationUtils;
+
+import androidx.annotation.NonNull;
+
+import com.android.internal.R;
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.Objects;
+
+/**
+ * A class representing the shape of a display. It provides a {@link Path} of the display shape of
+ * the display shape.
+ *
+ * {@link DisplayShape} is immutable.
+ */
+public final class DisplayShape implements Parcelable {
+
+ /** @hide */
+ public static final DisplayShape NONE = new DisplayShape("" /* displayShapeSpec */,
+ 0 /* displayWidth */, 0 /* displayHeight */, 0 /* physicalPixelDisplaySizeRatio */,
+ 0 /* rotation */);
+
+ /** @hide */
+ @VisibleForTesting
+ public final String mDisplayShapeSpec;
+ private final float mPhysicalPixelDisplaySizeRatio;
+ private final int mDisplayWidth;
+ private final int mDisplayHeight;
+ private final int mRotation;
+ private final int mOffsetX;
+ private final int mOffsetY;
+ private final float mScale;
+
+ private DisplayShape(@NonNull String displayShapeSpec, int displayWidth, int displayHeight,
+ float physicalPixelDisplaySizeRatio, int rotation) {
+ this(displayShapeSpec, displayWidth, displayHeight, physicalPixelDisplaySizeRatio,
+ rotation, 0, 0, 1f);
+ }
+
+ private DisplayShape(@NonNull String displayShapeSpec, int displayWidth, int displayHeight,
+ float physicalPixelDisplaySizeRatio, int rotation, int offsetX, int offsetY,
+ float scale) {
+ mDisplayShapeSpec = displayShapeSpec;
+ mDisplayWidth = displayWidth;
+ mDisplayHeight = displayHeight;
+ mPhysicalPixelDisplaySizeRatio = physicalPixelDisplaySizeRatio;
+ mRotation = rotation;
+ mOffsetX = offsetX;
+ mOffsetY = offsetY;
+ mScale = scale;
+ }
+
+ /**
+ * @hide
+ */
+ @NonNull
+ public static DisplayShape fromResources(
+ @NonNull Resources res, @NonNull String displayUniqueId, int physicalDisplayWidth,
+ int physicalDisplayHeight, int displayWidth, int displayHeight) {
+ final boolean isScreenRound = RoundedCorners.getBuiltInDisplayIsRound(res, displayUniqueId);
+ final String spec = getSpecString(res, displayUniqueId);
+ if (spec == null || spec.isEmpty()) {
+ return createDefaultDisplayShape(displayWidth, displayHeight, isScreenRound);
+ }
+ final float physicalPixelDisplaySizeRatio = DisplayUtils.getPhysicalPixelDisplaySizeRatio(
+ physicalDisplayWidth, physicalDisplayHeight, displayWidth, displayHeight);
+ return fromSpecString(spec, physicalPixelDisplaySizeRatio, displayWidth, displayHeight);
+ }
+
+ /**
+ * @hide
+ */
+ @NonNull
+ public static DisplayShape createDefaultDisplayShape(
+ int displayWidth, int displayHeight, boolean isScreenRound) {
+ return fromSpecString(createDefaultSpecString(displayWidth, displayHeight, isScreenRound),
+ 1f, displayWidth, displayHeight);
+ }
+
+ /**
+ * @hide
+ */
+ @TestApi
+ @NonNull
+ public static DisplayShape fromSpecString(@NonNull String spec,
+ float physicalPixelDisplaySizeRatio, int displayWidth, int displayHeight) {
+ return Cache.getDisplayShape(spec, physicalPixelDisplaySizeRatio, displayWidth,
+ displayHeight);
+ }
+
+ private static String createDefaultSpecString(int displayWidth, int displayHeight,
+ boolean isCircular) {
+ final String spec;
+ if (isCircular) {
+ final float xRadius = displayWidth / 2f;
+ final float yRadius = displayHeight / 2f;
+ // Draw a circular display shape.
+ spec = "M0," + yRadius
+ // Draw upper half circle with arcTo command.
+ + " A" + xRadius + "," + yRadius + " 0 1,1 " + displayWidth + "," + yRadius
+ // Draw lower half circle with arcTo command.
+ + " A" + xRadius + "," + yRadius + " 0 1,1 0," + yRadius + " Z";
+ } else {
+ // Draw a rectangular display shape.
+ spec = "M0,0"
+ // Draw top edge.
+ + " L" + displayWidth + ",0"
+ // Draw right edge.
+ + " L" + displayWidth + "," + displayHeight
+ // Draw bottom edge.
+ + " L0," + displayHeight
+ // Draw left edge by close command which draws a line from current position to
+ // the initial points (0,0).
+ + " Z";
+ }
+ return spec;
+ }
+
+ /**
+ * Gets the display shape svg spec string of a display which is determined by the given display
+ * unique id.
+ *
+ * Loads the default config {@link R.string#config_mainDisplayShape} if
+ * {@link R.array#config_displayUniqueIdArray} is not set.
+ *
+ * @hide
+ */
+ public static String getSpecString(Resources res, String displayUniqueId) {
+ final int index = DisplayUtils.getDisplayUniqueIdConfigIndex(res, displayUniqueId);
+ final TypedArray array = res.obtainTypedArray(R.array.config_displayShapeArray);
+ final String spec;
+ if (index >= 0 && index < array.length()) {
+ spec = array.getString(index);
+ } else {
+ spec = res.getString(R.string.config_mainDisplayShape);
+ }
+ array.recycle();
+ return spec;
+ }
+
+ /**
+ * @hide
+ */
+ public DisplayShape setRotation(int rotation) {
+ return new DisplayShape(mDisplayShapeSpec, mDisplayWidth, mDisplayHeight,
+ mPhysicalPixelDisplaySizeRatio, rotation, mOffsetX, mOffsetY, mScale);
+ }
+
+ /**
+ * @hide
+ */
+ public DisplayShape setOffset(int offsetX, int offsetY) {
+ return new DisplayShape(mDisplayShapeSpec, mDisplayWidth, mDisplayHeight,
+ mPhysicalPixelDisplaySizeRatio, mRotation, offsetX, offsetY, mScale);
+ }
+
+ /**
+ * @hide
+ */
+ public DisplayShape setScale(float scale) {
+ return new DisplayShape(mDisplayShapeSpec, mDisplayWidth, mDisplayHeight,
+ mPhysicalPixelDisplaySizeRatio, mRotation, mOffsetX, mOffsetY, scale);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mDisplayShapeSpec, mDisplayWidth, mDisplayHeight,
+ mPhysicalPixelDisplaySizeRatio, mRotation, mOffsetX, mOffsetY, mScale);
+ }
+
+ @Override
+ public boolean equals(@Nullable Object o) {
+ if (o == this) {
+ return true;
+ }
+ if (o instanceof DisplayShape) {
+ DisplayShape ds = (DisplayShape) o;
+ return Objects.equals(mDisplayShapeSpec, ds.mDisplayShapeSpec)
+ && mDisplayWidth == ds.mDisplayWidth && mDisplayHeight == ds.mDisplayHeight
+ && mPhysicalPixelDisplaySizeRatio == ds.mPhysicalPixelDisplaySizeRatio
+ && mRotation == ds.mRotation && mOffsetX == ds.mOffsetX
+ && mOffsetY == ds.mOffsetY && mScale == ds.mScale;
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return "DisplayShape{"
+ + " spec=" + mDisplayShapeSpec
+ + " displayWidth=" + mDisplayWidth
+ + " displayHeight=" + mDisplayHeight
+ + " physicalPixelDisplaySizeRatio=" + mPhysicalPixelDisplaySizeRatio
+ + " rotation=" + mRotation
+ + " offsetX=" + mOffsetX
+ + " offsetY=" + mOffsetY
+ + " scale=" + mScale + "}";
+ }
+
+ /**
+ * Returns a {@link Path} of the display shape.
+ *
+ * @return a {@link Path} of the display shape.
+ */
+ @NonNull
+ public Path getPath() {
+ return Cache.getPath(this);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeString8(mDisplayShapeSpec);
+ dest.writeInt(mDisplayWidth);
+ dest.writeInt(mDisplayHeight);
+ dest.writeFloat(mPhysicalPixelDisplaySizeRatio);
+ dest.writeInt(mRotation);
+ dest.writeInt(mOffsetX);
+ dest.writeInt(mOffsetY);
+ dest.writeFloat(mScale);
+ }
+
+ public static final @NonNull Creator<DisplayShape> CREATOR = new Creator<DisplayShape>() {
+ @Override
+ public DisplayShape createFromParcel(Parcel in) {
+ final String spec = in.readString8();
+ final int displayWidth = in.readInt();
+ final int displayHeight = in.readInt();
+ final float ratio = in.readFloat();
+ final int rotation = in.readInt();
+ final int offsetX = in.readInt();
+ final int offsetY = in.readInt();
+ final float scale = in.readFloat();
+ return new DisplayShape(spec, displayWidth, displayHeight, ratio, rotation, offsetX,
+ offsetY, scale);
+ }
+
+ @Override
+ public DisplayShape[] newArray(int size) {
+ return new DisplayShape[size];
+ }
+ };
+
+ private static final class Cache {
+ private static final Object CACHE_LOCK = new Object();
+
+ @GuardedBy("CACHE_LOCK")
+ private static String sCachedSpec;
+ @GuardedBy("CACHE_LOCK")
+ private static int sCachedDisplayWidth;
+ @GuardedBy("CACHE_LOCK")
+ private static int sCachedDisplayHeight;
+ @GuardedBy("CACHE_LOCK")
+ private static float sCachedPhysicalPixelDisplaySizeRatio;
+ @GuardedBy("CACHE_LOCK")
+ private static DisplayShape sCachedDisplayShape;
+
+ @GuardedBy("CACHE_LOCK")
+ private static DisplayShape sCacheForPath;
+ @GuardedBy("CACHE_LOCK")
+ private static Path sCachedPath;
+
+ static DisplayShape getDisplayShape(String spec, float physicalPixelDisplaySizeRatio,
+ int displayWidth, int displayHeight) {
+ synchronized (CACHE_LOCK) {
+ if (spec.equals(sCachedSpec)
+ && sCachedDisplayWidth == displayWidth
+ && sCachedDisplayHeight == displayHeight
+ && sCachedPhysicalPixelDisplaySizeRatio == physicalPixelDisplaySizeRatio) {
+ return sCachedDisplayShape;
+ }
+ }
+
+ final DisplayShape shape = new DisplayShape(spec, displayWidth, displayHeight,
+ physicalPixelDisplaySizeRatio, ROTATION_0);
+
+ synchronized (CACHE_LOCK) {
+ sCachedSpec = spec;
+ sCachedDisplayWidth = displayWidth;
+ sCachedDisplayHeight = displayHeight;
+ sCachedPhysicalPixelDisplaySizeRatio = physicalPixelDisplaySizeRatio;
+ sCachedDisplayShape = shape;
+ }
+ return shape;
+ }
+
+ static Path getPath(@NonNull DisplayShape shape) {
+ synchronized (CACHE_LOCK) {
+ if (shape.equals(sCacheForPath)) {
+ return sCachedPath;
+ }
+ }
+
+ final Path path = PathParser.createPathFromPathData(shape.mDisplayShapeSpec);
+
+ if (!path.isEmpty()) {
+ final Matrix matrix = new Matrix();
+ if (shape.mRotation != ROTATION_0) {
+ RotationUtils.transformPhysicalToLogicalCoordinates(
+ shape.mRotation, shape.mDisplayWidth, shape.mDisplayHeight, matrix);
+ }
+ if (shape.mPhysicalPixelDisplaySizeRatio != 1f) {
+ matrix.preScale(shape.mPhysicalPixelDisplaySizeRatio,
+ shape.mPhysicalPixelDisplaySizeRatio);
+ }
+ if (shape.mOffsetX != 0 || shape.mOffsetY != 0) {
+ matrix.postTranslate(shape.mOffsetX, shape.mOffsetY);
+ }
+ if (shape.mScale != 1f) {
+ matrix.postScale(shape.mScale, shape.mScale);
+ }
+ path.transform(matrix);
+ }
+
+ synchronized (CACHE_LOCK) {
+ sCacheForPath = shape;
+ sCachedPath = path;
+ }
+ return path;
+ }
+ }
+}
diff --git a/core/java/android/view/InsetsState.java b/core/java/android/view/InsetsState.java
index a8cc9b6..c56d618 100644
--- a/core/java/android/view/InsetsState.java
+++ b/core/java/android/view/InsetsState.java
@@ -197,6 +197,9 @@
private PrivacyIndicatorBounds mPrivacyIndicatorBounds =
new PrivacyIndicatorBounds();
+ /** The display shape */
+ private DisplayShape mDisplayShape = DisplayShape.NONE;
+
public InsetsState() {
}
@@ -271,6 +274,7 @@
alwaysConsumeSystemBars, calculateRelativeCutout(frame),
calculateRelativeRoundedCorners(frame),
calculateRelativePrivacyIndicatorBounds(frame),
+ calculateRelativeDisplayShape(frame),
compatInsetsTypes, (legacySystemUiFlags & SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0);
}
@@ -335,6 +339,16 @@
return mPrivacyIndicatorBounds.inset(insetLeft, insetTop, insetRight, insetBottom);
}
+ private DisplayShape calculateRelativeDisplayShape(Rect frame) {
+ if (mDisplayFrame.equals(frame)) {
+ return mDisplayShape;
+ }
+ if (frame == null) {
+ return DisplayShape.NONE;
+ }
+ return mDisplayShape.setOffset(-frame.left, -frame.top);
+ }
+
public Insets calculateInsets(Rect frame, @InsetsType int types, boolean ignoreVisibility) {
Insets insets = Insets.NONE;
for (int type = FIRST_TYPE; type <= LAST_TYPE; type++) {
@@ -589,6 +603,14 @@
return mPrivacyIndicatorBounds;
}
+ public void setDisplayShape(DisplayShape displayShape) {
+ mDisplayShape = displayShape;
+ }
+
+ public DisplayShape getDisplayShape() {
+ return mDisplayShape;
+ }
+
/**
* Modifies the state of this class to exclude a certain type to make it ready for dispatching
* to the client.
@@ -628,6 +650,7 @@
mRoundedCorners = mRoundedCorners.scale(scale);
mRoundedCornerFrame.scale(scale);
mPrivacyIndicatorBounds = mPrivacyIndicatorBounds.scale(scale);
+ mDisplayShape = mDisplayShape.setScale(scale);
for (int i = 0; i < SIZE; i++) {
final InsetsSource source = mSources[i];
if (source != null) {
@@ -650,6 +673,7 @@
mRoundedCorners = other.getRoundedCorners();
mRoundedCornerFrame.set(other.mRoundedCornerFrame);
mPrivacyIndicatorBounds = other.getPrivacyIndicatorBounds();
+ mDisplayShape = other.getDisplayShape();
if (copySources) {
for (int i = 0; i < SIZE; i++) {
InsetsSource source = other.mSources[i];
@@ -675,6 +699,7 @@
mRoundedCorners = other.getRoundedCorners();
mRoundedCornerFrame.set(other.mRoundedCornerFrame);
mPrivacyIndicatorBounds = other.getPrivacyIndicatorBounds();
+ mDisplayShape = other.getDisplayShape();
final ArraySet<Integer> t = toInternalType(types);
for (int i = t.size() - 1; i >= 0; i--) {
final int type = t.valueAt(i);
@@ -807,6 +832,7 @@
pw.println(newPrefix + "mRoundedCorners=" + mRoundedCorners);
pw.println(newPrefix + "mRoundedCornerFrame=" + mRoundedCornerFrame);
pw.println(newPrefix + "mPrivacyIndicatorBounds=" + mPrivacyIndicatorBounds);
+ pw.println(newPrefix + "mDisplayShape=" + mDisplayShape);
for (int i = 0; i < SIZE; i++) {
InsetsSource source = mSources[i];
if (source == null) continue;
@@ -911,7 +937,8 @@
|| !mDisplayCutout.equals(state.mDisplayCutout)
|| !mRoundedCorners.equals(state.mRoundedCorners)
|| !mRoundedCornerFrame.equals(state.mRoundedCornerFrame)
- || !mPrivacyIndicatorBounds.equals(state.mPrivacyIndicatorBounds)) {
+ || !mPrivacyIndicatorBounds.equals(state.mPrivacyIndicatorBounds)
+ || !mDisplayShape.equals(state.mDisplayShape)) {
return false;
}
for (int i = 0; i < SIZE; i++) {
@@ -941,7 +968,7 @@
@Override
public int hashCode() {
return Objects.hash(mDisplayFrame, mDisplayCutout, Arrays.hashCode(mSources),
- mRoundedCorners, mPrivacyIndicatorBounds, mRoundedCornerFrame);
+ mRoundedCorners, mPrivacyIndicatorBounds, mRoundedCornerFrame, mDisplayShape);
}
public InsetsState(Parcel in) {
@@ -961,6 +988,7 @@
dest.writeTypedObject(mRoundedCorners, flags);
mRoundedCornerFrame.writeToParcel(dest, flags);
dest.writeTypedObject(mPrivacyIndicatorBounds, flags);
+ dest.writeTypedObject(mDisplayShape, flags);
}
public static final @NonNull Creator<InsetsState> CREATOR = new Creator<InsetsState>() {
@@ -981,6 +1009,7 @@
mRoundedCorners = in.readTypedObject(RoundedCorners.CREATOR);
mRoundedCornerFrame.readFromParcel(in);
mPrivacyIndicatorBounds = in.readTypedObject(PrivacyIndicatorBounds.CREATOR);
+ mDisplayShape = in.readTypedObject(DisplayShape.CREATOR);
}
@Override
@@ -998,6 +1027,7 @@
+ ", mRoundedCorners=" + mRoundedCorners
+ " mRoundedCornerFrame=" + mRoundedCornerFrame
+ ", mPrivacyIndicatorBounds=" + mPrivacyIndicatorBounds
+ + ", mDisplayShape=" + mDisplayShape
+ ", mSources= { " + joiner
+ " }";
}
diff --git a/core/java/android/view/WindowInsets.java b/core/java/android/view/WindowInsets.java
index 03b25c2..8de15c1 100644
--- a/core/java/android/view/WindowInsets.java
+++ b/core/java/android/view/WindowInsets.java
@@ -42,7 +42,6 @@
import android.content.Intent;
import android.graphics.Insets;
import android.graphics.Rect;
-import android.util.SparseArray;
import android.view.View.OnApplyWindowInsetsListener;
import android.view.WindowInsets.Type.InsetsType;
import android.view.inputmethod.EditorInfo;
@@ -83,6 +82,7 @@
@Nullable private final DisplayCutout mDisplayCutout;
@Nullable private final RoundedCorners mRoundedCorners;
@Nullable private final PrivacyIndicatorBounds mPrivacyIndicatorBounds;
+ @Nullable private final DisplayShape mDisplayShape;
/**
* In multi-window we force show the navigation bar. Because we don't want that the surface size
@@ -115,24 +115,9 @@
public static final @NonNull WindowInsets CONSUMED;
static {
- CONSUMED = new WindowInsets((Rect) null, null, false, false, null);
- }
-
- /**
- * Construct a new WindowInsets from individual insets.
- *
- * A {@code null} inset indicates that the respective inset is consumed.
- *
- * @hide
- * @deprecated Use {@link WindowInsets(SparseArray, SparseArray, boolean, boolean, DisplayCutout)}
- */
- @Deprecated
- public WindowInsets(Rect systemWindowInsetsRect, Rect stableInsetsRect, boolean isRound,
- boolean alwaysConsumeSystemBars, DisplayCutout displayCutout) {
- this(createCompatTypeMap(systemWindowInsetsRect), createCompatTypeMap(stableInsetsRect),
- createCompatVisibilityMap(createCompatTypeMap(systemWindowInsetsRect)),
- isRound, alwaysConsumeSystemBars, displayCutout, null, null,
- systemBars(), false /* compatIgnoreVisibility */);
+ CONSUMED = new WindowInsets(createCompatTypeMap(null), createCompatTypeMap(null),
+ createCompatVisibilityMap(createCompatTypeMap(null)), false, false, null, null,
+ null, null, systemBars(), false);
}
/**
@@ -154,6 +139,7 @@
boolean alwaysConsumeSystemBars, DisplayCutout displayCutout,
RoundedCorners roundedCorners,
PrivacyIndicatorBounds privacyIndicatorBounds,
+ DisplayShape displayShape,
@InsetsType int compatInsetsTypes, boolean compatIgnoreVisibility) {
mSystemWindowInsetsConsumed = typeInsetsMap == null;
mTypeInsetsMap = mSystemWindowInsetsConsumed
@@ -177,6 +163,7 @@
mRoundedCorners = roundedCorners;
mPrivacyIndicatorBounds = privacyIndicatorBounds;
+ mDisplayShape = displayShape;
}
/**
@@ -191,6 +178,7 @@
src.mAlwaysConsumeSystemBars, displayCutoutCopyConstructorArgument(src),
src.mRoundedCorners,
src.mPrivacyIndicatorBounds,
+ src.mDisplayShape,
src.mCompatInsetsTypes,
src.mCompatIgnoreVisibility);
}
@@ -244,15 +232,18 @@
@UnsupportedAppUsage
public WindowInsets(Rect systemWindowInsets) {
this(createCompatTypeMap(systemWindowInsets), null, new boolean[SIZE], false, false, null,
- null, null, systemBars(), false /* compatIgnoreVisibility */);
+ null, null, null, systemBars(), false /* compatIgnoreVisibility */);
}
/**
* Creates a indexOf(type) -> inset map for which the {@code insets} is just mapped to
* {@link Type#statusBars()} and {@link Type#navigationBars()}, depending on the
* location of the inset.
+ *
+ * @hide
*/
- private static Insets[] createCompatTypeMap(@Nullable Rect insets) {
+ @VisibleForTesting
+ public static Insets[] createCompatTypeMap(@Nullable Rect insets) {
if (insets == null) {
return null;
}
@@ -271,6 +262,10 @@
Insets.of(insets.left, 0, insets.right, insets.bottom);
}
+ /**
+ * @hide
+ */
+ @VisibleForTesting
private static boolean[] createCompatVisibilityMap(@Nullable Insets[] typeInsetsMap) {
boolean[] typeVisibilityMap = new boolean[SIZE];
if (typeInsetsMap == null) {
@@ -533,6 +528,17 @@
}
/**
+ * Returns the display shape in the coordinate space of the window.
+ *
+ * @return the display shape
+ * @see DisplayShape
+ */
+ @Nullable
+ public DisplayShape getDisplayShape() {
+ return mDisplayShape;
+ }
+
+ /**
* Returns a copy of this WindowInsets with the cutout fully consumed.
*
* @return A modified copy of this WindowInsets
@@ -547,7 +553,7 @@
mStableInsetsConsumed ? null : mTypeMaxInsetsMap,
mTypeVisibilityMap,
mIsRound, mAlwaysConsumeSystemBars,
- null /* displayCutout */, mRoundedCorners, mPrivacyIndicatorBounds,
+ null /* displayCutout */, mRoundedCorners, mPrivacyIndicatorBounds, mDisplayShape,
mCompatInsetsTypes, mCompatIgnoreVisibility);
}
@@ -602,7 +608,7 @@
// it.
(mCompatInsetsTypes & displayCutout()) != 0
? null : displayCutoutCopyConstructorArgument(this),
- mRoundedCorners, mPrivacyIndicatorBounds, mCompatInsetsTypes,
+ mRoundedCorners, mPrivacyIndicatorBounds, mDisplayShape, mCompatInsetsTypes,
mCompatIgnoreVisibility);
}
@@ -911,6 +917,8 @@
result.append(mPrivacyIndicatorBounds != null ? "privacyIndicatorBounds="
+ mPrivacyIndicatorBounds : "");
result.append("\n ");
+ result.append(mDisplayShape != null ? "displayShape=" + mDisplayShape : "");
+ result.append("\n ");
result.append("compatInsetsTypes=" + mCompatInsetsTypes);
result.append("\n ");
result.append("compatIgnoreVisibility=" + mCompatIgnoreVisibility);
@@ -1018,6 +1026,7 @@
mPrivacyIndicatorBounds == null
? null
: mPrivacyIndicatorBounds.inset(left, top, right, bottom),
+ mDisplayShape,
mCompatInsetsTypes, mCompatIgnoreVisibility);
}
@@ -1037,7 +1046,8 @@
&& Arrays.equals(mTypeVisibilityMap, that.mTypeVisibilityMap)
&& Objects.equals(mDisplayCutout, that.mDisplayCutout)
&& Objects.equals(mRoundedCorners, that.mRoundedCorners)
- && Objects.equals(mPrivacyIndicatorBounds, that.mPrivacyIndicatorBounds);
+ && Objects.equals(mPrivacyIndicatorBounds, that.mPrivacyIndicatorBounds)
+ && Objects.equals(mDisplayShape, that.mDisplayShape);
}
@Override
@@ -1045,7 +1055,7 @@
return Objects.hash(Arrays.hashCode(mTypeInsetsMap), Arrays.hashCode(mTypeMaxInsetsMap),
Arrays.hashCode(mTypeVisibilityMap), mIsRound, mDisplayCutout, mRoundedCorners,
mAlwaysConsumeSystemBars, mSystemWindowInsetsConsumed, mStableInsetsConsumed,
- mDisplayCutoutConsumed, mPrivacyIndicatorBounds);
+ mDisplayCutoutConsumed, mPrivacyIndicatorBounds, mDisplayShape);
}
@@ -1106,6 +1116,7 @@
private DisplayCutout mDisplayCutout;
private RoundedCorners mRoundedCorners = RoundedCorners.NO_ROUNDED_CORNERS;
+ private DisplayShape mDisplayShape = DisplayShape.NONE;
private boolean mIsRound;
private boolean mAlwaysConsumeSystemBars;
@@ -1137,6 +1148,7 @@
mIsRound = insets.mIsRound;
mAlwaysConsumeSystemBars = insets.mAlwaysConsumeSystemBars;
mPrivacyIndicatorBounds = insets.mPrivacyIndicatorBounds;
+ mDisplayShape = insets.mDisplayShape;
}
/**
@@ -1381,6 +1393,19 @@
return this;
}
+ /**
+ * Sets the display shape.
+ *
+ * @see #getDisplayShape().
+ * @param displayShape the display shape.
+ * @return itself.
+ */
+ @NonNull
+ public Builder setDisplayShape(@NonNull DisplayShape displayShape) {
+ mDisplayShape = displayShape;
+ return this;
+ }
+
/** @hide */
@NonNull
public Builder setRound(boolean round) {
@@ -1405,7 +1430,8 @@
return new WindowInsets(mSystemInsetsConsumed ? null : mTypeInsetsMap,
mStableInsetsConsumed ? null : mTypeMaxInsetsMap, mTypeVisibilityMap,
mIsRound, mAlwaysConsumeSystemBars, mDisplayCutout, mRoundedCorners,
- mPrivacyIndicatorBounds, systemBars(), false /* compatIgnoreVisibility */);
+ mPrivacyIndicatorBounds, mDisplayShape, systemBars(),
+ false /* compatIgnoreVisibility */);
}
}
diff --git a/core/java/com/android/internal/telephony/IPhoneStateListener.aidl b/core/java/com/android/internal/telephony/IPhoneStateListener.aidl
index 4b1753a..9cb2e68 100644
--- a/core/java/com/android/internal/telephony/IPhoneStateListener.aidl
+++ b/core/java/com/android/internal/telephony/IPhoneStateListener.aidl
@@ -17,7 +17,7 @@
package com.android.internal.telephony;
import android.telephony.BarringInfo;
-import android.telephony.CallAttributes;
+import android.telephony.CallState;
import android.telephony.CellIdentity;
import android.telephony.CellInfo;
import android.telephony.DataConnectionRealTimeInfo;
@@ -62,7 +62,7 @@
void onPhoneCapabilityChanged(in PhoneCapability capability);
void onActiveDataSubIdChanged(in int subId);
void onRadioPowerStateChanged(in int state);
- void onCallAttributesChanged(in CallAttributes callAttributes);
+ void onCallStatesChanged(in List<CallState> callStateList);
@SuppressWarnings(value={"untyped-collection"})
void onEmergencyNumberListChanged(in Map emergencyNumberList);
void onOutgoingEmergencyCall(in EmergencyNumber placedEmergencyNumber, int subscriptionId);
diff --git a/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl b/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
index c7fa757..7ba2686 100644
--- a/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
+++ b/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
@@ -66,8 +66,8 @@
void notifyCellLocationForSubscriber(in int subId, in CellIdentity cellLocation);
@UnsupportedAppUsage
void notifyCellInfo(in List<CellInfo> cellInfo);
- void notifyPreciseCallState(int phoneId, int subId, int ringingCallState,
- int foregroundCallState, int backgroundCallState);
+ void notifyPreciseCallState(int phoneId, int subId, in int[] callStates, in String[] imsCallIds,
+ in int[] imsCallServiceTypes, in int[] imsCallTypes);
void notifyDisconnectCause(int phoneId, int subId, int disconnectCause,
int preciseDisconnectCause);
void notifyCellInfoForSubscriber(in int subId, in List<CellInfo> cellInfo);
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index 6460007..eb70344 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -1705,8 +1705,7 @@
-->
<flag name="systemExempted" value="0x400" />
<!-- "Short service" foreground service type. See
- TODO: Change it to a real link
- {@code android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_SHORT_SERVICE}.
+ {@link android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_SHORT_SERVICE}.
for more details.
-->
<flag name="shortService" value="0x800" />
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 9c2643b..2ab5b75 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -5985,4 +5985,35 @@
<string-array translatable="false" name="config_fontManagerServiceCerts">
</string-array>
+ <!-- A string config in svg path format for the main display shape.
+ (@see https://www.w3.org/TR/SVG/paths.html#PathData).
+
+ This config must be set unless:
+ 1. {@link Configuration#isScreenRound} is true which means the display shape is circular
+ and the system will auto-generate a circular shape.
+ 2. The display has no rounded corner and the system will auto-generate a rectangular shape.
+ (@see DisplayShape#createDefaultDisplayShape)
+
+ Note: If the display supports multiple resolutions, please define the path config based on
+ the highest resolution so that it can be scaled correctly in each resolution. -->
+ <string name="config_mainDisplayShape" translatable="false"></string>
+
+ <!-- A string config in svg path format for the secondary display shape.
+ (@see https://www.w3.org/TR/SVG/paths.html#PathData).
+
+ This config must be set unless:
+ 1. {@link Configuration#isScreenRound} is true which means the display shape is circular
+ and the system will auto-generate a circular shape.
+ 2. The display has no rounded corner and the system will auto-generate a rectangular shape.
+ (@see DisplayShape#createDefaultDisplayShape)
+
+ Note: If the display supports multiple resolutions, please define the path config based on
+ the highest resolution so that it can be scaled correctly in each resolution. -->
+ <string name="config_secondaryDisplayShape" translatable="false"></string>
+
+ <!-- The display shape config for each display in a multi-display device. -->
+ <string-array name="config_displayShapeArray" translatable="false">
+ <item>@string/config_mainDisplayShape</item>
+ <item>@string/config_secondaryDisplayShape</item>
+ </string-array>
</resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index cd93932..168806a 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -4915,4 +4915,8 @@
<java-symbol type="dimen" name="status_bar_height_default" />
<java-symbol type="string" name="default_card_name"/>
+
+ <java-symbol type="string" name="config_mainDisplayShape"/>
+ <java-symbol type="string" name="config_secondaryDisplayShape"/>
+ <java-symbol type="array" name="config_displayShapeArray" />
</resources>
diff --git a/core/tests/coretests/src/android/view/DisplayShapeTest.java b/core/tests/coretests/src/android/view/DisplayShapeTest.java
new file mode 100644
index 0000000..77dd8bd
--- /dev/null
+++ b/core/tests/coretests/src/android/view/DisplayShapeTest.java
@@ -0,0 +1,143 @@
+/*
+ * 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.view;
+
+import static android.view.Surface.ROTATION_270;
+import static android.view.Surface.ROTATION_90;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.sameInstance;
+import static org.junit.Assert.assertEquals;
+
+import android.graphics.Path;
+import android.graphics.RectF;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests for {@link DisplayShape}.
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+@Presubmit
+public class DisplayShapeTest {
+ // Rectangle with w=100, height=200
+ private static final String SPEC_RECTANGULAR_SHAPE = "M0,0 L100,0 L100,200 L0,200 Z";
+
+ @Test
+ public void testGetPath() {
+ final DisplayShape displayShape = DisplayShape.fromSpecString(
+ SPEC_RECTANGULAR_SHAPE, 1f, 100, 200);
+ final Path path = displayShape.getPath();
+ final RectF actualRect = new RectF();
+ path.computeBounds(actualRect, false);
+
+ final RectF expectRect = new RectF(0f, 0f, 100f, 200f);
+ assertEquals(actualRect, expectRect);
+ }
+
+ @Test
+ public void testDefaultShape_screenIsRound() {
+ final DisplayShape displayShape = DisplayShape.createDefaultDisplayShape(100, 100, true);
+
+ // A circle with radius = 50.
+ final String expect = "M0,50.0 A50.0,50.0 0 1,1 100,50.0 A50.0,50.0 0 1,1 0,50.0 Z";
+ assertEquals(displayShape.mDisplayShapeSpec, expect);
+ }
+
+ @Test
+ public void testDefaultShape_screenIsNotRound() {
+ final DisplayShape displayShape = DisplayShape.createDefaultDisplayShape(100, 200, false);
+
+ // A rectangle with width/height = 100/200.
+ final String expect = "M0,0 L100,0 L100,200 L0,200 Z";
+ assertEquals(displayShape.mDisplayShapeSpec, expect);
+ }
+
+ @Test
+ public void testFromSpecString_cache() {
+ final DisplayShape cached = DisplayShape.fromSpecString(
+ SPEC_RECTANGULAR_SHAPE, 1f, 100, 200);
+ assertThat(DisplayShape.fromSpecString(SPEC_RECTANGULAR_SHAPE, 1f, 100, 200),
+ sameInstance(cached));
+ }
+
+ @Test
+ public void testGetPath_cache() {
+ final Path cached = DisplayShape.fromSpecString(
+ SPEC_RECTANGULAR_SHAPE, 1f, 100, 200).getPath();
+ assertThat(DisplayShape.fromSpecString(
+ SPEC_RECTANGULAR_SHAPE, 1f, 100, 200).getPath(),
+ sameInstance(cached));
+ }
+
+ @Test
+ public void testRotate_90() {
+ DisplayShape displayShape = DisplayShape.fromSpecString(
+ SPEC_RECTANGULAR_SHAPE, 1f, 100, 200);
+ displayShape = displayShape.setRotation(ROTATION_90);
+ final Path path = displayShape.getPath();
+ final RectF actualRect = new RectF();
+ path.computeBounds(actualRect, false);
+
+ final RectF expectRect = new RectF(0f, 0f, 200f, 100f);
+ assertEquals(actualRect, expectRect);
+ }
+
+ @Test
+ public void testRotate_270() {
+ DisplayShape displayShape = DisplayShape.fromSpecString(
+ SPEC_RECTANGULAR_SHAPE, 1f, 100, 200);
+ displayShape = displayShape.setRotation(ROTATION_270);
+ final Path path = displayShape.getPath();
+ final RectF actualRect = new RectF();
+ path.computeBounds(actualRect, false);
+
+ final RectF expectRect = new RectF(0f, 0f, 200f, 100f);
+ assertEquals(actualRect, expectRect);
+ }
+
+ @Test
+ public void testOffset() {
+ DisplayShape displayShape = DisplayShape.fromSpecString(
+ SPEC_RECTANGULAR_SHAPE, 1f, 100, 200);
+ displayShape = displayShape.setOffset(-10, -20);
+ final Path path = displayShape.getPath();
+ final RectF actualRect = new RectF();
+ path.computeBounds(actualRect, false);
+
+ final RectF expectRect = new RectF(-10f, -20f, 90f, 180f);
+ assertEquals(actualRect, expectRect);
+ }
+
+ @Test
+ public void testPhysicalPixelDisplaySizeRatio() {
+ final DisplayShape displayShape = DisplayShape.fromSpecString(
+ SPEC_RECTANGULAR_SHAPE, 0.5f, 100, 200);
+ final Path path = displayShape.getPath();
+ final RectF actualRect = new RectF();
+ path.computeBounds(actualRect, false);
+
+ final RectF expectRect = new RectF(0f, 0f, 50f, 100f);
+ assertEquals(actualRect, expectRect);
+ }
+}
diff --git a/core/tests/coretests/src/android/view/InsetsStateTest.java b/core/tests/coretests/src/android/view/InsetsStateTest.java
index be9da11..6a96f28 100644
--- a/core/tests/coretests/src/android/view/InsetsStateTest.java
+++ b/core/tests/coretests/src/android/view/InsetsStateTest.java
@@ -517,6 +517,19 @@
windowInsets.getRoundedCorner(POSITION_BOTTOM_LEFT));
}
+ @Test
+ public void testCalculateRelativeDisplayShape() {
+ mState.setDisplayFrame(new Rect(0, 0, 200, 400));
+ mState.setDisplayShape(DisplayShape.createDefaultDisplayShape(200, 400, false));
+ WindowInsets windowInsets = mState.calculateInsets(new Rect(10, 20, 200, 400), null, false,
+ false, SOFT_INPUT_ADJUST_UNSPECIFIED, 0, 0, TYPE_APPLICATION,
+ WINDOWING_MODE_UNDEFINED, new SparseIntArray());
+
+ final DisplayShape expect =
+ DisplayShape.createDefaultDisplayShape(200, 400, false).setOffset(-10, -20);
+ assertEquals(expect, windowInsets.getDisplayShape());
+ }
+
private void assertEqualsAndHashCode() {
assertEquals(mState, mState2);
assertEquals(mState.hashCode(), mState2.hashCode());
diff --git a/core/tests/coretests/src/android/view/WindowInsetsTest.java b/core/tests/coretests/src/android/view/WindowInsetsTest.java
index dd9634b..4fed396 100644
--- a/core/tests/coretests/src/android/view/WindowInsetsTest.java
+++ b/core/tests/coretests/src/android/view/WindowInsetsTest.java
@@ -39,13 +39,16 @@
@Test
public void systemWindowInsets_afterConsuming_isConsumed() {
- assertTrue(new WindowInsets(new Rect(1, 2, 3, 4), null, false, false, null)
+ assertTrue(new WindowInsets(WindowInsets.createCompatTypeMap(new Rect(1, 2, 3, 4)), null,
+ null, false, false, null, null, null, null,
+ WindowInsets.Type.systemBars(), false)
.consumeSystemWindowInsets().isConsumed());
}
@Test
public void multiNullConstructor_isConsumed() {
- assertTrue(new WindowInsets((Rect) null, null, false, false, null).isConsumed());
+ assertTrue(new WindowInsets(null, null, null, false, false, null, null, null, null,
+ WindowInsets.Type.systemBars(), false).isConsumed());
}
@Test
@@ -61,7 +64,8 @@
WindowInsets.assignCompatInsets(maxInsets, new Rect(0, 10, 0, 0));
WindowInsets.assignCompatInsets(insets, new Rect(0, 0, 0, 0));
WindowInsets windowInsets = new WindowInsets(insets, maxInsets, visible, false, false, null,
- null, null, systemBars(), true /* compatIgnoreVisibility */);
+ null, null, DisplayShape.NONE, systemBars(),
+ true /* compatIgnoreVisibility */);
assertEquals(Insets.of(0, 10, 0, 0), windowInsets.getSystemWindowInsets());
}
}
diff --git a/core/tests/coretests/src/com/android/internal/widget/ActionBarOverlayLayoutTest.java b/core/tests/coretests/src/com/android/internal/widget/ActionBarOverlayLayoutTest.java
index d10f173..4d4ec35 100644
--- a/core/tests/coretests/src/com/android/internal/widget/ActionBarOverlayLayoutTest.java
+++ b/core/tests/coretests/src/com/android/internal/widget/ActionBarOverlayLayoutTest.java
@@ -168,7 +168,8 @@
}
private WindowInsets insetsWith(Insets content, DisplayCutout cutout) {
- return new WindowInsets(content.toRect(), null, false, false, cutout);
+ return new WindowInsets(WindowInsets.createCompatTypeMap(content.toRect()), null, null,
+ false, false, cutout, null, null, null, WindowInsets.Type.systemBars(), false);
}
private ViewGroup createViewGroupWithId(int id) {
diff --git a/graphics/java/android/graphics/PathIterator.java b/graphics/java/android/graphics/PathIterator.java
index 33b9a47..bfda690 100644
--- a/graphics/java/android/graphics/PathIterator.java
+++ b/graphics/java/android/graphics/PathIterator.java
@@ -281,7 +281,7 @@
return mConicWeight;
}
- public Segment(@NonNull @Verb int verb, @NonNull float[] points, float conicWeight) {
+ Segment(@NonNull @Verb int verb, @NonNull float[] points, float conicWeight) {
mVerb = verb;
mPoints = points;
mConicWeight = conicWeight;
diff --git a/libs/androidfw/ApkAssets.cpp b/libs/androidfw/ApkAssets.cpp
old mode 100755
new mode 100644
index c0fa63a..15aaae2
--- a/libs/androidfw/ApkAssets.cpp
+++ b/libs/androidfw/ApkAssets.cpp
@@ -18,6 +18,7 @@
#include "android-base/errors.h"
#include "android-base/logging.h"
+#include "android-base/utf8.h"
namespace android {
@@ -84,7 +85,7 @@
}
std::string overlay_path(loaded_idmap->OverlayApkPath());
- auto fd = unique_fd(::open(overlay_path.c_str(), O_RDONLY|O_CLOEXEC));
+ auto fd = unique_fd(base::utf8::open(overlay_path.c_str(), O_RDONLY | O_CLOEXEC));
std::unique_ptr<AssetsProvider> overlay_assets;
if (IsFabricatedOverlay(fd)) {
// Fabricated overlays do not contain resource definitions. All of the overlay resource values
@@ -92,7 +93,7 @@
overlay_assets = EmptyAssetsProvider::Create(std::move(overlay_path));
} else {
// The overlay should be an APK.
- overlay_assets = ZipAssetsProvider::Create(std::move(fd), std::move(overlay_path), flags);
+ overlay_assets = ZipAssetsProvider::Create(std::move(overlay_path), flags, std::move(fd));
}
if (overlay_assets == nullptr) {
return {};
diff --git a/libs/androidfw/AssetManager2.cpp b/libs/androidfw/AssetManager2.cpp
index c3d153d..cc7e871 100644
--- a/libs/androidfw/AssetManager2.cpp
+++ b/libs/androidfw/AssetManager2.cpp
@@ -374,7 +374,7 @@
const std::string name = ToFormattedResourceString(*res_name);
output.append(base::StringPrintf(
"resource='%s' overlayable='%s' actor='%s' policy='0x%08x'\n",
- name.c_str(), info->name.c_str(), info->actor.c_str(), info->policy_flags));
+ name.c_str(), info->name.data(), info->actor.data(), info->policy_flags));
}
}
}
@@ -1356,21 +1356,22 @@
void AssetManager2::RebuildFilterList() {
for (PackageGroup& group : package_groups_) {
- for (ConfiguredPackage& impl : group.packages_) {
- impl.filtered_configs_.clear();
-
+ for (ConfiguredPackage& package : group.packages_) {
+ package.filtered_configs_.forEachItem([](auto, auto& fcg) { fcg.type_entries.clear(); });
// Create the filters here.
- impl.loaded_package_->ForEachTypeSpec([&](const TypeSpec& type_spec, uint8_t type_id) {
+ package.loaded_package_->ForEachTypeSpec([&](const TypeSpec& type_spec, uint8_t type_id) {
FilteredConfigGroup* group = nullptr;
for (const auto& type_entry : type_spec.type_entries) {
if (type_entry.config.match(configuration_)) {
if (!group) {
- group = &impl.filtered_configs_.editItemAt(type_id - 1);
+ group = &package.filtered_configs_.editItemAt(type_id - 1);
}
group->type_entries.push_back(&type_entry);
}
}
});
+ package.filtered_configs_.trimBuckets(
+ [](const auto& fcg) { return fcg.type_entries.empty(); });
}
}
}
@@ -1411,30 +1412,34 @@
std::unique_ptr<Theme> AssetManager2::NewTheme() {
constexpr size_t kInitialReserveSize = 32;
auto theme = std::unique_ptr<Theme>(new Theme(this));
+ theme->keys_.reserve(kInitialReserveSize);
theme->entries_.reserve(kInitialReserveSize);
return theme;
}
+void AssetManager2::ForEachPackage(base::function_ref<bool(const std::string&, uint8_t)> func,
+ package_property_t excluded_property_flags) const {
+ for (const PackageGroup& package_group : package_groups_) {
+ const auto loaded_package = package_group.packages_.front().loaded_package_;
+ if ((loaded_package->GetPropertyFlags() & excluded_property_flags) == 0U
+ && !func(loaded_package->GetPackageName(),
+ package_group.dynamic_ref_table->mAssignedPackageId)) {
+ return;
+ }
+ }
+}
+
Theme::Theme(AssetManager2* asset_manager) : asset_manager_(asset_manager) {
}
Theme::~Theme() = default;
struct Theme::Entry {
- uint32_t attr_res_id;
ApkAssetsCookie cookie;
uint32_t type_spec_flags;
Res_value value;
};
-namespace {
-struct ThemeEntryKeyComparer {
- bool operator() (const Theme::Entry& entry, uint32_t attr_res_id) const noexcept {
- return entry.attr_res_id < attr_res_id;
- }
-};
-} // namespace
-
base::expected<std::monostate, NullOrIOError> Theme::ApplyStyle(uint32_t resid, bool force) {
ATRACE_NAME("Theme::ApplyStyle");
@@ -1463,18 +1468,20 @@
continue;
}
- auto entry_it = std::lower_bound(entries_.begin(), entries_.end(), attr_res_id,
- ThemeEntryKeyComparer{});
- if (entry_it != entries_.end() && entry_it->attr_res_id == attr_res_id) {
+ const auto key_it = std::lower_bound(keys_.begin(), keys_.end(), attr_res_id);
+ const auto entry_it = entries_.begin() + (key_it - keys_.begin());
+ if (key_it != keys_.end() && *key_it == attr_res_id) {
if (is_undefined) {
// DATA_NULL_UNDEFINED clears the value of the attribute in the theme only when `force` is
- /// true.
+ // true.
+ keys_.erase(key_it);
entries_.erase(entry_it);
} else if (force) {
- *entry_it = Entry{attr_res_id, it->cookie, (*bag)->type_spec_flags, it->value};
+ *entry_it = Entry{it->cookie, (*bag)->type_spec_flags, it->value};
}
} else {
- entries_.insert(entry_it, Entry{attr_res_id, it->cookie, (*bag)->type_spec_flags, it->value});
+ keys_.insert(key_it, attr_res_id);
+ entries_.insert(entry_it, Entry{it->cookie, (*bag)->type_spec_flags, it->value});
}
}
return {};
@@ -1485,6 +1492,7 @@
ATRACE_NAME("Theme::Rebase");
// Reset the entries without changing the vector capacity to prevent reallocations during
// ApplyStyle.
+ keys_.clear();
entries_.clear();
asset_manager_ = am;
for (size_t i = 0; i < style_count; i++) {
@@ -1493,16 +1501,14 @@
}
std::optional<AssetManager2::SelectedValue> Theme::GetAttribute(uint32_t resid) const {
-
constexpr const uint32_t kMaxIterations = 20;
uint32_t type_spec_flags = 0u;
for (uint32_t i = 0; i <= kMaxIterations; i++) {
- auto entry_it = std::lower_bound(entries_.begin(), entries_.end(), resid,
- ThemeEntryKeyComparer{});
- if (entry_it == entries_.end() || entry_it->attr_res_id != resid) {
+ const auto key_it = std::lower_bound(keys_.begin(), keys_.end(), resid);
+ if (key_it == keys_.end() || *key_it != resid) {
return std::nullopt;
}
-
+ const auto entry_it = entries_.begin() + (key_it - keys_.begin());
type_spec_flags |= entry_it->type_spec_flags;
if (entry_it->value.dataType == Res_value::TYPE_ATTRIBUTE) {
resid = entry_it->value.data;
@@ -1536,6 +1542,7 @@
}
void Theme::Clear() {
+ keys_.clear();
entries_.clear();
}
@@ -1547,11 +1554,12 @@
type_spec_flags_ = source.type_spec_flags_;
if (asset_manager_ == source.asset_manager_) {
+ keys_ = source.keys_;
entries_ = source.entries_;
} else {
- std::map<ApkAssetsCookie, ApkAssetsCookie> src_to_dest_asset_cookies;
- typedef std::map<int, int> SourceToDestinationRuntimePackageMap;
- std::map<ApkAssetsCookie, SourceToDestinationRuntimePackageMap> src_asset_cookie_id_map;
+ std::unordered_map<ApkAssetsCookie, ApkAssetsCookie> src_to_dest_asset_cookies;
+ using SourceToDestinationRuntimePackageMap = std::unordered_map<int, int>;
+ std::unordered_map<ApkAssetsCookie, SourceToDestinationRuntimePackageMap> src_asset_cookie_id_map;
// Determine which ApkAssets are loaded in both theme AssetManagers.
const auto src_assets = source.asset_manager_->GetApkAssets();
@@ -1579,15 +1587,17 @@
}
src_to_dest_asset_cookies.insert(std::make_pair(i, j));
- src_asset_cookie_id_map.insert(std::make_pair(i, package_map));
+ src_asset_cookie_id_map.insert(std::make_pair(i, std::move(package_map)));
break;
}
}
// Reset the data in the destination theme.
+ keys_.clear();
entries_.clear();
- for (const auto& entry : source.entries_) {
+ for (size_t i = 0, size = source.entries_.size(); i != size; ++i) {
+ const auto& entry = source.entries_[i];
bool is_reference = (entry.value.dataType == Res_value::TYPE_ATTRIBUTE
|| entry.value.dataType == Res_value::TYPE_REFERENCE
|| entry.value.dataType == Res_value::TYPE_DYNAMIC_ATTRIBUTE
@@ -1627,13 +1637,15 @@
}
}
+ const auto source_res_id = source.keys_[i];
+
// The package id of the attribute needs to be rewritten to the package id of the
// attribute in the destination.
- int attribute_dest_package_id = get_package_id(entry.attr_res_id);
+ int attribute_dest_package_id = get_package_id(source_res_id);
if (attribute_dest_package_id != 0x01) {
// Find the cookie of the attribute resource id in the source AssetManager
base::expected<FindEntryResult, NullOrIOError> attribute_entry_result =
- source.asset_manager_->FindEntry(entry.attr_res_id, 0 /* density_override */ ,
+ source.asset_manager_->FindEntry(source_res_id, 0 /* density_override */ ,
true /* stop_at_first_match */,
true /* ignore_configuration */);
if (UNLIKELY(IsIOError(attribute_entry_result))) {
@@ -1657,16 +1669,15 @@
attribute_dest_package_id = attribute_dest_package->second;
}
- auto dest_attr_id = make_resid(attribute_dest_package_id, get_type_id(entry.attr_res_id),
- get_entry_id(entry.attr_res_id));
- Theme::Entry new_entry{dest_attr_id, data_dest_cookie, entry.type_spec_flags,
- Res_value{.dataType = entry.value.dataType,
- .data = attribute_data}};
-
+ auto dest_attr_id = make_resid(attribute_dest_package_id, get_type_id(source_res_id),
+ get_entry_id(source_res_id));
+ const auto key_it = std::lower_bound(keys_.begin(), keys_.end(), dest_attr_id);
+ const auto entry_it = entries_.begin() + (key_it - keys_.begin());
// Since the entries were cleared, the attribute resource id has yet been mapped to any value.
- auto entry_it = std::lower_bound(entries_.begin(), entries_.end(), dest_attr_id,
- ThemeEntryKeyComparer{});
- entries_.insert(entry_it, new_entry);
+ keys_.insert(key_it, dest_attr_id);
+ entries_.insert(entry_it, Entry{data_dest_cookie, entry.type_spec_flags,
+ Res_value{.dataType = entry.value.dataType,
+ .data = attribute_data}});
}
}
return {};
@@ -1674,9 +1685,11 @@
void Theme::Dump() const {
LOG(INFO) << base::StringPrintf("Theme(this=%p, AssetManager2=%p)", this, asset_manager_);
- for (auto& entry : entries_) {
+ for (size_t i = 0, size = keys_.size(); i != size; ++i) {
+ auto res_id = keys_[i];
+ const auto& entry = entries_[i];
LOG(INFO) << base::StringPrintf(" entry(0x%08x)=(0x%08x) type=(0x%02x), cookie(%d)",
- entry.attr_res_id, entry.value.data, entry.value.dataType,
+ res_id, entry.value.data, entry.value.dataType,
entry.cookie);
}
}
diff --git a/libs/androidfw/AssetsProvider.cpp b/libs/androidfw/AssetsProvider.cpp
index 80e5607..b9264c5 100644
--- a/libs/androidfw/AssetsProvider.cpp
+++ b/libs/androidfw/AssetsProvider.cpp
@@ -92,21 +92,27 @@
last_mod_time_(last_mod_time) {}
std::unique_ptr<ZipAssetsProvider> ZipAssetsProvider::Create(std::string path,
- package_property_t flags) {
+ package_property_t flags,
+ base::unique_fd fd) {
+ const auto released_fd = fd.ok() ? fd.release() : -1;
ZipArchiveHandle handle;
- if (int32_t result = OpenArchive(path.c_str(), &handle); result != 0) {
+ if (int32_t result = released_fd < 0 ? OpenArchive(path.c_str(), &handle)
+ : OpenArchiveFd(released_fd, path.c_str(), &handle)) {
LOG(ERROR) << "Failed to open APK '" << path << "': " << ::ErrorCodeString(result);
CloseArchive(handle);
return {};
}
struct stat sb{.st_mtime = -1};
- if (stat(path.c_str(), &sb) < 0) {
- // Stat requires execute permissions on all directories path to the file. If the process does
- // not have execute permissions on this file, allow the zip to be opened but IsUpToDate() will
- // always have to return true.
- LOG(WARNING) << "Failed to stat file '" << path << "': "
- << base::SystemErrorCodeToString(errno);
+ // Skip all up-to-date checks if the file won't ever change.
+ if (!isReadonlyFilesystem(path.c_str())) {
+ if ((released_fd < 0 ? stat(path.c_str(), &sb) : fstat(released_fd, &sb)) < 0) {
+ // Stat requires execute permissions on all directories path to the file. If the process does
+ // not have execute permissions on this file, allow the zip to be opened but IsUpToDate() will
+ // always have to return true.
+ LOG(WARNING) << "Failed to stat file '" << path << "': "
+ << base::SystemErrorCodeToString(errno);
+ }
}
return std::unique_ptr<ZipAssetsProvider>(
@@ -133,12 +139,15 @@
}
struct stat sb{.st_mtime = -1};
- if (fstat(released_fd, &sb) < 0) {
- // Stat requires execute permissions on all directories path to the file. If the process does
- // not have execute permissions on this file, allow the zip to be opened but IsUpToDate() will
- // always have to return true.
- LOG(WARNING) << "Failed to fstat file '" << friendly_name << "': "
- << base::SystemErrorCodeToString(errno);
+ // Skip all up-to-date checks if the file won't ever change.
+ if (!isReadonlyFilesystem(released_fd)) {
+ if (fstat(released_fd, &sb) < 0) {
+ // Stat requires execute permissions on all directories path to the file. If the process does
+ // not have execute permissions on this file, allow the zip to be opened but IsUpToDate() will
+ // always have to return true.
+ LOG(WARNING) << "Failed to fstat file '" << friendly_name
+ << "': " << base::SystemErrorCodeToString(errno);
+ }
}
return std::unique_ptr<ZipAssetsProvider>(
@@ -275,6 +284,9 @@
}
bool ZipAssetsProvider::IsUpToDate() const {
+ if (last_mod_time_ == -1) {
+ return true;
+ }
struct stat sb{};
if (fstat(GetFileDescriptor(zip_handle_.get()), &sb) < 0) {
// If fstat fails on the zip archive, return true so the zip archive the resource system does
@@ -288,7 +300,7 @@
: dir_(std::forward<std::string>(path)), last_mod_time_(last_mod_time) {}
std::unique_ptr<DirectoryAssetsProvider> DirectoryAssetsProvider::Create(std::string path) {
- struct stat sb{};
+ struct stat sb;
const int result = stat(path.c_str(), &sb);
if (result == -1) {
LOG(ERROR) << "Failed to find directory '" << path << "'.";
@@ -304,8 +316,9 @@
path += OS_PATH_SEPARATOR;
}
- return std::unique_ptr<DirectoryAssetsProvider>(new DirectoryAssetsProvider(std::move(path),
- sb.st_mtime));
+ const bool isReadonly = isReadonlyFilesystem(path.c_str());
+ return std::unique_ptr<DirectoryAssetsProvider>(
+ new DirectoryAssetsProvider(std::move(path), isReadonly ? -1 : sb.st_mtime));
}
std::unique_ptr<Asset> DirectoryAssetsProvider::OpenInternal(const std::string& path,
@@ -335,7 +348,10 @@
}
bool DirectoryAssetsProvider::IsUpToDate() const {
- struct stat sb{};
+ if (last_mod_time_ == -1) {
+ return true;
+ }
+ struct stat sb;
if (stat(dir_.c_str(), &sb) < 0) {
// If stat fails on the zip archive, return true so the zip archive the resource system does
// attempt to refresh the ApkAsset.
@@ -431,4 +447,4 @@
return true;
}
-} // namespace android
\ No newline at end of file
+} // namespace android
diff --git a/libs/androidfw/LoadedArsc.cpp b/libs/androidfw/LoadedArsc.cpp
index 386f718..c0fdfe2 100644
--- a/libs/androidfw/LoadedArsc.cpp
+++ b/libs/androidfw/LoadedArsc.cpp
@@ -645,16 +645,16 @@
}
std::string name;
- util::ReadUtf16StringFromDevice(overlayable->name, arraysize(overlayable->name), &name);
+ util::ReadUtf16StringFromDevice(overlayable->name, std::size(overlayable->name), &name);
std::string actor;
- util::ReadUtf16StringFromDevice(overlayable->actor, arraysize(overlayable->actor), &actor);
-
- if (loaded_package->overlayable_map_.find(name) !=
- loaded_package->overlayable_map_.end()) {
- LOG(ERROR) << "Multiple <overlayable> blocks with the same name '" << name << "'.";
+ util::ReadUtf16StringFromDevice(overlayable->actor, std::size(overlayable->actor), &actor);
+ auto [name_to_actor_it, inserted] =
+ loaded_package->overlayable_map_.emplace(std::move(name), std::move(actor));
+ if (!inserted) {
+ LOG(ERROR) << "Multiple <overlayable> blocks with the same name '"
+ << name_to_actor_it->first << "'.";
return {};
}
- loaded_package->overlayable_map_.emplace(name, actor);
// Iterate over the overlayable policy chunks contained within the overlayable chunk data
ChunkIterator overlayable_iter(child_chunk.data_ptr(), child_chunk.data_size());
@@ -669,7 +669,6 @@
LOG(ERROR) << "RES_TABLE_OVERLAYABLE_POLICY_TYPE too small.";
return {};
}
-
if ((overlayable_child_chunk.data_size() / sizeof(ResTable_ref))
< dtohl(policy_header->entry_count)) {
LOG(ERROR) << "RES_TABLE_OVERLAYABLE_POLICY_TYPE too small to hold entries.";
@@ -691,8 +690,8 @@
// Add the pairing of overlayable properties and resource ids to the package
OverlayableInfo overlayable_info {
- .name = name,
- .actor = actor,
+ .name = name_to_actor_it->first,
+ .actor = name_to_actor_it->second,
.policy_flags = policy_header->policy_flags
};
loaded_package->overlayable_infos_.emplace_back(std::move(overlayable_info), std::move(ids));
@@ -736,6 +735,7 @@
const auto entry_end = entry_begin + dtohl(lib_alias->count);
std::unordered_set<uint32_t> finalized_ids;
finalized_ids.reserve(entry_end - entry_begin);
+ loaded_package->alias_id_map_.reserve(entry_end - entry_begin);
for (auto entry_iter = entry_begin; entry_iter != entry_end; ++entry_iter) {
if (!entry_iter) {
LOG(ERROR) << "NULL ResTable_staged_alias_entry record??";
@@ -749,13 +749,20 @@
}
auto staged_id = dtohl(entry_iter->stagedResId);
- auto [_, success] = loaded_package->alias_id_map_.emplace(staged_id, finalized_id);
- if (!success) {
+ loaded_package->alias_id_map_.emplace_back(staged_id, finalized_id);
+ }
+
+ std::sort(loaded_package->alias_id_map_.begin(), loaded_package->alias_id_map_.end(),
+ [](auto&& l, auto&& r) { return l.first < r.first; });
+ const auto duplicate_it =
+ std::adjacent_find(loaded_package->alias_id_map_.begin(),
+ loaded_package->alias_id_map_.end(),
+ [](auto&& l, auto&& r) { return l.first == r.first; });
+ if (duplicate_it != loaded_package->alias_id_map_.end()) {
LOG(ERROR) << StringPrintf("Repeated staged resource id '%08x' in staged aliases.",
- staged_id);
+ duplicate_it->first);
return {};
}
- }
} break;
default:
diff --git a/libs/androidfw/include/androidfw/AssetManager2.h b/libs/androidfw/include/androidfw/AssetManager2.h
index e9aaedc..e4d1218 100644
--- a/libs/androidfw/include/androidfw/AssetManager2.h
+++ b/libs/androidfw/include/androidfw/AssetManager2.h
@@ -17,6 +17,7 @@
#ifndef ANDROIDFW_ASSETMANAGER2_H_
#define ANDROIDFW_ASSETMANAGER2_H_
+#include "android-base/function_ref.h"
#include "android-base/macros.h"
#include <array>
@@ -320,17 +321,8 @@
// Creates a new Theme from this AssetManager.
std::unique_ptr<Theme> NewTheme();
- void ForEachPackage(const std::function<bool(const std::string&, uint8_t)> func,
- package_property_t excluded_property_flags = 0U) const {
- for (const PackageGroup& package_group : package_groups_) {
- const auto loaded_package = package_group.packages_.front().loaded_package_;
- if ((loaded_package->GetPropertyFlags() & excluded_property_flags) == 0U
- && !func(loaded_package->GetPackageName(),
- package_group.dynamic_ref_table->mAssignedPackageId)) {
- return;
- }
- }
- }
+ void ForEachPackage(base::function_ref<bool(const std::string&, uint8_t)> func,
+ package_property_t excluded_property_flags = 0U) const;
void DumpToLog() const;
@@ -571,6 +563,7 @@
AssetManager2* asset_manager_ = nullptr;
uint32_t type_spec_flags_ = 0u;
+ std::vector<uint32_t> keys_;
std::vector<Entry> entries_;
};
diff --git a/libs/androidfw/include/androidfw/AssetsProvider.h b/libs/androidfw/include/androidfw/AssetsProvider.h
index af6e7f4..7891194 100644
--- a/libs/androidfw/include/androidfw/AssetsProvider.h
+++ b/libs/androidfw/include/androidfw/AssetsProvider.h
@@ -80,8 +80,8 @@
// Supplies assets from a zip archive.
struct ZipAssetsProvider : public AssetsProvider {
- static std::unique_ptr<ZipAssetsProvider> Create(std::string path,
- package_property_t flags);
+ static std::unique_ptr<ZipAssetsProvider> Create(std::string path, package_property_t flags,
+ base::unique_fd fd = {});
static std::unique_ptr<ZipAssetsProvider> Create(base::unique_fd fd,
std::string friendly_name,
diff --git a/libs/androidfw/include/androidfw/ByteBucketArray.h b/libs/androidfw/include/androidfw/ByteBucketArray.h
index 05a2c4d..ca0a9ed 100644
--- a/libs/androidfw/include/androidfw/ByteBucketArray.h
+++ b/libs/androidfw/include/androidfw/ByteBucketArray.h
@@ -17,6 +17,7 @@
#ifndef __BYTE_BUCKET_ARRAY_H
#define __BYTE_BUCKET_ARRAY_H
+#include <algorithm>
#include <cstdint>
#include <cstring>
@@ -36,15 +37,11 @@
}
~ByteBucketArray() {
- clear();
+ deleteBuckets();
}
void clear() {
- for (size_t i = 0; i < kNumBuckets; i++) {
- if (buckets_[i] != NULL) {
- delete[] buckets_[i];
- }
- }
+ deleteBuckets();
memset(buckets_, 0, sizeof(buckets_));
}
@@ -59,7 +56,7 @@
uint8_t bucket_index = static_cast<uint8_t>(index) >> 4;
T* bucket = buckets_[bucket_index];
- if (bucket == NULL) {
+ if (bucket == nullptr) {
return default_;
}
return bucket[0x0f & static_cast<uint8_t>(index)];
@@ -70,9 +67,9 @@
<< ") with size=" << size();
uint8_t bucket_index = static_cast<uint8_t>(index) >> 4;
- T* bucket = buckets_[bucket_index];
- if (bucket == NULL) {
- bucket = buckets_[bucket_index] = new T[kBucketSize]();
+ T*& bucket = buckets_[bucket_index];
+ if (bucket == nullptr) {
+ bucket = new T[kBucketSize]();
}
return bucket[0x0f & static_cast<uint8_t>(index)];
}
@@ -86,9 +83,42 @@
return true;
}
+ template <class Func>
+ void forEachItem(Func f) {
+ for (size_t i = 0; i < kNumBuckets; i++) {
+ const auto bucket = buckets_[i];
+ if (bucket != nullptr) {
+ for (size_t j = 0; j < kBucketSize; j++) {
+ f((i << 4) | j, bucket[j]);
+ }
+ }
+ }
+ }
+
+ template <class Func>
+ void trimBuckets(Func isEmptyFunc) {
+ for (size_t i = 0; i < kNumBuckets; i++) {
+ const auto bucket = buckets_[i];
+ if (bucket != nullptr) {
+ if (std::all_of(bucket, bucket + kBucketSize, isEmptyFunc)) {
+ delete[] bucket;
+ buckets_[i] = nullptr;
+ }
+ }
+ }
+ }
+
private:
enum { kNumBuckets = 16, kBucketSize = 16 };
+ void deleteBuckets() {
+ for (size_t i = 0; i < kNumBuckets; i++) {
+ if (buckets_[i] != nullptr) {
+ delete[] buckets_[i];
+ }
+ }
+ }
+
T* buckets_[kNumBuckets];
static inline const T default_ = {};
};
diff --git a/libs/androidfw/include/androidfw/LoadedArsc.h b/libs/androidfw/include/androidfw/LoadedArsc.h
index 79d96282..4d12885 100644
--- a/libs/androidfw/include/androidfw/LoadedArsc.h
+++ b/libs/androidfw/include/androidfw/LoadedArsc.h
@@ -99,8 +99,8 @@
};
struct OverlayableInfo {
- std::string name;
- std::string actor;
+ std::string_view name;
+ std::string_view actor;
uint32_t policy_flags;
};
@@ -275,7 +275,7 @@
return overlayable_map_;
}
- const std::map<uint32_t, uint32_t>& GetAliasResourceIdMap() const {
+ const std::vector<std::pair<uint32_t, uint32_t>>& GetAliasResourceIdMap() const {
return alias_id_map_;
}
@@ -295,8 +295,8 @@
std::unordered_map<uint8_t, TypeSpec> type_specs_;
ByteBucketArray<uint32_t> resource_ids_;
std::vector<DynamicPackageEntry> dynamic_package_map_;
- std::vector<const std::pair<OverlayableInfo, std::unordered_set<uint32_t>>> overlayable_infos_;
- std::map<uint32_t, uint32_t> alias_id_map_;
+ std::vector<std::pair<OverlayableInfo, std::unordered_set<uint32_t>>> overlayable_infos_;
+ std::vector<std::pair<uint32_t, uint32_t>> alias_id_map_;
// A map of overlayable name to actor
std::unordered_map<std::string, std::string> overlayable_map_;
diff --git a/libs/androidfw/include/androidfw/misc.h b/libs/androidfw/include/androidfw/misc.h
index 5a5a0e2..d40d24e 100644
--- a/libs/androidfw/include/androidfw/misc.h
+++ b/libs/androidfw/include/androidfw/misc.h
@@ -44,6 +44,10 @@
/* get the file's modification date; returns -1 w/errno set on failure */
time_t getFileModDate(const char* fileName);
+// Check if |path| or |fd| resides on a readonly filesystem.
+bool isReadonlyFilesystem(const char* path);
+bool isReadonlyFilesystem(int fd);
+
}; // namespace android
#endif // _LIBS_ANDROID_FW_MISC_H
diff --git a/libs/androidfw/misc.cpp b/libs/androidfw/misc.cpp
index 5285420..7af5066 100644
--- a/libs/androidfw/misc.cpp
+++ b/libs/androidfw/misc.cpp
@@ -21,12 +21,17 @@
//
#include <androidfw/misc.h>
-#include <sys/stat.h>
-#include <cstring>
-#include <errno.h>
-#include <cstdio>
+#include "android-base/logging.h"
-using namespace android;
+#ifndef _WIN32
+#include <sys/statvfs.h>
+#include <sys/vfs.h>
+#endif // _WIN32
+
+#include <cstring>
+#include <cstdio>
+#include <errno.h>
+#include <sys/stat.h>
namespace android {
@@ -41,8 +46,7 @@
if (errno == ENOENT || errno == ENOTDIR)
return kFileTypeNonexistent;
else {
- fprintf(stderr, "getFileType got errno=%d on '%s'\n",
- errno, fileName);
+ PLOG(ERROR) << "getFileType(): stat(" << fileName << ") failed";
return kFileTypeUnknown;
}
} else {
@@ -82,4 +86,32 @@
return sb.st_mtime;
}
+#ifdef _WIN32
+// No need to implement these for Windows, the functions only matter on a device.
+bool isReadonlyFilesystem(const char*) {
+ return false;
+}
+bool isReadonlyFilesystem(int) {
+ return false;
+}
+#else // _WIN32
+bool isReadonlyFilesystem(const char* path) {
+ struct statfs sfs;
+ if (::statfs(path, &sfs)) {
+ PLOG(ERROR) << "isReadonlyFilesystem(): statfs(" << path << ") failed";
+ return false;
+ }
+ return (sfs.f_flags & ST_RDONLY) != 0;
+}
+
+bool isReadonlyFilesystem(int fd) {
+ struct statfs sfs;
+ if (::fstatfs(fd, &sfs)) {
+ PLOG(ERROR) << "isReadonlyFilesystem(): fstatfs(" << fd << ") failed";
+ return false;
+ }
+ return (sfs.f_flags & ST_RDONLY) != 0;
+}
+#endif // _WIN32
+
}; // namespace android
diff --git a/libs/androidfw/tests/ByteBucketArray_test.cpp b/libs/androidfw/tests/ByteBucketArray_test.cpp
index 5d464c7..9c36cfb 100644
--- a/libs/androidfw/tests/ByteBucketArray_test.cpp
+++ b/libs/androidfw/tests/ByteBucketArray_test.cpp
@@ -52,4 +52,57 @@
}
}
+TEST(ByteBucketArrayTest, TestForEach) {
+ ByteBucketArray<int> bba;
+ ASSERT_TRUE(bba.set(0, 1));
+ ASSERT_TRUE(bba.set(10, 2));
+ ASSERT_TRUE(bba.set(26, 3));
+ ASSERT_TRUE(bba.set(129, 4));
+ ASSERT_TRUE(bba.set(234, 5));
+
+ int count = 0;
+ bba.forEachItem([&count](auto i, auto val) {
+ ++count;
+ switch (i) {
+ case 0:
+ EXPECT_EQ(1, val);
+ break;
+ case 10:
+ EXPECT_EQ(2, val);
+ break;
+ case 26:
+ EXPECT_EQ(3, val);
+ break;
+ case 129:
+ EXPECT_EQ(4, val);
+ break;
+ case 234:
+ EXPECT_EQ(5, val);
+ break;
+ default:
+ EXPECT_EQ(0, val);
+ break;
+ }
+ });
+ ASSERT_EQ(4 * 16, count);
+}
+
+TEST(ByteBucketArrayTest, TestTrimBuckets) {
+ ByteBucketArray<int> bba;
+ ASSERT_TRUE(bba.set(0, 1));
+ ASSERT_TRUE(bba.set(255, 2));
+ {
+ bba.trimBuckets([](auto val) { return val < 2; });
+ int count = 0;
+ bba.forEachItem([&count](auto, auto) { ++count; });
+ ASSERT_EQ(1 * 16, count);
+ }
+ {
+ bba.trimBuckets([](auto val) { return val < 3; });
+ int count = 0;
+ bba.forEachItem([&count](auto, auto) { ++count; });
+ ASSERT_EQ(0, count);
+ }
+}
+
} // namespace android
diff --git a/libs/hwui/SkiaInterpolator.cpp b/libs/hwui/SkiaInterpolator.cpp
index 0695dd1..153c3b6 100644
--- a/libs/hwui/SkiaInterpolator.cpp
+++ b/libs/hwui/SkiaInterpolator.cpp
@@ -17,6 +17,8 @@
#include "SkiaInterpolator.h"
#include "include/core/SkMath.h"
+#include "include/core/SkScalar.h"
+#include "include/core/SkTypes.h"
#include "include/private/SkFixed.h"
#include "include/private/SkMalloc.h"
#include "include/private/SkTo.h"
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaLogger.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaLogger.kt
index 00a0362..6ecb7fa 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaLogger.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaLogger.kt
@@ -16,6 +16,7 @@
package com.android.settingslib.spa.framework.common
+import android.os.Bundle
import android.util.Log
// Defines the category of the log, for quick filter
@@ -38,10 +39,13 @@
// Entry related events.
ENTRY_CLICK,
- ENTRY_SWITCH_ON,
- ENTRY_SWITCH_OFF,
+ ENTRY_SWITCH,
}
+internal const val LOG_DATA_DISPLAY_NAME = "name"
+internal const val LOG_DATA_SESSION_NAME = "session"
+internal const val LOG_DATA_SWITCH_STATUS = "switch"
+
/**
* The interface of logger in Spa
*/
@@ -54,7 +58,7 @@
id: String,
event: LogEvent,
category: LogCategory = LogCategory.DEFAULT,
- details: String? = null
+ extraData: Bundle = Bundle.EMPTY
) {
}
}
@@ -64,8 +68,8 @@
Log.d("SpaMsg-$category", "[$tag] $msg")
}
- override fun event(id: String, event: LogEvent, category: LogCategory, details: String?) {
- val extraMsg = if (details == null) "" else " ($details)"
- Log.d("SpaEvent-$category", "[$id] $event$extraMsg")
+ override fun event(id: String, event: LogEvent, category: LogCategory, extraData: Bundle) {
+ val extraMsg = extraData.toString().removeRange(0, 6)
+ Log.d("SpaEvent-$category", "[$id] $event $extraMsg")
}
}
\ No newline at end of file
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/EntryLogger.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/EntryLogger.kt
index 8d0a35c..1c88187 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/EntryLogger.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/EntryLogger.kt
@@ -16,17 +16,22 @@
package com.android.settingslib.spa.framework.util
+import android.os.Bundle
import androidx.compose.runtime.Composable
+import androidx.core.os.bundleOf
+import com.android.settingslib.spa.framework.common.LOG_DATA_SWITCH_STATUS
import com.android.settingslib.spa.framework.common.LocalEntryDataProvider
import com.android.settingslib.spa.framework.common.LogCategory
import com.android.settingslib.spa.framework.common.LogEvent
import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
@Composable
-fun logEntryEvent(): (event: LogEvent) -> Unit {
- val entryId = LocalEntryDataProvider.current.entryId ?: return {}
- return {
- SpaEnvironmentFactory.instance.logger.event(entryId, it, category = LogCategory.VIEW)
+fun logEntryEvent(): (event: LogEvent, extraData: Bundle) -> Unit {
+ val entryId = LocalEntryDataProvider.current.entryId ?: return { _, _ -> }
+ return { event, extraData ->
+ SpaEnvironmentFactory.instance.logger.event(
+ entryId, event, category = LogCategory.VIEW, extraData = extraData
+ )
}
}
@@ -35,7 +40,7 @@
if (onClick == null) return null
val logEvent = logEntryEvent()
return {
- logEvent(LogEvent.ENTRY_CLICK)
+ logEvent(LogEvent.ENTRY_CLICK, Bundle.EMPTY)
onClick()
}
}
@@ -45,8 +50,7 @@
if (onSwitch == null) return null
val logEvent = logEntryEvent()
return {
- val event = if (it) LogEvent.ENTRY_SWITCH_ON else LogEvent.ENTRY_SWITCH_OFF
- logEvent(event)
+ logEvent(LogEvent.ENTRY_SWITCH, bundleOf(LOG_DATA_SWITCH_STATUS to it))
onSwitch(it)
}
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/Flows.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/Flows.kt
index d801840..97e3ac2 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/Flows.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/Flows.kt
@@ -27,6 +27,13 @@
import kotlinx.coroutines.flow.map
/**
+ * Returns a [Flow] whose values are a list which containing the results of applying the given
+ * [transform] function to each element in the original flow's list.
+ */
+inline fun <T, R> Flow<List<T>>.mapItem(crossinline transform: (T) -> R): Flow<List<R>> =
+ map { list -> list.map(transform) }
+
+/**
* Returns a [Flow] whose values are a list which containing the results of asynchronously applying
* the given [transform] function to each element in the original flow's list.
*/
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/PageLogger.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/PageLogger.kt
index b9e4b78..a881254 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/PageLogger.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/PageLogger.kt
@@ -21,8 +21,11 @@
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalLifecycleOwner
+import androidx.core.os.bundleOf
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver
+import com.android.settingslib.spa.framework.common.LOG_DATA_DISPLAY_NAME
+import com.android.settingslib.spa.framework.common.LOG_DATA_SESSION_NAME
import com.android.settingslib.spa.framework.common.LogCategory
import com.android.settingslib.spa.framework.common.LogEvent
import com.android.settingslib.spa.framework.common.SettingsPageProvider
@@ -37,21 +40,21 @@
val navController = LocalNavController.current
DisposableEffect(lifecycleOwner) {
val observer = LifecycleEventObserver { _, event ->
- val spaLogger = SpaEnvironmentFactory.instance.logger
+ val logPageEvent: (event: LogEvent) -> Unit = {
+ SpaEnvironmentFactory.instance.logger.event(
+ id = page.id,
+ event = it,
+ category = LogCategory.FRAMEWORK,
+ extraData = bundleOf(
+ LOG_DATA_DISPLAY_NAME to page.displayName,
+ LOG_DATA_SESSION_NAME to navController.sessionSourceName,
+ )
+ )
+ }
if (event == Lifecycle.Event.ON_START) {
- spaLogger.event(
- page.id,
- LogEvent.PAGE_ENTER,
- category = LogCategory.FRAMEWORK,
- details = navController.sessionSourceName ?: page.displayName,
- )
+ logPageEvent(LogEvent.PAGE_ENTER)
} else if (event == Lifecycle.Event.ON_STOP) {
- spaLogger.event(
- page.id,
- LogEvent.PAGE_LEAVE,
- category = LogCategory.FRAMEWORK,
- details = navController.sessionSourceName ?: page.displayName,
- )
+ logPageEvent(LogEvent.PAGE_LEAVE)
}
}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/tests/testutils/SpaEnvironmentForTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/tests/testutils/SpaEnvironmentForTest.kt
index 6385954..f38bd08 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/tests/testutils/SpaEnvironmentForTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/tests/testutils/SpaEnvironmentForTest.kt
@@ -51,7 +51,7 @@
messageCount[key] = (messageCount[key] ?: 0) + 1
}
- override fun event(id: String, event: LogEvent, category: LogCategory, details: String?) {
+ override fun event(id: String, event: LogEvent, category: LogCategory, extraData: Bundle) {
val key = EventCountKey(id, event, category)
eventCount[key] = (eventCount[key] ?: 0) + 1
}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListModel.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListModel.kt
index a7122d0..6999908 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListModel.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListModel.kt
@@ -33,7 +33,8 @@
*
* @return the [AppRecord] list which will be displayed.
*/
- fun filter(userIdFlow: Flow<Int>, option: Int, recordListFlow: Flow<List<T>>): Flow<List<T>>
+ fun filter(userIdFlow: Flow<Int>, option: Int, recordListFlow: Flow<List<T>>): Flow<List<T>> =
+ recordListFlow
/**
* This function is called when the App List's loading is finished and displayed to the user.
@@ -67,5 +68,5 @@
* @return null if no summary should be displayed.
*/
@Composable
- fun getSummary(option: Int, record: T): State<String>?
+ fun getSummary(option: Int, record: T): State<String>? = null
}
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListViewModelTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListViewModelTest.kt
index 65c547a..b9c875b 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListViewModelTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListViewModelTest.kt
@@ -21,7 +21,7 @@
import androidx.compose.runtime.Composable
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.settingslib.spa.framework.compose.stateOf
-import com.android.settingslib.spa.framework.util.asyncMapItem
+import com.android.settingslib.spa.framework.util.mapItem
import com.android.settingslib.spa.testutils.waitUntil
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -116,16 +116,7 @@
var onFirstLoadedCalled = false
override fun transform(userIdFlow: Flow<Int>, appListFlow: Flow<List<ApplicationInfo>>) =
- appListFlow.asyncMapItem { TestAppRecord(it) }
-
- @Composable
- override fun getSummary(option: Int, record: TestAppRecord) = null
-
- override fun filter(
- userIdFlow: Flow<Int>,
- option: Int,
- recordListFlow: Flow<List<TestAppRecord>>,
- ) = recordListFlow
+ appListFlow.mapItem(::TestAppRecord)
override suspend fun onFirstLoaded(recordList: List<TestAppRecord>) {
onFirstLoadedCalled = true
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/tests/testutils/TestAppListModel.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/tests/testutils/TestAppListModel.kt
index d556487..ada4016 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/tests/testutils/TestAppListModel.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/tests/testutils/TestAppListModel.kt
@@ -17,8 +17,7 @@
package com.android.settingslib.spaprivileged.tests.testutils
import android.content.pm.ApplicationInfo
-import androidx.compose.runtime.Composable
-import com.android.settingslib.spa.framework.util.asyncMapItem
+import com.android.settingslib.spa.framework.util.mapItem
import com.android.settingslib.spaprivileged.model.app.AppListModel
import com.android.settingslib.spaprivileged.model.app.AppRecord
import kotlinx.coroutines.flow.Flow
@@ -35,16 +34,7 @@
override fun getSpinnerOptions() = options
override fun transform(userIdFlow: Flow<Int>, appListFlow: Flow<List<ApplicationInfo>>) =
- appListFlow.asyncMapItem { TestAppRecord(it) }
-
- @Composable
- override fun getSummary(option: Int, record: TestAppRecord) = null
-
- override fun filter(
- userIdFlow: Flow<Int>,
- option: Int,
- recordListFlow: Flow<List<TestAppRecord>>,
- ) = recordListFlow
+ appListFlow.mapItem(::TestAppRecord)
override fun getGroupTitle(option: Int, record: TestAppRecord) =
if (enableGrouping) record.group else null
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/data/content/FakeKeyguardQuickAffordanceProviderClient.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/data/content/FakeKeyguardQuickAffordanceProviderClient.kt
new file mode 100644
index 0000000..f490c54
--- /dev/null
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/data/content/FakeKeyguardQuickAffordanceProviderClient.kt
@@ -0,0 +1,190 @@
+/*
+ * 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.shared.quickaffordance.data.content
+
+import android.graphics.drawable.BitmapDrawable
+import android.graphics.drawable.Drawable
+import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.combine
+
+class FakeKeyguardQuickAffordanceProviderClient(
+ slots: List<KeyguardQuickAffordanceProviderClient.Slot> =
+ listOf(
+ KeyguardQuickAffordanceProviderClient.Slot(
+ id = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START,
+ capacity = 1,
+ ),
+ KeyguardQuickAffordanceProviderClient.Slot(
+ id = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END,
+ capacity = 1,
+ ),
+ ),
+ affordances: List<KeyguardQuickAffordanceProviderClient.Affordance> =
+ listOf(
+ KeyguardQuickAffordanceProviderClient.Affordance(
+ id = AFFORDANCE_1,
+ name = AFFORDANCE_1,
+ iconResourceId = 0,
+ ),
+ KeyguardQuickAffordanceProviderClient.Affordance(
+ id = AFFORDANCE_2,
+ name = AFFORDANCE_2,
+ iconResourceId = 0,
+ ),
+ KeyguardQuickAffordanceProviderClient.Affordance(
+ id = AFFORDANCE_3,
+ name = AFFORDANCE_3,
+ iconResourceId = 0,
+ ),
+ ),
+ flags: List<KeyguardQuickAffordanceProviderClient.Flag> =
+ listOf(
+ KeyguardQuickAffordanceProviderClient.Flag(
+ name = KeyguardQuickAffordanceProviderContract.FlagsTable.FLAG_NAME_FEATURE_ENABLED,
+ value = true,
+ )
+ ),
+) : KeyguardQuickAffordanceProviderClient {
+
+ private val slots = MutableStateFlow(slots)
+ private val affordances = MutableStateFlow(affordances)
+ private val flags = MutableStateFlow(flags)
+
+ private val selections = MutableStateFlow<Map<String, List<String>>>(emptyMap())
+
+ override suspend fun insertSelection(slotId: String, affordanceId: String) {
+ val slotCapacity =
+ querySlots().find { it.id == slotId }?.capacity
+ ?: error("Slot with ID \"$slotId\" not found!")
+ val affordances = selections.value.getOrDefault(slotId, mutableListOf()).toMutableList()
+ while (affordances.size + 1 > slotCapacity) {
+ affordances.removeAt(0)
+ }
+ affordances.remove(affordanceId)
+ affordances.add(affordanceId)
+ selections.value = selections.value.toMutableMap().apply { this[slotId] = affordances }
+ }
+
+ override suspend fun querySlots(): List<KeyguardQuickAffordanceProviderClient.Slot> {
+ return slots.value
+ }
+
+ override suspend fun queryFlags(): List<KeyguardQuickAffordanceProviderClient.Flag> {
+ return flags.value
+ }
+
+ override fun observeSlots(): Flow<List<KeyguardQuickAffordanceProviderClient.Slot>> {
+ return slots.asStateFlow()
+ }
+
+ override fun observeFlags(): Flow<List<KeyguardQuickAffordanceProviderClient.Flag>> {
+ return flags.asStateFlow()
+ }
+
+ override suspend fun queryAffordances():
+ List<KeyguardQuickAffordanceProviderClient.Affordance> {
+ return affordances.value
+ }
+
+ override fun observeAffordances():
+ Flow<List<KeyguardQuickAffordanceProviderClient.Affordance>> {
+ return affordances.asStateFlow()
+ }
+
+ override suspend fun querySelections(): List<KeyguardQuickAffordanceProviderClient.Selection> {
+ return toSelectionList(selections.value, affordances.value)
+ }
+
+ override fun observeSelections(): Flow<List<KeyguardQuickAffordanceProviderClient.Selection>> {
+ return combine(selections, affordances) { selections, affordances ->
+ toSelectionList(selections, affordances)
+ }
+ }
+
+ override suspend fun deleteSelection(slotId: String, affordanceId: String) {
+ val affordances = selections.value.getOrDefault(slotId, mutableListOf()).toMutableList()
+ affordances.remove(affordanceId)
+
+ selections.value = selections.value.toMutableMap().apply { this[slotId] = affordances }
+ }
+
+ override suspend fun deleteAllSelections(slotId: String) {
+ selections.value = selections.value.toMutableMap().apply { this[slotId] = emptyList() }
+ }
+
+ override suspend fun getAffordanceIcon(iconResourceId: Int, tintColor: Int): Drawable {
+ return BitmapDrawable()
+ }
+
+ fun setFlag(
+ name: String,
+ value: Boolean,
+ ) {
+ flags.value =
+ flags.value.toMutableList().apply {
+ removeIf { it.name == name }
+ add(KeyguardQuickAffordanceProviderClient.Flag(name = name, value = value))
+ }
+ }
+
+ fun setSlotCapacity(slotId: String, capacity: Int) {
+ slots.value =
+ slots.value.toMutableList().apply {
+ val index = indexOfFirst { it.id == slotId }
+ check(index != -1) { "Slot with ID \"$slotId\" doesn't exist!" }
+ set(
+ index,
+ KeyguardQuickAffordanceProviderClient.Slot(id = slotId, capacity = capacity)
+ )
+ }
+ }
+
+ fun addAffordance(affordance: KeyguardQuickAffordanceProviderClient.Affordance): Int {
+ affordances.value = affordances.value + listOf(affordance)
+ return affordances.value.size - 1
+ }
+
+ private fun toSelectionList(
+ selections: Map<String, List<String>>,
+ affordances: List<KeyguardQuickAffordanceProviderClient.Affordance>,
+ ): List<KeyguardQuickAffordanceProviderClient.Selection> {
+ return selections
+ .map { (slotId, affordanceIds) ->
+ affordanceIds.map { affordanceId ->
+ val affordanceName =
+ affordances.find { it.id == affordanceId }?.name
+ ?: error("No affordance with ID of \"$affordanceId\"!")
+ KeyguardQuickAffordanceProviderClient.Selection(
+ slotId = slotId,
+ affordanceId = affordanceId,
+ affordanceName = affordanceName,
+ )
+ }
+ }
+ .flatten()
+ }
+
+ companion object {
+ const val AFFORDANCE_1 = "affordance_1"
+ const val AFFORDANCE_2 = "affordance_2"
+ const val AFFORDANCE_3 = "affordance_3"
+ }
+}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/data/content/KeyguardQuickAffordanceProviderClient.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/data/content/KeyguardQuickAffordanceProviderClient.kt
new file mode 100644
index 0000000..3213b2e
--- /dev/null
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/data/content/KeyguardQuickAffordanceProviderClient.kt
@@ -0,0 +1,479 @@
+/*
+ * 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.shared.quickaffordance.data.content
+
+import android.annotation.SuppressLint
+import android.content.ContentValues
+import android.content.Context
+import android.database.ContentObserver
+import android.graphics.Color
+import android.graphics.drawable.Drawable
+import android.net.Uri
+import androidx.annotation.DrawableRes
+import com.android.systemui.shared.quickaffordance.data.content.KeyguardQuickAffordanceProviderContract as Contract
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.withContext
+
+/** Client for using a content provider implementing the [Contract]. */
+interface KeyguardQuickAffordanceProviderClient {
+
+ /**
+ * Selects an affordance with the given ID for a slot on the lock screen with the given ID.
+ *
+ * Note that the maximum number of selected affordances on this slot is automatically enforced.
+ * Selecting a slot that is already full (e.g. already has a number of selected affordances at
+ * its maximum capacity) will automatically remove the oldest selected affordance before adding
+ * the one passed in this call. Additionally, selecting an affordance that's already one of the
+ * selected affordances on the slot will move the selected affordance to the newest location in
+ * the slot.
+ */
+ suspend fun insertSelection(
+ slotId: String,
+ affordanceId: String,
+ )
+
+ /** Returns all available slots supported by the device. */
+ suspend fun querySlots(): List<Slot>
+
+ /** Returns the list of flags. */
+ suspend fun queryFlags(): List<Flag>
+
+ /**
+ * Returns [Flow] for observing the collection of slots.
+ *
+ * @see [querySlots]
+ */
+ fun observeSlots(): Flow<List<Slot>>
+
+ /**
+ * Returns [Flow] for observing the collection of flags.
+ *
+ * @see [queryFlags]
+ */
+ fun observeFlags(): Flow<List<Flag>>
+
+ /**
+ * Returns all available affordances supported by the device, regardless of current slot
+ * placement.
+ */
+ suspend fun queryAffordances(): List<Affordance>
+
+ /**
+ * Returns [Flow] for observing the collection of affordances.
+ *
+ * @see [queryAffordances]
+ */
+ fun observeAffordances(): Flow<List<Affordance>>
+
+ /** Returns the current slot-affordance selections. */
+ suspend fun querySelections(): List<Selection>
+
+ /**
+ * Returns [Flow] for observing the collection of selections.
+ *
+ * @see [querySelections]
+ */
+ fun observeSelections(): Flow<List<Selection>>
+
+ /** Unselects an affordance with the given ID from the slot with the given ID. */
+ suspend fun deleteSelection(
+ slotId: String,
+ affordanceId: String,
+ )
+
+ /** Unselects all affordances from the slot with the given ID. */
+ suspend fun deleteAllSelections(
+ slotId: String,
+ )
+
+ /** Returns a [Drawable] with the given ID, loaded from the system UI package. */
+ suspend fun getAffordanceIcon(
+ @DrawableRes iconResourceId: Int,
+ tintColor: Int = Color.WHITE,
+ ): Drawable
+
+ /** Models a slot. A position that quick affordances can be positioned in. */
+ data class Slot(
+ /** Unique ID of the slot. */
+ val id: String,
+ /**
+ * The maximum number of quick affordances that are allowed to be positioned in this slot.
+ */
+ val capacity: Int,
+ )
+
+ /**
+ * Models a quick affordance. An action that can be selected by the user to appear in one or
+ * more slots on the lock screen.
+ */
+ data class Affordance(
+ /** Unique ID of the quick affordance. */
+ val id: String,
+ /** User-facing label for this affordance. */
+ val name: String,
+ /**
+ * Resource ID for the user-facing icon for this affordance. This resource is hosted by the
+ * System UI process so it must be used with
+ * `PackageManager.getResourcesForApplication(String)`.
+ */
+ val iconResourceId: Int,
+ /**
+ * Whether the affordance is enabled. Disabled affordances should be shown on the picker but
+ * should be rendered as "disabled". When tapped, the enablement properties should be used
+ * to populate UI that would explain to the user what to do in order to re-enable this
+ * affordance.
+ */
+ val isEnabled: Boolean = true,
+ /**
+ * If the affordance is disabled, this is a set of instruction messages to be shown to the
+ * user when the disabled affordance is selected. The instructions should help the user
+ * figure out what to do in order to re-neable this affordance.
+ */
+ val enablementInstructions: List<String>? = null,
+ /**
+ * If the affordance is disabled, this is a label for a button shown together with the set
+ * of instruction messages when the disabled affordance is selected. The button should help
+ * send the user to a flow that would help them achieve the instructions and re-enable this
+ * affordance.
+ *
+ * If `null`, the button should not be shown.
+ */
+ val enablementActionText: String? = null,
+ /**
+ * If the affordance is disabled, this is a "component name" of the format
+ * `packageName/action` to be used as an `Intent` for `startActivity` when the action button
+ * (shown together with the set of instruction messages when the disabled affordance is
+ * selected) is clicked by the user. The button should help send the user to a flow that
+ * would help them achieve the instructions and re-enable this affordance.
+ *
+ * If `null`, the button should not be shown.
+ */
+ val enablementActionComponentName: String? = null,
+ )
+
+ /** Models a selection of a quick affordance on a slot. */
+ data class Selection(
+ /** The unique ID of the slot. */
+ val slotId: String,
+ /** The unique ID of the quick affordance. */
+ val affordanceId: String,
+ /** The user-visible label for the quick affordance. */
+ val affordanceName: String,
+ )
+
+ /** Models a System UI flag. */
+ data class Flag(
+ /** The name of the flag. */
+ val name: String,
+ /** The value of the flag. */
+ val value: Boolean,
+ )
+}
+
+class KeyguardQuickAffordanceProviderClientImpl(
+ private val context: Context,
+ private val backgroundDispatcher: CoroutineDispatcher,
+) : KeyguardQuickAffordanceProviderClient {
+
+ override suspend fun insertSelection(
+ slotId: String,
+ affordanceId: String,
+ ) {
+ withContext(backgroundDispatcher) {
+ context.contentResolver.insert(
+ Contract.SelectionTable.URI,
+ ContentValues().apply {
+ put(Contract.SelectionTable.Columns.SLOT_ID, slotId)
+ put(Contract.SelectionTable.Columns.AFFORDANCE_ID, affordanceId)
+ }
+ )
+ }
+ }
+
+ override suspend fun querySlots(): List<KeyguardQuickAffordanceProviderClient.Slot> {
+ return withContext(backgroundDispatcher) {
+ context.contentResolver
+ .query(
+ Contract.SlotTable.URI,
+ null,
+ null,
+ null,
+ null,
+ )
+ ?.use { cursor ->
+ buildList {
+ val idColumnIndex = cursor.getColumnIndex(Contract.SlotTable.Columns.ID)
+ val capacityColumnIndex =
+ cursor.getColumnIndex(Contract.SlotTable.Columns.CAPACITY)
+ if (idColumnIndex == -1 || capacityColumnIndex == -1) {
+ return@buildList
+ }
+
+ while (cursor.moveToNext()) {
+ add(
+ KeyguardQuickAffordanceProviderClient.Slot(
+ id = cursor.getString(idColumnIndex),
+ capacity = cursor.getInt(capacityColumnIndex),
+ )
+ )
+ }
+ }
+ }
+ }
+ ?: emptyList()
+ }
+
+ override suspend fun queryFlags(): List<KeyguardQuickAffordanceProviderClient.Flag> {
+ return withContext(backgroundDispatcher) {
+ context.contentResolver
+ .query(
+ Contract.FlagsTable.URI,
+ null,
+ null,
+ null,
+ null,
+ )
+ ?.use { cursor ->
+ buildList {
+ val nameColumnIndex =
+ cursor.getColumnIndex(Contract.FlagsTable.Columns.NAME)
+ val valueColumnIndex =
+ cursor.getColumnIndex(Contract.FlagsTable.Columns.VALUE)
+ if (nameColumnIndex == -1 || valueColumnIndex == -1) {
+ return@buildList
+ }
+
+ while (cursor.moveToNext()) {
+ add(
+ KeyguardQuickAffordanceProviderClient.Flag(
+ name = cursor.getString(nameColumnIndex),
+ value = cursor.getInt(valueColumnIndex) == 1,
+ )
+ )
+ }
+ }
+ }
+ }
+ ?: emptyList()
+ }
+
+ override fun observeSlots(): Flow<List<KeyguardQuickAffordanceProviderClient.Slot>> {
+ return observeUri(Contract.SlotTable.URI).map { querySlots() }
+ }
+
+ override fun observeFlags(): Flow<List<KeyguardQuickAffordanceProviderClient.Flag>> {
+ return observeUri(Contract.FlagsTable.URI).map { queryFlags() }
+ }
+
+ override suspend fun queryAffordances():
+ List<KeyguardQuickAffordanceProviderClient.Affordance> {
+ return withContext(backgroundDispatcher) {
+ context.contentResolver
+ .query(
+ Contract.AffordanceTable.URI,
+ null,
+ null,
+ null,
+ null,
+ )
+ ?.use { cursor ->
+ buildList {
+ val idColumnIndex =
+ cursor.getColumnIndex(Contract.AffordanceTable.Columns.ID)
+ val nameColumnIndex =
+ cursor.getColumnIndex(Contract.AffordanceTable.Columns.NAME)
+ val iconColumnIndex =
+ cursor.getColumnIndex(Contract.AffordanceTable.Columns.ICON)
+ val isEnabledColumnIndex =
+ cursor.getColumnIndex(Contract.AffordanceTable.Columns.IS_ENABLED)
+ val enablementInstructionsColumnIndex =
+ cursor.getColumnIndex(
+ Contract.AffordanceTable.Columns.ENABLEMENT_INSTRUCTIONS
+ )
+ val enablementActionTextColumnIndex =
+ cursor.getColumnIndex(
+ Contract.AffordanceTable.Columns.ENABLEMENT_ACTION_TEXT
+ )
+ val enablementComponentNameColumnIndex =
+ cursor.getColumnIndex(
+ Contract.AffordanceTable.Columns.ENABLEMENT_COMPONENT_NAME
+ )
+ if (
+ idColumnIndex == -1 ||
+ nameColumnIndex == -1 ||
+ iconColumnIndex == -1 ||
+ isEnabledColumnIndex == -1 ||
+ enablementInstructionsColumnIndex == -1 ||
+ enablementActionTextColumnIndex == -1 ||
+ enablementComponentNameColumnIndex == -1
+ ) {
+ return@buildList
+ }
+
+ while (cursor.moveToNext()) {
+ add(
+ KeyguardQuickAffordanceProviderClient.Affordance(
+ id = cursor.getString(idColumnIndex),
+ name = cursor.getString(nameColumnIndex),
+ iconResourceId = cursor.getInt(iconColumnIndex),
+ isEnabled = cursor.getInt(isEnabledColumnIndex) == 1,
+ enablementInstructions =
+ cursor
+ .getString(enablementInstructionsColumnIndex)
+ ?.split(
+ Contract.AffordanceTable
+ .ENABLEMENT_INSTRUCTIONS_DELIMITER
+ ),
+ enablementActionText =
+ cursor.getString(enablementActionTextColumnIndex),
+ enablementActionComponentName =
+ cursor.getString(enablementComponentNameColumnIndex),
+ )
+ )
+ }
+ }
+ }
+ }
+ ?: emptyList()
+ }
+
+ override fun observeAffordances():
+ Flow<List<KeyguardQuickAffordanceProviderClient.Affordance>> {
+ return observeUri(Contract.AffordanceTable.URI).map { queryAffordances() }
+ }
+
+ override suspend fun querySelections(): List<KeyguardQuickAffordanceProviderClient.Selection> {
+ return withContext(backgroundDispatcher) {
+ context.contentResolver
+ .query(
+ Contract.SelectionTable.URI,
+ null,
+ null,
+ null,
+ null,
+ )
+ ?.use { cursor ->
+ buildList {
+ val slotIdColumnIndex =
+ cursor.getColumnIndex(Contract.SelectionTable.Columns.SLOT_ID)
+ val affordanceIdColumnIndex =
+ cursor.getColumnIndex(Contract.SelectionTable.Columns.AFFORDANCE_ID)
+ val affordanceNameColumnIndex =
+ cursor.getColumnIndex(Contract.SelectionTable.Columns.AFFORDANCE_NAME)
+ if (
+ slotIdColumnIndex == -1 ||
+ affordanceIdColumnIndex == -1 ||
+ affordanceNameColumnIndex == -1
+ ) {
+ return@buildList
+ }
+
+ while (cursor.moveToNext()) {
+ add(
+ KeyguardQuickAffordanceProviderClient.Selection(
+ slotId = cursor.getString(slotIdColumnIndex),
+ affordanceId = cursor.getString(affordanceIdColumnIndex),
+ affordanceName = cursor.getString(affordanceNameColumnIndex),
+ )
+ )
+ }
+ }
+ }
+ }
+ ?: emptyList()
+ }
+
+ override fun observeSelections(): Flow<List<KeyguardQuickAffordanceProviderClient.Selection>> {
+ return observeUri(Contract.SelectionTable.URI).map { querySelections() }
+ }
+
+ override suspend fun deleteSelection(
+ slotId: String,
+ affordanceId: String,
+ ) {
+ withContext(backgroundDispatcher) {
+ context.contentResolver.delete(
+ Contract.SelectionTable.URI,
+ "${Contract.SelectionTable.Columns.SLOT_ID} = ? AND" +
+ " ${Contract.SelectionTable.Columns.AFFORDANCE_ID} = ?",
+ arrayOf(
+ slotId,
+ affordanceId,
+ ),
+ )
+ }
+ }
+
+ override suspend fun deleteAllSelections(
+ slotId: String,
+ ) {
+ withContext(backgroundDispatcher) {
+ context.contentResolver.delete(
+ Contract.SelectionTable.URI,
+ Contract.SelectionTable.Columns.SLOT_ID,
+ arrayOf(
+ slotId,
+ ),
+ )
+ }
+ }
+
+ @SuppressLint("UseCompatLoadingForDrawables")
+ override suspend fun getAffordanceIcon(
+ @DrawableRes iconResourceId: Int,
+ tintColor: Int,
+ ): Drawable {
+ return withContext(backgroundDispatcher) {
+ context.packageManager
+ .getResourcesForApplication(SYSTEM_UI_PACKAGE_NAME)
+ .getDrawable(iconResourceId, context.theme)
+ .apply { setTint(tintColor) }
+ }
+ }
+
+ private fun observeUri(
+ uri: Uri,
+ ): Flow<Unit> {
+ return callbackFlow {
+ val observer =
+ object : ContentObserver(null) {
+ override fun onChange(selfChange: Boolean) {
+ trySend(Unit)
+ }
+ }
+
+ context.contentResolver.registerContentObserver(
+ uri,
+ /* notifyForDescendants= */ true,
+ observer,
+ )
+
+ awaitClose { context.contentResolver.unregisterContentObserver(observer) }
+ }
+ .onStart { emit(Unit) }
+ }
+
+ companion object {
+ private const val SYSTEM_UI_PACKAGE_NAME = "com.android.systemui"
+ }
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/keyguard/data/content/KeyguardQuickAffordanceProviderContract.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/data/content/KeyguardQuickAffordanceProviderContract.kt
similarity index 98%
rename from packages/SystemUI/shared/src/com/android/systemui/shared/keyguard/data/content/KeyguardQuickAffordanceProviderContract.kt
rename to packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/data/content/KeyguardQuickAffordanceProviderContract.kt
index 98d8d3e..17be74b 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/keyguard/data/content/KeyguardQuickAffordanceProviderContract.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/data/content/KeyguardQuickAffordanceProviderContract.kt
@@ -15,7 +15,7 @@
*
*/
-package com.android.systemui.shared.keyguard.data.content
+package com.android.systemui.shared.quickaffordance.data.content
import android.content.ContentResolver
import android.net.Uri
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/keyguard/shared/model/KeyguardQuickAffordanceSlots.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/shared/model/KeyguardQuickAffordanceSlots.kt
similarity index 100%
rename from packages/SystemUI/shared/src/com/android/systemui/shared/keyguard/shared/model/KeyguardQuickAffordanceSlots.kt
rename to packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/shared/model/KeyguardQuickAffordanceSlots.kt
diff --git a/packages/SystemUI/src/com/android/systemui/backup/BackupHelper.kt b/packages/SystemUI/src/com/android/systemui/backup/BackupHelper.kt
index 5616a00..621b99d 100644
--- a/packages/SystemUI/src/com/android/systemui/backup/BackupHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/backup/BackupHelper.kt
@@ -29,13 +29,15 @@
import android.util.Log
import com.android.systemui.controls.controller.AuxiliaryPersistenceWrapper
import com.android.systemui.controls.controller.ControlsFavoritePersistenceWrapper
+import com.android.systemui.keyguard.domain.backup.KeyguardQuickAffordanceBackupHelper
import com.android.systemui.people.widget.PeopleBackupHelper
/**
* Helper for backing up elements in SystemUI
*
- * This helper is invoked by BackupManager whenever a backup or restore is required in SystemUI.
- * The helper can be used to back up any element that is stored in [Context.getFilesDir].
+ * This helper is invoked by BackupManager whenever a backup or restore is required in SystemUI. The
+ * helper can be used to back up any element that is stored in [Context.getFilesDir] or
+ * [Context.getSharedPreferences].
*
* After restoring is done, a [ACTION_RESTORE_FINISHED] intent will be send to SystemUI user 0,
* indicating that restoring is finished for a given user.
@@ -47,9 +49,11 @@
internal const val CONTROLS = ControlsFavoritePersistenceWrapper.FILE_NAME
private const val NO_OVERWRITE_FILES_BACKUP_KEY = "systemui.files_no_overwrite"
private const val PEOPLE_TILES_BACKUP_KEY = "systemui.people.shared_preferences"
+ private const val KEYGUARD_QUICK_AFFORDANCES_BACKUP_KEY =
+ "systemui.keyguard.quickaffordance.shared_preferences"
val controlsDataLock = Any()
const val ACTION_RESTORE_FINISHED = "com.android.systemui.backup.RESTORE_FINISHED"
- private const val PERMISSION_SELF = "com.android.systemui.permission.SELF"
+ const val PERMISSION_SELF = "com.android.systemui.permission.SELF"
}
override fun onCreate(userHandle: UserHandle, operationType: Int) {
@@ -67,17 +71,27 @@
}
val keys = PeopleBackupHelper.getFilesToBackup()
- addHelper(PEOPLE_TILES_BACKUP_KEY, PeopleBackupHelper(
- this, userHandle, keys.toTypedArray()))
+ addHelper(
+ PEOPLE_TILES_BACKUP_KEY,
+ PeopleBackupHelper(this, userHandle, keys.toTypedArray())
+ )
+ addHelper(
+ KEYGUARD_QUICK_AFFORDANCES_BACKUP_KEY,
+ KeyguardQuickAffordanceBackupHelper(
+ context = this,
+ userId = userHandle.identifier,
+ ),
+ )
}
override fun onRestoreFinished() {
super.onRestoreFinished()
- val intent = Intent(ACTION_RESTORE_FINISHED).apply {
- `package` = packageName
- putExtra(Intent.EXTRA_USER_ID, userId)
- flags = Intent.FLAG_RECEIVER_REGISTERED_ONLY
- }
+ val intent =
+ Intent(ACTION_RESTORE_FINISHED).apply {
+ `package` = packageName
+ putExtra(Intent.EXTRA_USER_ID, userId)
+ flags = Intent.FLAG_RECEIVER_REGISTERED_ONLY
+ }
sendBroadcastAsUser(intent, UserHandle.SYSTEM, PERMISSION_SELF)
}
@@ -90,7 +104,9 @@
* @property lock a lock to hold while backing up and restoring the files.
* @property context the context of the [BackupAgent]
* @property fileNamesAndPostProcess a map from the filenames to back up and the post processing
+ * ```
* actions to take
+ * ```
*/
private class NoOverwriteFileBackupHelper(
val lock: Any,
@@ -115,23 +131,23 @@
data: BackupDataOutput?,
newState: ParcelFileDescriptor?
) {
- synchronized(lock) {
- super.performBackup(oldState, data, newState)
- }
+ synchronized(lock) { super.performBackup(oldState, data, newState) }
}
}
}
+
private fun getPPControlsFile(context: Context): () -> Unit {
return {
val filesDir = context.filesDir
val file = Environment.buildPath(filesDir, BackupHelper.CONTROLS)
if (file.exists()) {
- val dest = Environment.buildPath(filesDir,
- AuxiliaryPersistenceWrapper.AUXILIARY_FILE_NAME)
+ val dest =
+ Environment.buildPath(filesDir, AuxiliaryPersistenceWrapper.AUXILIARY_FILE_NAME)
file.copyTo(dest)
val jobScheduler = context.getSystemService(JobScheduler::class.java)
jobScheduler?.schedule(
- AuxiliaryPersistenceWrapper.DeletionJobService.getJobForContext(context))
+ AuxiliaryPersistenceWrapper.DeletionJobService.getJobForContext(context)
+ )
}
}
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt b/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt
index 537cbc5..a0a892d 100644
--- a/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt
+++ b/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt
@@ -64,8 +64,9 @@
* from SystemUI. That way the number of calls to [BroadcastReceiver.onReceive] can be reduced for
* a given broadcast.
*
- * Use only for IntentFilters with actions and optionally categories. It does not support,
- * permissions, schemes, data types, data authorities or priority different than 0.
+ * Use only for IntentFilters with actions and optionally categories. It does not support schemes,
+ * data types, data authorities or priority different than 0.
+ *
* Cannot be used for getting sticky broadcasts (either as return of registering or as re-delivery).
* Broadcast handling may be asynchronous *without* calling goAsync(), as it's running within sysui
* and doesn't need to worry about being killed.
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index aa6c619..2d558ad 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -346,6 +346,12 @@
// TODO(b/256873975): Tracking Bug
@JvmField @Keep val WM_BUBBLE_BAR = unreleasedFlag(1111, "wm_bubble_bar")
+ // TODO(b/260271148): Tracking bug
+ @Keep
+ @JvmField
+ val WM_DESKTOP_WINDOWING_2 =
+ sysPropBooleanFlag(1112, "persist.wm.debug.desktop_mode_2", default = false)
+
// 1200 - predictive back
@Keep
@JvmField
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProvider.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProvider.kt
index 29febb6..4ae37c5 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProvider.kt
@@ -29,7 +29,7 @@
import com.android.systemui.SystemUIAppComponentFactoryBase
import com.android.systemui.SystemUIAppComponentFactoryBase.ContextAvailableCallback
import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor
-import com.android.systemui.shared.keyguard.data.content.KeyguardQuickAffordanceProviderContract as Contract
+import com.android.systemui.shared.quickaffordance.data.content.KeyguardQuickAffordanceProviderContract as Contract
import javax.inject.Inject
import kotlinx.coroutines.runBlocking
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfig.kt
index 3c09aab..dbc376e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfig.kt
@@ -26,14 +26,17 @@
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import dagger.Lazy
+import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flowOf
-import javax.inject.Inject
@SysUISingleton
-class CameraQuickAffordanceConfig @Inject constructor(
- @Application private val context: Context,
- private val cameraGestureHelper: CameraGestureHelper,
+class CameraQuickAffordanceConfig
+@Inject
+constructor(
+ @Application private val context: Context,
+ private val cameraGestureHelper: Lazy<CameraGestureHelper>,
) : KeyguardQuickAffordanceConfig {
override val key: String
@@ -46,17 +49,23 @@
get() = com.android.internal.R.drawable.perm_group_camera
override val lockScreenState: Flow<KeyguardQuickAffordanceConfig.LockScreenState>
- get() = flowOf(
- KeyguardQuickAffordanceConfig.LockScreenState.Visible(
- icon = Icon.Resource(
+ get() =
+ flowOf(
+ KeyguardQuickAffordanceConfig.LockScreenState.Visible(
+ icon =
+ Icon.Resource(
com.android.internal.R.drawable.perm_group_camera,
ContentDescription.Resource(R.string.accessibility_camera_button)
- )
+ )
+ )
)
- )
- override fun onTriggered(expandable: Expandable?): KeyguardQuickAffordanceConfig.OnTriggeredResult {
- cameraGestureHelper.launchCamera(StatusBarManager.CAMERA_LAUNCH_SOURCE_QUICK_AFFORDANCE)
+ override fun onTriggered(
+ expandable: Expandable?
+ ): KeyguardQuickAffordanceConfig.OnTriggeredResult {
+ cameraGestureHelper
+ .get()
+ .launchCamera(StatusBarManager.CAMERA_LAUNCH_SOURCE_QUICK_AFFORDANCE)
return KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled
}
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfig.kt
index 4477310..98b1a73 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfig.kt
@@ -21,7 +21,7 @@
import com.android.systemui.animation.Expandable
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.keyguard.shared.quickaffordance.ActivationState
-import com.android.systemui.shared.keyguard.data.content.KeyguardQuickAffordanceProviderContract as Contract
+import com.android.systemui.shared.quickaffordance.data.content.KeyguardQuickAffordanceProviderContract as Contract
import kotlinx.coroutines.flow.Flow
/** Defines interface that can act as data source for a single quick affordance model. */
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceSelectionManager.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceSelectionManager.kt
index b29cf45..4f37e5f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceSelectionManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceSelectionManager.kt
@@ -18,9 +18,11 @@
package com.android.systemui.keyguard.data.quickaffordance
import android.content.Context
+import android.content.IntentFilter
import android.content.SharedPreferences
-import androidx.annotation.VisibleForTesting
import com.android.systemui.R
+import com.android.systemui.backup.BackupHelper
+import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
@@ -28,14 +30,18 @@
import com.android.systemui.settings.UserFileManager
import com.android.systemui.settings.UserTracker
import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.onStart
/**
* Manages and provides access to the current "selections" of keyguard quick affordances, answering
* the question "which affordances should the keyguard show?".
*/
+@OptIn(ExperimentalCoroutinesApi::class)
@SysUISingleton
class KeyguardQuickAffordanceSelectionManager
@Inject
@@ -43,15 +49,10 @@
@Application context: Context,
private val userFileManager: UserFileManager,
private val userTracker: UserTracker,
+ broadcastDispatcher: BroadcastDispatcher,
) {
- private val sharedPrefs: SharedPreferences
- get() =
- userFileManager.getSharedPreferences(
- FILE_NAME,
- Context.MODE_PRIVATE,
- userTracker.userId,
- )
+ private var sharedPrefs: SharedPreferences = instantiateSharedPrefs()
private val userId: Flow<Int> = conflatedCallbackFlow {
val callback =
@@ -78,21 +79,54 @@
}
}
+ /**
+ * Emits an event each time a Backup & Restore restoration job is completed. Does not emit an
+ * initial value.
+ */
+ private val backupRestorationEvents: Flow<Unit> =
+ broadcastDispatcher.broadcastFlow(
+ filter = IntentFilter(BackupHelper.ACTION_RESTORE_FINISHED),
+ flags = Context.RECEIVER_NOT_EXPORTED,
+ permission = BackupHelper.PERMISSION_SELF,
+ )
+
/** IDs of affordances to show, indexed by slot ID, and sorted in descending priority order. */
val selections: Flow<Map<String, List<String>>> =
- userId.flatMapLatest {
- conflatedCallbackFlow {
- val listener =
- SharedPreferences.OnSharedPreferenceChangeListener { _, _ ->
- trySend(getSelections())
- }
-
- sharedPrefs.registerOnSharedPreferenceChangeListener(listener)
- send(getSelections())
-
- awaitClose { sharedPrefs.unregisterOnSharedPreferenceChangeListener(listener) }
+ combine(
+ userId,
+ backupRestorationEvents.onStart {
+ // We emit an initial event to make sure that the combine emits at least once,
+ // even
+ // if we never get a Backup & Restore restoration event (which is the most
+ // common
+ // case anyway as restoration really only happens on initial device setup).
+ emit(Unit)
+ }
+ ) { _, _ ->
}
- }
+ .flatMapLatest {
+ conflatedCallbackFlow {
+ // We want to instantiate a new SharedPreferences instance each time either the
+ // user
+ // ID changes or we have a backup & restore restoration event. The reason is
+ // that
+ // our sharedPrefs instance needs to be replaced with a new one as it depends on
+ // the
+ // user ID and when the B&R job completes, the backing file is replaced but the
+ // existing instance still has a stale in-memory cache.
+ sharedPrefs = instantiateSharedPrefs()
+
+ val listener =
+ SharedPreferences.OnSharedPreferenceChangeListener { _, _ ->
+ trySend(getSelections())
+ }
+
+ sharedPrefs.registerOnSharedPreferenceChangeListener(listener)
+ send(getSelections())
+
+ awaitClose { sharedPrefs.unregisterOnSharedPreferenceChangeListener(listener) }
+ }
+ }
/**
* Returns a snapshot of the IDs of affordances to show, indexed by slot ID, and sorted in
@@ -144,9 +178,17 @@
sharedPrefs.edit().putString(key, value).apply()
}
+ private fun instantiateSharedPrefs(): SharedPreferences {
+ return userFileManager.getSharedPreferences(
+ FILE_NAME,
+ Context.MODE_PRIVATE,
+ userTracker.userId,
+ )
+ }
+
companion object {
private const val TAG = "KeyguardQuickAffordanceSelectionManager"
- @VisibleForTesting const val FILE_NAME = "quick_affordance_selections"
+ const val FILE_NAME = "quick_affordance_selections"
private const val KEY_PREFIX_SLOT = "slot_"
private const val SLOT_AFFORDANCES_DELIMITER = ":"
private const val AFFORDANCE_DELIMITER = ","
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/backup/KeyguardQuickAffordanceBackupHelper.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/backup/KeyguardQuickAffordanceBackupHelper.kt
new file mode 100644
index 0000000..0e865ce
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/backup/KeyguardQuickAffordanceBackupHelper.kt
@@ -0,0 +1,44 @@
+/*
+ * 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.keyguard.domain.backup
+
+import android.app.backup.SharedPreferencesBackupHelper
+import android.content.Context
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceSelectionManager
+import com.android.systemui.settings.UserFileManagerImpl
+
+/** Handles backup & restore for keyguard quick affordances. */
+class KeyguardQuickAffordanceBackupHelper(
+ context: Context,
+ userId: Int,
+) :
+ SharedPreferencesBackupHelper(
+ context,
+ if (UserFileManagerImpl.isPrimaryUser(userId)) {
+ KeyguardQuickAffordanceSelectionManager.FILE_NAME
+ } else {
+ UserFileManagerImpl.secondaryUserFile(
+ context = context,
+ fileName = KeyguardQuickAffordanceSelectionManager.FILE_NAME,
+ directoryName = UserFileManagerImpl.SHARED_PREFS,
+ userId = userId,
+ )
+ .also { UserFileManagerImpl.ensureParentDirExists(it) }
+ .toString()
+ }
+ )
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
index 2d94d76..ee7154f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
@@ -34,8 +34,8 @@
import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.settings.UserTracker
-import com.android.systemui.shared.keyguard.data.content.KeyguardQuickAffordanceProviderContract
import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
+import com.android.systemui.shared.quickaffordance.data.content.KeyguardQuickAffordanceProviderContract
import com.android.systemui.statusbar.policy.KeyguardStateController
import dagger.Lazy
import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/settings/UserFileManagerImpl.kt b/packages/SystemUI/src/com/android/systemui/settings/UserFileManagerImpl.kt
index d450afa..bfba6df 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/UserFileManagerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/settings/UserFileManagerImpl.kt
@@ -35,12 +35,14 @@
import javax.inject.Inject
/**
- * Implementation for retrieving file paths for file storage of system and secondary users.
- * Files lie in {File Directory}/UserFileManager/{User Id} for secondary user.
- * For system user, we use the conventional {File Directory}
+ * Implementation for retrieving file paths for file storage of system and secondary users. Files
+ * lie in {File Directory}/UserFileManager/{User Id} for secondary user. For system user, we use the
+ * conventional {File Directory}
*/
@SysUISingleton
-class UserFileManagerImpl @Inject constructor(
+class UserFileManagerImpl
+@Inject
+constructor(
// Context of system process and system user.
private val context: Context,
val userManager: UserManager,
@@ -49,80 +51,114 @@
) : UserFileManager, CoreStartable {
companion object {
private const val FILES = "files"
- @VisibleForTesting internal const val SHARED_PREFS = "shared_prefs"
+ const val SHARED_PREFS = "shared_prefs"
@VisibleForTesting internal const val ID = "UserFileManager"
- }
- private val broadcastReceiver = object : BroadcastReceiver() {
+ /** Returns `true` if the given user ID is that for the primary/system user. */
+ fun isPrimaryUser(userId: Int): Boolean {
+ return UserHandle(userId).isSystem
+ }
+
/**
- * Listen to Intent.ACTION_USER_REMOVED to clear user data.
+ * Returns a [File] pointing to the correct path for a secondary user ID.
+ *
+ * Note that there is no check for the type of user. This should only be called for
+ * secondary users, never for the system user. For that, make sure to call [isPrimaryUser].
+ *
+ * Note also that there is no guarantee that the parent directory structure for the file
+ * exists on disk. For that, call [ensureParentDirExists].
+ *
+ * @param context The context
+ * @param fileName The name of the file
+ * @param directoryName The name of the directory that would contain the file
+ * @param userId The ID of the user to build a file path for
*/
- override fun onReceive(context: Context, intent: Intent) {
- if (intent.action == Intent.ACTION_USER_REMOVED) {
- clearDeletedUserData()
+ fun secondaryUserFile(
+ context: Context,
+ fileName: String,
+ directoryName: String,
+ userId: Int,
+ ): File {
+ return Environment.buildPath(
+ context.filesDir,
+ ID,
+ userId.toString(),
+ directoryName,
+ fileName,
+ )
+ }
+
+ /**
+ * Checks to see if parent dir of the file exists. If it does not, we create the parent dirs
+ * recursively.
+ */
+ fun ensureParentDirExists(file: File) {
+ val parent = file.parentFile
+ if (!parent.exists()) {
+ if (!parent.mkdirs()) {
+ Log.e(ID, "Could not create parent directory for file: ${file.absolutePath}")
+ }
}
}
}
- /**
- * Poll for user-specific directories to delete upon start up.
- */
+ private val broadcastReceiver =
+ object : BroadcastReceiver() {
+ /** Listen to Intent.ACTION_USER_REMOVED to clear user data. */
+ override fun onReceive(context: Context, intent: Intent) {
+ if (intent.action == Intent.ACTION_USER_REMOVED) {
+ clearDeletedUserData()
+ }
+ }
+ }
+
+ /** Poll for user-specific directories to delete upon start up. */
override fun start() {
clearDeletedUserData()
- val filter = IntentFilter().apply {
- addAction(Intent.ACTION_USER_REMOVED)
- }
+ val filter = IntentFilter().apply { addAction(Intent.ACTION_USER_REMOVED) }
broadcastDispatcher.registerReceiver(broadcastReceiver, filter, backgroundExecutor)
}
- /**
- * Return the file based on current user.
- */
+ /** Return the file based on current user. */
override fun getFile(fileName: String, userId: Int): File {
- return if (UserHandle(userId).isSystem) {
- Environment.buildPath(
- context.filesDir,
- fileName
- )
+ return if (isPrimaryUser(userId)) {
+ Environment.buildPath(context.filesDir, fileName)
} else {
- val secondaryFile = Environment.buildPath(
- context.filesDir,
- ID,
- userId.toString(),
- FILES,
- fileName
- )
+ val secondaryFile =
+ secondaryUserFile(
+ context = context,
+ userId = userId,
+ directoryName = FILES,
+ fileName = fileName,
+ )
ensureParentDirExists(secondaryFile)
secondaryFile
}
}
- /**
- * Get shared preferences from user.
- */
+ /** Get shared preferences from user. */
override fun getSharedPreferences(
fileName: String,
@Context.PreferencesMode mode: Int,
userId: Int
): SharedPreferences {
- if (UserHandle(userId).isSystem) {
+ if (isPrimaryUser(userId)) {
return context.getSharedPreferences(fileName, mode)
}
- val secondaryUserDir = Environment.buildPath(
- context.filesDir,
- ID,
- userId.toString(),
- SHARED_PREFS,
- fileName
- )
+
+ val secondaryUserDir =
+ secondaryUserFile(
+ context = context,
+ fileName = fileName,
+ directoryName = SHARED_PREFS,
+ userId = userId,
+ )
ensureParentDirExists(secondaryUserDir)
return context.getSharedPreferences(secondaryUserDir, mode)
}
- /**
- * Remove dirs for deleted users.
- */
+ /** Remove dirs for deleted users. */
@VisibleForTesting
internal fun clearDeletedUserData() {
backgroundExecutor.execute {
@@ -133,10 +169,11 @@
dirsToDelete.forEach { dir ->
try {
- val dirToDelete = Environment.buildPath(
- file,
- dir,
- )
+ val dirToDelete =
+ Environment.buildPath(
+ file,
+ dir,
+ )
dirToDelete.deleteRecursively()
} catch (e: Exception) {
Log.e(ID, "Deletion failed.", e)
@@ -144,18 +181,4 @@
}
}
}
-
- /**
- * Checks to see if parent dir of the file exists. If it does not, we create the parent dirs
- * recursively.
- */
- @VisibleForTesting
- internal fun ensureParentDirExists(file: File) {
- val parent = file.parentFile
- if (!parent.exists()) {
- if (!parent.mkdirs()) {
- Log.e(ID, "Could not create parent directory for file: ${file.absolutePath}")
- }
- }
- }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProviderTest.kt
index cedde58..32c5b3f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProviderTest.kt
@@ -36,8 +36,8 @@
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.settings.UserFileManager
import com.android.systemui.settings.UserTracker
-import com.android.systemui.shared.keyguard.data.content.KeyguardQuickAffordanceProviderContract as Contract
import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
+import com.android.systemui.shared.quickaffordance.data.content.KeyguardQuickAffordanceProviderContract as Contract
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.FakeSharedPreferences
import com.android.systemui.util.mockito.mock
@@ -89,6 +89,7 @@
.thenReturn(FakeSharedPreferences())
},
userTracker = userTracker,
+ broadcastDispatcher = fakeBroadcastDispatcher,
)
val quickAffordanceRepository =
KeyguardQuickAffordanceRepository(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfigTest.kt
index 623becf..7205f30 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfigTest.kt
@@ -37,25 +37,29 @@
@Mock private lateinit var cameraGestureHelper: CameraGestureHelper
@Mock private lateinit var context: Context
+
private lateinit var underTest: CameraQuickAffordanceConfig
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
- underTest = CameraQuickAffordanceConfig(
+
+ underTest =
+ CameraQuickAffordanceConfig(
context,
- cameraGestureHelper,
- )
+ ) {
+ cameraGestureHelper
+ }
}
@Test
fun `affordance triggered -- camera launch called`() {
- //when
+ // When
val result = underTest.onTriggered(null)
- //then
+ // Then
verify(cameraGestureHelper)
- .launchCamera(StatusBarManager.CAMERA_LAUNCH_SOURCE_QUICK_AFFORDANCE)
+ .launchCamera(StatusBarManager.CAMERA_LAUNCH_SOURCE_QUICK_AFFORDANCE)
assertEquals(KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled, result)
}
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncerTest.kt
index 8ef921e..552b8cb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncerTest.kt
@@ -89,6 +89,7 @@
.thenReturn(FakeSharedPreferences())
},
userTracker = FakeUserTracker(),
+ broadcastDispatcher = fakeBroadcastDispatcher,
)
settings = FakeSettings()
settings.putInt(Settings.Secure.LOCKSCREEN_SHOW_CONTROLS, 0)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceSelectionManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceSelectionManagerTest.kt
index d8ee9f1..6a2376b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceSelectionManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceSelectionManagerTest.kt
@@ -17,6 +17,7 @@
package com.android.systemui.keyguard.data.quickaffordance
+import android.content.Intent
import android.content.SharedPreferences
import android.content.pm.UserInfo
import androidx.test.filters.SmallTest
@@ -27,10 +28,15 @@
import com.android.systemui.util.FakeSharedPreferences
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.toList
import kotlinx.coroutines.launch
import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.resetMain
import kotlinx.coroutines.test.runTest
+import kotlinx.coroutines.test.setMain
+import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -38,8 +44,12 @@
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.ArgumentMatchers.anyString
import org.mockito.Mock
+import org.mockito.Mockito.atLeastOnce
+import org.mockito.Mockito.clearInvocations
+import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
+@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(JUnit4::class)
class KeyguardQuickAffordanceSelectionManagerTest : SysuiTestCase() {
@@ -60,15 +70,23 @@
sharedPrefs.getOrPut(userId) { FakeSharedPreferences() }
}
userTracker = FakeUserTracker()
+ val dispatcher = UnconfinedTestDispatcher()
+ Dispatchers.setMain(dispatcher)
underTest =
KeyguardQuickAffordanceSelectionManager(
context = context,
userFileManager = userFileManager,
userTracker = userTracker,
+ broadcastDispatcher = fakeBroadcastDispatcher,
)
}
+ @After
+ fun tearDown() {
+ Dispatchers.resetMain()
+ }
+
@Test
fun setSelections() = runTest {
overrideResource(R.array.config_keyguardQuickAffordanceDefaults, arrayOf<String>())
@@ -318,6 +336,22 @@
job.cancel()
}
+ @Test
+ fun `responds to backup and restore by reloading the selections from disk`() = runTest {
+ overrideResource(R.array.config_keyguardQuickAffordanceDefaults, arrayOf<String>())
+ val affordanceIdsBySlotId = mutableListOf<Map<String, List<String>>>()
+ val job =
+ launch(UnconfinedTestDispatcher()) {
+ underTest.selections.toList(affordanceIdsBySlotId)
+ }
+ clearInvocations(userFileManager)
+
+ fakeBroadcastDispatcher.registeredReceivers.firstOrNull()?.onReceive(context, Intent())
+
+ verify(userFileManager, atLeastOnce()).getSharedPreferences(anyString(), anyInt(), anyInt())
+ job.cancel()
+ }
+
private fun assertSelections(
observed: Map<String, List<String>>?,
expected: Map<String, List<String>>,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt
index 5c75417..652fae9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt
@@ -76,6 +76,7 @@
.thenReturn(FakeSharedPreferences())
},
userTracker = FakeUserTracker(),
+ broadcastDispatcher = fakeBroadcastDispatcher,
)
underTest =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
index c2650ec..ba7c40b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
@@ -252,6 +252,7 @@
.thenReturn(FakeSharedPreferences())
},
userTracker = userTracker,
+ broadcastDispatcher = fakeBroadcastDispatcher,
)
val quickAffordanceRepository =
KeyguardQuickAffordanceRepository(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
index b790306..8d0c4ef 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
@@ -113,6 +113,7 @@
.thenReturn(FakeSharedPreferences())
},
userTracker = userTracker,
+ broadcastDispatcher = fakeBroadcastDispatcher,
)
val quickAffordanceRepository =
KeyguardQuickAffordanceRepository(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
index 8b166bd..32849cd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
@@ -136,6 +136,7 @@
.thenReturn(FakeSharedPreferences())
},
userTracker = userTracker,
+ broadcastDispatcher = fakeBroadcastDispatcher,
)
val quickAffordanceRepository =
KeyguardQuickAffordanceRepository(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/settings/UserFileManagerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/settings/UserFileManagerImplTest.kt
index 6d9b01e..020a866 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/settings/UserFileManagerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/settings/UserFileManagerImplTest.kt
@@ -50,24 +50,20 @@
lateinit var userFileManager: UserFileManagerImpl
lateinit var backgroundExecutor: FakeExecutor
- @Mock
- lateinit var userManager: UserManager
- @Mock
- lateinit var broadcastDispatcher: BroadcastDispatcher
+ @Mock lateinit var userManager: UserManager
+ @Mock lateinit var broadcastDispatcher: BroadcastDispatcher
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
backgroundExecutor = FakeExecutor(FakeSystemClock())
- userFileManager = UserFileManagerImpl(context, userManager,
- broadcastDispatcher, backgroundExecutor)
+ userFileManager =
+ UserFileManagerImpl(context, userManager, broadcastDispatcher, backgroundExecutor)
}
@After
fun end() {
- val dir = Environment.buildPath(
- context.filesDir,
- UserFileManagerImpl.ID)
+ val dir = Environment.buildPath(context.filesDir, UserFileManagerImpl.ID)
dir.deleteRecursively()
}
@@ -82,13 +78,14 @@
@Test
fun testGetSharedPreferences() {
val secondarySharedPref = userFileManager.getSharedPreferences(TEST_FILE_NAME, 0, 11)
- val secondaryUserDir = Environment.buildPath(
- context.filesDir,
- UserFileManagerImpl.ID,
- "11",
- UserFileManagerImpl.SHARED_PREFS,
- TEST_FILE_NAME
- )
+ val secondaryUserDir =
+ Environment.buildPath(
+ context.filesDir,
+ UserFileManagerImpl.ID,
+ "11",
+ UserFileManagerImpl.SHARED_PREFS,
+ TEST_FILE_NAME
+ )
assertThat(secondarySharedPref).isNotNull()
assertThat(secondaryUserDir.exists())
@@ -101,32 +98,35 @@
val userFileManager = spy(userFileManager)
userFileManager.start()
verify(userFileManager).clearDeletedUserData()
- verify(broadcastDispatcher).registerReceiver(any(BroadcastReceiver::class.java),
- any(IntentFilter::class.java),
- any(Executor::class.java), isNull(), eq(Context.RECEIVER_EXPORTED), isNull())
+ verify(broadcastDispatcher)
+ .registerReceiver(
+ any(BroadcastReceiver::class.java),
+ any(IntentFilter::class.java),
+ any(Executor::class.java),
+ isNull(),
+ eq(Context.RECEIVER_EXPORTED),
+ isNull()
+ )
}
@Test
fun testClearDeletedUserData() {
- val dir = Environment.buildPath(
- context.filesDir,
- UserFileManagerImpl.ID,
- "11",
- "files"
- )
+ val dir = Environment.buildPath(context.filesDir, UserFileManagerImpl.ID, "11", "files")
dir.mkdirs()
- val file = Environment.buildPath(
- context.filesDir,
- UserFileManagerImpl.ID,
- "11",
- "files",
- TEST_FILE_NAME
- )
- val secondaryUserDir = Environment.buildPath(
- context.filesDir,
- UserFileManagerImpl.ID,
- "11",
- )
+ val file =
+ Environment.buildPath(
+ context.filesDir,
+ UserFileManagerImpl.ID,
+ "11",
+ "files",
+ TEST_FILE_NAME
+ )
+ val secondaryUserDir =
+ Environment.buildPath(
+ context.filesDir,
+ UserFileManagerImpl.ID,
+ "11",
+ )
file.createNewFile()
assertThat(secondaryUserDir.exists()).isTrue()
assertThat(file.exists()).isTrue()
@@ -139,15 +139,16 @@
@Test
fun testEnsureParentDirExists() {
- val file = Environment.buildPath(
- context.filesDir,
- UserFileManagerImpl.ID,
- "11",
- "files",
- TEST_FILE_NAME
- )
+ val file =
+ Environment.buildPath(
+ context.filesDir,
+ UserFileManagerImpl.ID,
+ "11",
+ "files",
+ TEST_FILE_NAME
+ )
assertThat(file.parentFile.exists()).isFalse()
- userFileManager.ensureParentDirExists(file)
+ UserFileManagerImpl.ensureParentDirExists(file)
assertThat(file.parentFile.exists()).isTrue()
}
}
diff --git a/services/core/java/com/android/server/DockObserver.java b/services/core/java/com/android/server/DockObserver.java
index 540ed4c..3487613 100644
--- a/services/core/java/com/android/server/DockObserver.java
+++ b/services/core/java/com/android/server/DockObserver.java
@@ -19,6 +19,7 @@
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
+import android.database.ContentObserver;
import android.media.AudioManager;
import android.media.Ringtone;
import android.media.RingtoneManager;
@@ -73,6 +74,7 @@
private final boolean mAllowTheaterModeWakeFromDock;
private final List<ExtconStateConfig> mExtconStateConfigs;
+ private DeviceProvisionedObserver mDeviceProvisionedObserver;
static final class ExtconStateProvider {
private final Map<String, String> mState;
@@ -110,7 +112,7 @@
Slog.w(TAG, "No state file found at: " + stateFilePath);
return new ExtconStateProvider(new HashMap<>());
} catch (Exception e) {
- Slog.e(TAG, "" , e);
+ Slog.e(TAG, "", e);
return new ExtconStateProvider(new HashMap<>());
}
}
@@ -136,7 +138,7 @@
private static List<ExtconStateConfig> loadExtconStateConfigs(Context context) {
String[] rows = context.getResources().getStringArray(
- com.android.internal.R.array.config_dockExtconStateMapping);
+ com.android.internal.R.array.config_dockExtconStateMapping);
try {
ArrayList<ExtconStateConfig> configs = new ArrayList<>();
for (String row : rows) {
@@ -167,6 +169,7 @@
com.android.internal.R.bool.config_allowTheaterModeWakeFromDock);
mKeepDreamingWhenUndocking = context.getResources().getBoolean(
com.android.internal.R.bool.config_keepDreamingWhenUndocking);
+ mDeviceProvisionedObserver = new DeviceProvisionedObserver(mHandler);
mExtconStateConfigs = loadExtconStateConfigs(context);
@@ -199,15 +202,19 @@
if (phase == PHASE_ACTIVITY_MANAGER_READY) {
synchronized (mLock) {
mSystemReady = true;
-
- // don't bother broadcasting undocked here
- if (mReportedDockState != Intent.EXTRA_DOCK_STATE_UNDOCKED) {
- updateLocked();
- }
+ mDeviceProvisionedObserver.onSystemReady();
+ updateIfDockedLocked();
}
}
}
+ private void updateIfDockedLocked() {
+ // don't bother broadcasting undocked here
+ if (mReportedDockState != Intent.EXTRA_DOCK_STATE_UNDOCKED) {
+ updateLocked();
+ }
+ }
+
private void setActualDockStateLocked(int newState) {
mActualDockState = newState;
if (!mUpdatesStopped) {
@@ -252,8 +259,7 @@
// Skip the dock intent if not yet provisioned.
final ContentResolver cr = getContext().getContentResolver();
- if (Settings.Global.getInt(cr,
- Settings.Global.DEVICE_PROVISIONED, 0) == 0) {
+ if (!mDeviceProvisionedObserver.isDeviceProvisioned()) {
Slog.i(TAG, "Device not provisioned, skipping dock broadcast");
return;
}
@@ -419,4 +425,48 @@
}
}
}
+
+ private final class DeviceProvisionedObserver extends ContentObserver {
+ private boolean mRegistered;
+
+ public DeviceProvisionedObserver(Handler handler) {
+ super(handler);
+ }
+
+ @Override
+ public void onChange(boolean selfChange, Uri uri) {
+ synchronized (mLock) {
+ updateRegistration();
+ if (isDeviceProvisioned()) {
+ // Send the dock broadcast if device is docked after provisioning.
+ updateIfDockedLocked();
+ }
+ }
+ }
+
+ void onSystemReady() {
+ updateRegistration();
+ }
+
+ private void updateRegistration() {
+ boolean register = !isDeviceProvisioned();
+ if (register == mRegistered) {
+ return;
+ }
+ final ContentResolver resolver = getContext().getContentResolver();
+ if (register) {
+ resolver.registerContentObserver(
+ Settings.Global.getUriFor(Settings.Global.DEVICE_PROVISIONED),
+ false, this);
+ } else {
+ resolver.unregisterContentObserver(this);
+ }
+ mRegistered = register;
+ }
+
+ boolean isDeviceProvisioned() {
+ return Settings.Global.getInt(getContext().getContentResolver(),
+ Settings.Global.DEVICE_PROVISIONED, 0) != 0;
+ }
+ }
}
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index ca86021c..bd90d85 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -52,8 +52,8 @@
import android.telephony.Annotation.RadioPowerState;
import android.telephony.Annotation.SrvccState;
import android.telephony.BarringInfo;
-import android.telephony.CallAttributes;
import android.telephony.CallQuality;
+import android.telephony.CallState;
import android.telephony.CellIdentity;
import android.telephony.CellInfo;
import android.telephony.CellSignalStrength;
@@ -82,6 +82,7 @@
import android.telephony.TelephonyManager;
import android.telephony.data.ApnSetting;
import android.telephony.emergency.EmergencyNumber;
+import android.telephony.ims.ImsCallSession;
import android.telephony.ims.ImsReasonInfo;
import android.text.TextUtils;
import android.util.ArrayMap;
@@ -349,9 +350,9 @@
private CallQuality[] mCallQuality;
- private CallAttributes[] mCallAttributes;
+ private ArrayList<List<CallState>> mCallStateLists;
- // network type of the call associated with the mCallAttributes and mCallQuality
+ // network type of the call associated with the mCallStateLists and mCallQuality
private int[] mCallNetworkType;
private int[] mSrvccState;
@@ -687,7 +688,6 @@
mCallPreciseDisconnectCause = copyOf(mCallPreciseDisconnectCause, mNumPhones);
mCallQuality = copyOf(mCallQuality, mNumPhones);
mCallNetworkType = copyOf(mCallNetworkType, mNumPhones);
- mCallAttributes = copyOf(mCallAttributes, mNumPhones);
mOutgoingCallEmergencyNumber = copyOf(mOutgoingCallEmergencyNumber, mNumPhones);
mOutgoingSmsEmergencyNumber = copyOf(mOutgoingSmsEmergencyNumber, mNumPhones);
mTelephonyDisplayInfos = copyOf(mTelephonyDisplayInfos, mNumPhones);
@@ -707,6 +707,7 @@
cutListToSize(mLinkCapacityEstimateLists, mNumPhones);
cutListToSize(mCarrierPrivilegeStates, mNumPhones);
cutListToSize(mCarrierServiceStates, mNumPhones);
+ cutListToSize(mCallStateLists, mNumPhones);
return;
}
@@ -730,8 +731,7 @@
mCallDisconnectCause[i] = DisconnectCause.NOT_VALID;
mCallPreciseDisconnectCause[i] = PreciseDisconnectCause.NOT_VALID;
mCallQuality[i] = createCallQuality();
- mCallAttributes[i] = new CallAttributes(createPreciseCallState(),
- TelephonyManager.NETWORK_TYPE_UNKNOWN, createCallQuality());
+ mCallStateLists.add(i, new ArrayList<>());
mCallNetworkType[i] = TelephonyManager.NETWORK_TYPE_UNKNOWN;
mPreciseCallState[i] = createPreciseCallState();
mRingingCallState[i] = PreciseCallState.PRECISE_CALL_STATE_IDLE;
@@ -799,7 +799,7 @@
mCallPreciseDisconnectCause = new int[numPhones];
mCallQuality = new CallQuality[numPhones];
mCallNetworkType = new int[numPhones];
- mCallAttributes = new CallAttributes[numPhones];
+ mCallStateLists = new ArrayList<>();
mPreciseDataConnectionStates = new ArrayList<>();
mCellInfo = new ArrayList<>(numPhones);
mImsReasonInfo = new ArrayList<>();
@@ -837,8 +837,7 @@
mCallDisconnectCause[i] = DisconnectCause.NOT_VALID;
mCallPreciseDisconnectCause[i] = PreciseDisconnectCause.NOT_VALID;
mCallQuality[i] = createCallQuality();
- mCallAttributes[i] = new CallAttributes(createPreciseCallState(),
- TelephonyManager.NETWORK_TYPE_UNKNOWN, createCallQuality());
+ mCallStateLists.add(i, new ArrayList<>());
mCallNetworkType[i] = TelephonyManager.NETWORK_TYPE_UNKNOWN;
mPreciseCallState[i] = createPreciseCallState();
mRingingCallState[i] = PreciseCallState.PRECISE_CALL_STATE_IDLE;
@@ -1336,7 +1335,7 @@
}
if (events.contains(TelephonyCallback.EVENT_CALL_ATTRIBUTES_CHANGED)) {
try {
- r.callback.onCallAttributesChanged(mCallAttributes[r.phoneId]);
+ r.callback.onCallStatesChanged(mCallStateLists.get(r.phoneId));
} catch (RemoteException ex) {
remove(r.binder);
}
@@ -2171,11 +2170,30 @@
}
}
- public void notifyPreciseCallState(int phoneId, int subId, int ringingCallState,
- int foregroundCallState, int backgroundCallState) {
+ /**
+ * Send a notification to registrants that the precise call state has changed.
+ *
+ * @param phoneId the phoneId carrying the data connection
+ * @param subId the subscriptionId for the data connection
+ * @param callStates Array of PreciseCallState of foreground, background & ringing calls.
+ * @param imsCallIds Array of IMS call session ID{@link ImsCallSession#getCallId()} for
+ * ringing, foreground & background calls.
+ * @param imsServiceTypes Array of IMS call service type for ringing, foreground &
+ * background calls.
+ * @param imsCallTypes Array of IMS call type for ringing, foreground & background calls.
+ */
+ public void notifyPreciseCallState(int phoneId, int subId,
+ @Annotation.PreciseCallStates int[] callStates, String[] imsCallIds,
+ @Annotation.ImsCallServiceType int[] imsServiceTypes,
+ @Annotation.ImsCallType int[] imsCallTypes) {
if (!checkNotifyPermission("notifyPreciseCallState()")) {
return;
}
+
+ int ringingCallState = callStates[CallState.CALL_CLASSIFICATION_RINGING];
+ int foregroundCallState = callStates[CallState.CALL_CLASSIFICATION_FOREGROUND];
+ int backgroundCallState = callStates[CallState.CALL_CLASSIFICATION_BACKGROUND];
+
synchronized (mRecords) {
if (validatePhoneId(phoneId)) {
mRingingCallState[phoneId] = ringingCallState;
@@ -2186,11 +2204,11 @@
backgroundCallState,
DisconnectCause.NOT_VALID,
PreciseDisconnectCause.NOT_VALID);
- boolean notifyCallAttributes = true;
+ boolean notifyCallState = true;
if (mCallQuality == null) {
log("notifyPreciseCallState: mCallQuality is null, "
+ "skipping call attributes");
- notifyCallAttributes = false;
+ notifyCallState = false;
} else {
// If the precise call state is no longer active, reset the call network type
// and call quality.
@@ -2199,8 +2217,65 @@
mCallNetworkType[phoneId] = TelephonyManager.NETWORK_TYPE_UNKNOWN;
mCallQuality[phoneId] = createCallQuality();
}
- mCallAttributes[phoneId] = new CallAttributes(mPreciseCallState[phoneId],
- mCallNetworkType[phoneId], mCallQuality[phoneId]);
+ mCallStateLists.get(phoneId).clear();
+ if (foregroundCallState != PreciseCallState.PRECISE_CALL_STATE_NOT_VALID
+ && foregroundCallState != PreciseCallState.PRECISE_CALL_STATE_IDLE) {
+ CallQuality callQuality = mCallQuality[phoneId];
+ CallState.Builder builder = new CallState.Builder(
+ callStates[CallState.CALL_CLASSIFICATION_FOREGROUND])
+ .setNetworkType(mCallNetworkType[phoneId])
+ .setCallQuality(callQuality)
+ .setCallClassification(
+ CallState.CALL_CLASSIFICATION_FOREGROUND);
+ if (imsCallIds != null && imsServiceTypes != null && imsCallTypes != null) {
+ builder = builder
+ .setImsCallSessionId(imsCallIds[
+ CallState.CALL_CLASSIFICATION_FOREGROUND])
+ .setImsCallServiceType(imsServiceTypes[
+ CallState.CALL_CLASSIFICATION_FOREGROUND])
+ .setImsCallType(imsCallTypes[
+ CallState.CALL_CLASSIFICATION_FOREGROUND]);
+ }
+ mCallStateLists.get(phoneId).add(builder.build());
+ }
+ if (backgroundCallState != PreciseCallState.PRECISE_CALL_STATE_NOT_VALID
+ && backgroundCallState != PreciseCallState.PRECISE_CALL_STATE_IDLE) {
+ CallState.Builder builder = new CallState.Builder(
+ callStates[CallState.CALL_CLASSIFICATION_BACKGROUND])
+ .setNetworkType(mCallNetworkType[phoneId])
+ .setCallQuality(createCallQuality())
+ .setCallClassification(
+ CallState.CALL_CLASSIFICATION_BACKGROUND);
+ if (imsCallIds != null && imsServiceTypes != null && imsCallTypes != null) {
+ builder = builder
+ .setImsCallSessionId(imsCallIds[
+ CallState.CALL_CLASSIFICATION_BACKGROUND])
+ .setImsCallServiceType(imsServiceTypes[
+ CallState.CALL_CLASSIFICATION_BACKGROUND])
+ .setImsCallType(imsCallTypes[
+ CallState.CALL_CLASSIFICATION_BACKGROUND]);
+ }
+ mCallStateLists.get(phoneId).add(builder.build());
+ }
+ if (ringingCallState != PreciseCallState.PRECISE_CALL_STATE_NOT_VALID
+ && ringingCallState != PreciseCallState.PRECISE_CALL_STATE_IDLE) {
+ CallState.Builder builder = new CallState.Builder(
+ callStates[CallState.CALL_CLASSIFICATION_RINGING])
+ .setNetworkType(mCallNetworkType[phoneId])
+ .setCallQuality(createCallQuality())
+ .setCallClassification(
+ CallState.CALL_CLASSIFICATION_RINGING);
+ if (imsCallIds != null && imsServiceTypes != null && imsCallTypes != null) {
+ builder = builder
+ .setImsCallSessionId(imsCallIds[
+ CallState.CALL_CLASSIFICATION_RINGING])
+ .setImsCallServiceType(imsServiceTypes[
+ CallState.CALL_CLASSIFICATION_RINGING])
+ .setImsCallType(imsCallTypes[
+ CallState.CALL_CLASSIFICATION_RINGING]);
+ }
+ mCallStateLists.get(phoneId).add(builder.build());
+ }
}
for (Record r : mRecords) {
@@ -2213,11 +2288,11 @@
mRemoveList.add(r.binder);
}
}
- if (notifyCallAttributes && r.matchTelephonyCallbackEvent(
+ if (notifyCallState && r.matchTelephonyCallbackEvent(
TelephonyCallback.EVENT_CALL_ATTRIBUTES_CHANGED)
&& idMatch(r, subId, phoneId)) {
try {
- r.callback.onCallAttributesChanged(mCallAttributes[phoneId]);
+ r.callback.onCallStatesChanged(mCallStateLists.get(phoneId));
} catch (RemoteException ex) {
mRemoveList.add(r.binder);
}
@@ -2515,15 +2590,29 @@
// merge CallQuality with PreciseCallState and network type
mCallQuality[phoneId] = callQuality;
mCallNetworkType[phoneId] = callNetworkType;
- mCallAttributes[phoneId] = new CallAttributes(mPreciseCallState[phoneId],
- callNetworkType, callQuality);
+ if (mCallStateLists.get(phoneId).size() > 0
+ && mCallStateLists.get(phoneId).get(0).getCallState()
+ == PreciseCallState.PRECISE_CALL_STATE_ACTIVE) {
+ CallState prev = mCallStateLists.get(phoneId).remove(0);
+ mCallStateLists.get(phoneId).add(
+ 0, new CallState.Builder(prev.getCallState())
+ .setNetworkType(callNetworkType)
+ .setCallQuality(callQuality)
+ .setCallClassification(prev.getCallClassification())
+ .setImsCallSessionId(prev.getImsCallSessionId())
+ .setImsCallServiceType(prev.getImsCallServiceType())
+ .setImsCallType(prev.getImsCallType()).build());
+ } else {
+ log("There is no active call to report CallQaulity");
+ return;
+ }
for (Record r : mRecords) {
if (r.matchTelephonyCallbackEvent(
TelephonyCallback.EVENT_CALL_ATTRIBUTES_CHANGED)
&& idMatch(r, subId, phoneId)) {
try {
- r.callback.onCallAttributesChanged(mCallAttributes[phoneId]);
+ r.callback.onCallStatesChanged(mCallStateLists.get(phoneId));
} catch (RemoteException ex) {
mRemoveList.add(r.binder);
}
@@ -2991,7 +3080,6 @@
pw.println("mSrvccState=" + mSrvccState[i]);
pw.println("mCallPreciseDisconnectCause=" + mCallPreciseDisconnectCause[i]);
pw.println("mCallQuality=" + mCallQuality[i]);
- pw.println("mCallAttributes=" + mCallAttributes[i]);
pw.println("mCallNetworkType=" + mCallNetworkType[i]);
pw.println("mPreciseDataConnectionStates=" + mPreciseDataConnectionStates.get(i));
pw.println("mOutgoingCallEmergencyNumber=" + mOutgoingCallEmergencyNumber[i]);
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 35b46c1..50be458 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -87,6 +87,7 @@
import static android.os.Process.isSdkSandboxUid;
import static android.os.Process.isThreadInProcess;
import static android.os.Process.killProcess;
+import static android.os.Process.killProcessGroup;
import static android.os.Process.killProcessQuiet;
import static android.os.Process.myPid;
import static android.os.Process.myUid;
@@ -952,13 +953,6 @@
}
return false;
}
-
- boolean doRemoveIfNoThreadInternal(int pid, ProcessRecord app) {
- if (app == null || app.getThread() != null) {
- return false;
- }
- return doRemoveInternal(pid, app);
- }
}
private final PendingStartActivityUids mPendingStartActivityUids;
@@ -990,7 +984,7 @@
* method.
*/
@GuardedBy("this")
- void removePidLocked(int pid, ProcessRecord app) {
+ boolean removePidLocked(int pid, ProcessRecord app) {
final boolean removed;
synchronized (mPidsSelfLocked) {
removed = mPidsSelfLocked.doRemoveInternal(pid, app);
@@ -1001,26 +995,6 @@
}
mAtmInternal.onProcessUnMapped(pid);
}
- }
-
- /**
- * Removes the process record from the map if it doesn't have a thread.
- * <p>NOTE: Callers should avoid acquiring the mPidsSelfLocked lock before calling this
- * method.
- */
- @GuardedBy("this")
- private boolean removePidIfNoThreadLocked(ProcessRecord app) {
- final boolean removed;
- final int pid = app.getPid();
- synchronized (mPidsSelfLocked) {
- removed = mPidsSelfLocked.doRemoveIfNoThreadInternal(pid, app);
- }
- if (removed) {
- synchronized (sActiveProcessInfoSelfLocked) {
- sActiveProcessInfoSelfLocked.remove(pid);
- }
- mAtmInternal.onProcessUnMapped(pid);
- }
return removed;
}
@@ -2364,7 +2338,7 @@
mAppErrors = null;
mPackageWatchdog = null;
mAppOpsService = mInjector.getAppOpsService(null /* file */, null /* handler */);
- mBatteryStatsService = null;
+ mBatteryStatsService = mInjector.getBatteryStatsService();
mHandler = new MainHandler(handlerThread.getLooper());
mHandlerThread = handlerThread;
mConstants = new ActivityManagerConstants(mContext, this, mHandler);
@@ -2379,7 +2353,7 @@
mIntentFirewall = null;
mProcessStats = new ProcessStatsService(this, mContext.getCacheDir());
mCpHelper = new ContentProviderHelper(this, false);
- mServices = null;
+ mServices = mInjector.getActiveServices(this);
mSystemThread = null;
mUiHandler = injector.getUiHandler(null /* service */);
mUidObserverController = new UidObserverController(mUiHandler);
@@ -4771,7 +4745,7 @@
@GuardedBy("this")
void handleProcessStartOrKillTimeoutLocked(ProcessRecord app, boolean isKillTimeout) {
final int pid = app.getPid();
- boolean gone = isKillTimeout || removePidIfNoThreadLocked(app);
+ boolean gone = isKillTimeout || removePidLocked(pid, app);
if (gone) {
if (isKillTimeout) {
@@ -4852,7 +4826,7 @@
}
@GuardedBy("this")
- private boolean attachApplicationLocked(@NonNull IApplicationThread thread,
+ private void attachApplicationLocked(@NonNull IApplicationThread thread,
int pid, int callingUid, long startSeq) {
// Find the application record that is being attached... either via
@@ -4917,7 +4891,7 @@
// Ignore exceptions.
}
}
- return false;
+ return;
}
// If this application record is still attached to a previous
@@ -4942,7 +4916,7 @@
mProcessList.startProcessLocked(app,
new HostingRecord(HostingRecord.HOSTING_TYPE_LINK_FAIL, processName),
ZYGOTE_POLICY_FLAG_EMPTY);
- return false;
+ return;
}
EventLogTags.writeAmProcBound(app.userId, pid, app.processName);
@@ -4965,8 +4939,6 @@
app.setUnlocked(StorageManager.isUserKeyUnlocked(app.userId));
}
- mHandler.removeMessages(PROC_START_TIMEOUT_MSG, app);
-
boolean normalMode = mProcessesReady || isAllowedWhileBooting(app.info);
List<ProviderInfo> providers = normalMode
? mCpHelper.generateApplicationProvidersLocked(app)
@@ -5132,7 +5104,7 @@
app.killLocked("error during bind", ApplicationExitInfo.REASON_INITIALIZATION_FAILURE,
true);
handleAppDiedLocked(app, pid, false, true, false /* fromBinderDied */);
- return false;
+ return;
}
// Remove this record from the list of starting applications.
@@ -5140,91 +5112,6 @@
if (DEBUG_PROCESSES && mProcessesOnHold.contains(app)) Slog.v(TAG_PROCESSES,
"Attach application locked removing on hold: " + app);
mProcessesOnHold.remove(app);
-
- boolean badApp = false;
- boolean didSomething = false;
-
- // See if the top visible activity is waiting to run in this process...
- if (normalMode) {
- try {
- didSomething = mAtmInternal.attachApplication(app.getWindowProcessController());
- } catch (Exception e) {
- Slog.wtf(TAG, "Exception thrown launching activities in " + app, e);
- badApp = true;
- }
- }
-
- // Find any services that should be running in this process...
- if (!badApp) {
- try {
- didSomething |= mServices.attachApplicationLocked(app, processName);
- checkTime(startTime, "attachApplicationLocked: after mServices.attachApplicationLocked");
- } catch (Exception e) {
- Slog.wtf(TAG, "Exception thrown starting services in " + app, e);
- badApp = true;
- }
- }
-
- // Check if a next-broadcast receiver is in this process...
- if (!badApp) {
- try {
- for (BroadcastQueue queue : mBroadcastQueues) {
- didSomething |= queue.onApplicationAttachedLocked(app);
- }
- checkTime(startTime, "attachApplicationLocked: after dispatching broadcasts");
- } catch (Exception e) {
- // If the app died trying to launch the receiver we declare it 'bad'
- Slog.wtf(TAG, "Exception thrown dispatching broadcasts in " + app, e);
- badApp = true;
- }
- }
-
- // Check whether the next backup agent is in this process...
- if (!badApp && backupTarget != null && backupTarget.app == app) {
- if (DEBUG_BACKUP) Slog.v(TAG_BACKUP,
- "New app is backup target, launching agent for " + app);
- notifyPackageUse(backupTarget.appInfo.packageName,
- PackageManager.NOTIFY_PACKAGE_USE_BACKUP);
- try {
- thread.scheduleCreateBackupAgent(backupTarget.appInfo,
- backupTarget.backupMode, backupTarget.userId,
- backupTarget.backupDestination);
- } catch (Exception e) {
- Slog.wtf(TAG, "Exception thrown creating backup agent in " + app, e);
- badApp = true;
- }
- }
-
- if (badApp) {
- app.killLocked("error during init", ApplicationExitInfo.REASON_INITIALIZATION_FAILURE,
- true);
- handleAppDiedLocked(app, pid, false, true, false /* fromBinderDied */);
- return false;
- }
-
- if (!didSomething) {
- updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_PROCESS_BEGIN);
- checkTime(startTime, "attachApplicationLocked: after updateOomAdjLocked");
- }
-
-
- final HostingRecord hostingRecord = app.getHostingRecord();
- String shortAction = getShortAction(hostingRecord.getAction());
- FrameworkStatsLog.write(
- FrameworkStatsLog.PROCESS_START_TIME,
- app.info.uid,
- pid,
- app.info.packageName,
- FrameworkStatsLog.PROCESS_START_TIME__TYPE__COLD,
- app.getStartElapsedTime(),
- (int) (bindApplicationTimeMillis - app.getStartUptime()),
- (int) (SystemClock.uptimeMillis() - app.getStartUptime()),
- hostingRecord.getType(),
- hostingRecord.getName(),
- shortAction,
- HostingRecord.getHostingTypeIdStatsd(hostingRecord.getType()),
- HostingRecord.getTriggerTypeForStatsd(hostingRecord.getTriggerType()));
- return true;
}
@Override
@@ -5241,6 +5128,143 @@
}
}
+ private void finishAttachApplicationInner(long startSeq, int uid, int pid) {
+ final long startTime = SystemClock.uptimeMillis();
+ // Find the application record that is being attached... either via
+ // the pid if we are running in multiple processes, or just pull the
+ // next app record if we are emulating process with anonymous threads.
+ final ProcessRecord app;
+ synchronized (mPidsSelfLocked) {
+ app = mPidsSelfLocked.get(pid);
+ }
+
+ if (app != null && app.getStartUid() == uid && app.getStartSeq() == startSeq) {
+ mHandler.removeMessages(PROC_START_TIMEOUT_MSG, app);
+ } else {
+ Slog.wtf(TAG, "Mismatched or missing ProcessRecord: " + app + ". Pid: " + pid
+ + ". Uid: " + uid);
+ killProcess(pid);
+ killProcessGroup(uid, pid);
+ mProcessList.noteAppKill(pid, uid,
+ ApplicationExitInfo.REASON_INITIALIZATION_FAILURE,
+ ApplicationExitInfo.SUBREASON_UNKNOWN,
+ "wrong startSeq");
+ app.killLocked("unexpected process record",
+ ApplicationExitInfo.REASON_OTHER, true);
+ return;
+ }
+
+ synchronized (this) {
+ final boolean normalMode = mProcessesReady || isAllowedWhileBooting(app.info);
+ final String processName = app.processName;
+ boolean badApp = false;
+ boolean didSomething = false;
+
+ // See if the top visible activity is waiting to run in this process...
+ if (normalMode) {
+ try {
+ didSomething = mAtmInternal.attachApplication(app.getWindowProcessController());
+ } catch (Exception e) {
+ Slog.wtf(TAG, "Exception thrown launching activities in " + app, e);
+ badApp = true;
+ }
+ }
+
+ // Find any services that should be running in this process...
+ if (!badApp) {
+ try {
+ didSomething |= mServices.attachApplicationLocked(app, processName);
+ checkTime(startTime, "finishAttachApplicationInner: "
+ + "after mServices.attachApplicationLocked");
+ } catch (Exception e) {
+ Slog.wtf(TAG, "Exception thrown starting services in " + app, e);
+ badApp = true;
+ }
+ }
+
+ // Check if a next-broadcast receiver is in this process...
+ if (!badApp) {
+ try {
+ for (BroadcastQueue queue : mBroadcastQueues) {
+ didSomething |= queue.onApplicationAttachedLocked(app);
+ }
+ checkTime(startTime, "finishAttachApplicationInner: "
+ + "after dispatching broadcasts");
+ } catch (Exception e) {
+ // If the app died trying to launch the receiver we declare it 'bad'
+ Slog.wtf(TAG, "Exception thrown dispatching broadcasts in " + app, e);
+ badApp = true;
+ }
+ }
+
+ // Check whether the next backup agent is in this process...
+ final BackupRecord backupTarget = mBackupTargets.get(app.userId);
+ if (!badApp && backupTarget != null && backupTarget.app == app) {
+ if (DEBUG_BACKUP) {
+ Slog.v(TAG_BACKUP,
+ "New app is backup target, launching agent for " + app);
+ }
+
+ notifyPackageUse(backupTarget.appInfo.packageName,
+ PackageManager.NOTIFY_PACKAGE_USE_BACKUP);
+ try {
+ app.getThread().scheduleCreateBackupAgent(backupTarget.appInfo,
+ backupTarget.backupMode, backupTarget.userId,
+ backupTarget.backupDestination);
+ } catch (Exception e) {
+ Slog.wtf(TAG, "Exception thrown creating backup agent in " + app, e);
+ badApp = true;
+ }
+ }
+
+ if (badApp) {
+ app.killLocked("error during init",
+ ApplicationExitInfo.REASON_INITIALIZATION_FAILURE, true);
+ handleAppDiedLocked(app, pid, false, true, false /* fromBinderDied */);
+ return;
+ }
+
+ if (!didSomething) {
+ updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_PROCESS_BEGIN);
+ checkTime(startTime, "finishAttachApplicationInner: after updateOomAdjLocked");
+ }
+
+ final HostingRecord hostingRecord = app.getHostingRecord();
+ final String shortAction = getShortAction(hostingRecord.getAction());
+ FrameworkStatsLog.write(
+ FrameworkStatsLog.PROCESS_START_TIME,
+ app.info.uid,
+ pid,
+ app.info.packageName,
+ FrameworkStatsLog.PROCESS_START_TIME__TYPE__COLD,
+ app.getStartElapsedTime(),
+ (int) (app.getBindApplicationTime() - app.getStartUptime()),
+ (int) (SystemClock.uptimeMillis() - app.getStartUptime()),
+ hostingRecord.getType(),
+ hostingRecord.getName(),
+ shortAction,
+ HostingRecord.getHostingTypeIdStatsd(hostingRecord.getType()),
+ HostingRecord.getTriggerTypeForStatsd(hostingRecord.getTriggerType()));
+ }
+ }
+
+ @Override
+ public final void finishAttachApplication(long startSeq) {
+ final int pid = Binder.getCallingPid();
+ final int uid = Binder.getCallingUid();
+
+ if (pid == MY_PID && uid == SYSTEM_UID) {
+ return;
+ }
+
+ final long origId = Binder.clearCallingIdentity();
+ try {
+ finishAttachApplicationInner(startSeq, uid, pid);
+ } finally {
+ Binder.restoreCallingIdentity(origId);
+ }
+ }
+
/**
* @return The last part of the string of an intent's action.
*/
@@ -18805,6 +18829,21 @@
return new ProcessList();
}
+ /**
+ * Returns the {@link BatteryStatsService} instance
+ */
+ public BatteryStatsService getBatteryStatsService() {
+ return new BatteryStatsService(mContext, SystemServiceManager.ensureSystemDir(),
+ BackgroundThread.get().getHandler());
+ }
+
+ /**
+ * Returns the {@link ActiveServices} instance
+ */
+ public ActiveServices getActiveServices(ActivityManagerService service) {
+ return new ActiveServices(service);
+ }
+
private boolean ensureHasNetworkManagementInternal() {
if (mNmi == null) {
mNmi = LocalServices.getService(NetworkManagementInternal.class);
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index ecea96e..937bbc9c 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -2508,7 +2508,7 @@
}
@GuardedBy("mService")
- private String isProcStartValidLocked(ProcessRecord app, long expectedStartSeq) {
+ String isProcStartValidLocked(ProcessRecord app, long expectedStartSeq) {
StringBuilder sb = null;
if (app.isKilledByAm()) {
if (sb == null) sb = new StringBuilder();
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index 0a8c640..4706c268 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -200,6 +200,11 @@
private volatile long mStartElapsedTime;
/**
+ * When the process was sent the bindApplication request
+ */
+ private volatile long mBindApplicationTime;
+
+ /**
* This will be same as {@link #uid} usually except for some apps used during factory testing.
*/
private volatile int mStartUid;
@@ -739,6 +744,10 @@
return mStartElapsedTime;
}
+ long getBindApplicationTime() {
+ return mBindApplicationTime;
+ }
+
int getStartUid() {
return mStartUid;
}
diff --git a/services/core/java/com/android/server/display/DisplayDeviceInfo.java b/services/core/java/com/android/server/display/DisplayDeviceInfo.java
index a0cbd7f..8811999 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceInfo.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceInfo.java
@@ -23,6 +23,7 @@
import android.view.DisplayAddress;
import android.view.DisplayCutout;
import android.view.DisplayEventReceiver;
+import android.view.DisplayShape;
import android.view.RoundedCorners;
import android.view.Surface;
@@ -316,6 +317,11 @@
public RoundedCorners roundedCorners;
/**
+ * The {@link RoundedCorners} if present or {@code null} otherwise.
+ */
+ public DisplayShape displayShape;
+
+ /**
* The touch attachment, per {@link DisplayViewport#touch}.
*/
public int touch;
@@ -451,7 +457,8 @@
|| !BrightnessSynchronizer.floatEquals(brightnessDefault,
other.brightnessDefault)
|| !Objects.equals(roundedCorners, other.roundedCorners)
- || installOrientation != other.installOrientation) {
+ || installOrientation != other.installOrientation
+ || !Objects.equals(displayShape, other.displayShape)) {
diff |= DIFF_OTHER;
}
return diff;
@@ -497,6 +504,7 @@
brightnessDefault = other.brightnessDefault;
roundedCorners = other.roundedCorners;
installOrientation = other.installOrientation;
+ displayShape = other.displayShape;
}
// For debugging purposes
@@ -546,6 +554,9 @@
}
sb.append(flagsToString(flags));
sb.append(", installOrientation ").append(installOrientation);
+ if (displayShape != null) {
+ sb.append(", displayShape ").append(displayShape);
+ }
sb.append("}");
return sb.toString();
}
diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index 5a714f5..4bf1e98 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -38,6 +38,7 @@
import android.view.DisplayAddress;
import android.view.DisplayCutout;
import android.view.DisplayEventReceiver;
+import android.view.DisplayShape;
import android.view.RoundedCorners;
import android.view.SurfaceControl;
@@ -686,6 +687,9 @@
res, mInfo.uniqueId, maxWidth, maxHeight, mInfo.width, mInfo.height);
mInfo.installOrientation = mStaticDisplayInfo.installOrientation;
+ mInfo.displayShape = DisplayShape.fromResources(
+ res, mInfo.uniqueId, maxWidth, maxHeight, mInfo.width, mInfo.height);
+
if (mStaticDisplayInfo.isInternal) {
mInfo.type = Display.TYPE_INTERNAL;
mInfo.touch = DisplayDeviceInfo.TOUCH_INTERNAL;
diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java
index 26ac528..28bdce3 100644
--- a/services/core/java/com/android/server/display/LogicalDisplay.java
+++ b/services/core/java/com/android/server/display/LogicalDisplay.java
@@ -233,6 +233,7 @@
info.displayCutout = mOverrideDisplayInfo.displayCutout;
info.logicalDensityDpi = mOverrideDisplayInfo.logicalDensityDpi;
info.roundedCorners = mOverrideDisplayInfo.roundedCorners;
+ info.displayShape = mOverrideDisplayInfo.displayShape;
}
mInfo.set(info);
}
@@ -437,6 +438,7 @@
mBaseDisplayInfo.brightnessDefault = deviceInfo.brightnessDefault;
mBaseDisplayInfo.roundedCorners = deviceInfo.roundedCorners;
mBaseDisplayInfo.installOrientation = deviceInfo.installOrientation;
+ mBaseDisplayInfo.displayShape = deviceInfo.displayShape;
mPrimaryDisplayDeviceInfo = deviceInfo;
mInfo.set(null);
}
diff --git a/services/core/java/com/android/server/display/OverlayDisplayAdapter.java b/services/core/java/com/android/server/display/OverlayDisplayAdapter.java
index b0de844..0e11b53 100644
--- a/services/core/java/com/android/server/display/OverlayDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/OverlayDisplayAdapter.java
@@ -29,6 +29,7 @@
import android.util.DisplayMetrics;
import android.util.Slog;
import android.view.Display;
+import android.view.DisplayShape;
import android.view.Gravity;
import android.view.Surface;
import android.view.SurfaceControl;
@@ -361,6 +362,8 @@
mInfo.state = mState;
// The display is trusted since it is created by system.
mInfo.flags |= FLAG_TRUSTED;
+ mInfo.displayShape =
+ DisplayShape.createDefaultDisplayShape(mInfo.width, mInfo.height, false);
}
return mInfo;
}
diff --git a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
index a23a073..d0e518b 100644
--- a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
@@ -52,6 +52,7 @@
import android.util.ArrayMap;
import android.util.Slog;
import android.view.Display;
+import android.view.DisplayShape;
import android.view.Surface;
import android.view.SurfaceControl;
@@ -524,6 +525,9 @@
mInfo.ownerUid = mOwnerUid;
mInfo.ownerPackageName = mOwnerPackageName;
+
+ mInfo.displayShape =
+ DisplayShape.createDefaultDisplayShape(mInfo.width, mInfo.height, false);
}
return mInfo;
}
diff --git a/services/core/java/com/android/server/display/WifiDisplayAdapter.java b/services/core/java/com/android/server/display/WifiDisplayAdapter.java
index 146b003..c759d98 100644
--- a/services/core/java/com/android/server/display/WifiDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/WifiDisplayAdapter.java
@@ -34,6 +34,7 @@
import android.util.Slog;
import android.view.Display;
import android.view.DisplayAddress;
+import android.view.DisplayShape;
import android.view.Surface;
import android.view.SurfaceControl;
@@ -655,6 +656,8 @@
mInfo.setAssumedDensityForExternalDisplay(mWidth, mHeight);
// The display is trusted since it is created by system.
mInfo.flags |= DisplayDeviceInfo.FLAG_TRUSTED;
+ mInfo.displayShape =
+ DisplayShape.createDefaultDisplayShape(mInfo.width, mInfo.height, false);
}
return mInfo;
}
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index b5c1206..494f46c 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -191,6 +191,7 @@
private static final String ATTR_CREATION_TIME = "created";
private static final String ATTR_LAST_LOGGED_IN_TIME = "lastLoggedIn";
private static final String ATTR_LAST_LOGGED_IN_FINGERPRINT = "lastLoggedInFingerprint";
+ private static final String ATTR_LAST_ENTERED_FOREGROUND_TIME = "lastEnteredForeground";
private static final String ATTR_SERIAL_NO = "serialNumber";
private static final String ATTR_NEXT_SERIAL_NO = "nextSerialNumber";
private static final String ATTR_PARTIAL = "partial";
@@ -341,6 +342,9 @@
/** Elapsed realtime since boot when the user was unlocked. */
long unlockRealtime;
+ /** Wall clock time in millis when the user last entered the foreground. */
+ long mLastEnteredForegroundTimeMillis;
+
private long mLastRequestQuietModeEnabledMillis;
/**
@@ -680,6 +684,10 @@
final UserData user = mUms.getUserDataLU(targetUser.getUserIdentifier());
if (user != null) {
user.startRealtime = SystemClock.elapsedRealtime();
+ if (targetUser.getUserIdentifier() == UserHandle.USER_SYSTEM
+ && targetUser.isFull()) {
+ mUms.setLastEnteredForegroundTimeToNow(user);
+ }
}
}
}
@@ -695,6 +703,16 @@
}
@Override
+ public void onUserSwitching(@NonNull TargetUser from, @NonNull TargetUser to) {
+ synchronized (mUms.mUsersLock) {
+ final UserData user = mUms.getUserDataLU(to.getUserIdentifier());
+ if (user != null) {
+ mUms.setLastEnteredForegroundTimeToNow(user);
+ }
+ }
+ }
+
+ @Override
public void onUserStopping(@NonNull TargetUser targetUser) {
synchronized (mUms.mUsersLock) {
final UserData user = mUms.getUserDataLU(targetUser.getUserIdentifier());
@@ -920,6 +938,30 @@
return UserHandle.USER_NULL;
}
+ @Override
+ public int getPreviousFullUserToEnterForeground() {
+ checkQueryOrCreateUsersPermission("get previous user");
+ int previousUser = UserHandle.USER_NULL;
+ long latestEnteredTime = 0;
+ final int currentUser = getCurrentUserId();
+ synchronized (mUsersLock) {
+ final int userSize = mUsers.size();
+ for (int i = 0; i < userSize; i++) {
+ final UserData userData = mUsers.valueAt(i);
+ final int userId = userData.info.id;
+ if (userId != currentUser && userData.info.isFull() && !userData.info.partial
+ && !mRemovingUserIds.get(userId)) {
+ final long userEnteredTime = userData.mLastEnteredForegroundTimeMillis;
+ if (userEnteredTime > latestEnteredTime) {
+ latestEnteredTime = userEnteredTime;
+ previousUser = userId;
+ }
+ }
+ }
+ }
+ return previousUser;
+ }
+
public @NonNull List<UserInfo> getUsers(boolean excludeDying) {
return getUsers(/*excludePartial= */ true, excludeDying, /* excludePreCreated= */
true);
@@ -3973,6 +4015,8 @@
serializer.attribute(null, ATTR_LAST_LOGGED_IN_FINGERPRINT,
userInfo.lastLoggedInFingerprint);
}
+ serializer.attributeLong(
+ null, ATTR_LAST_ENTERED_FOREGROUND_TIME, userData.mLastEnteredForegroundTimeMillis);
if (userInfo.iconPath != null) {
serializer.attribute(null, ATTR_ICON_PATH, userInfo.iconPath);
}
@@ -4146,6 +4190,7 @@
long lastLoggedInTime = 0L;
long lastRequestQuietModeEnabledTimestamp = 0L;
String lastLoggedInFingerprint = null;
+ long lastEnteredForegroundTime = 0L;
int profileGroupId = UserInfo.NO_PROFILE_GROUP_ID;
int profileBadge = 0;
int restrictedProfileParentId = UserInfo.NO_PROFILE_GROUP_ID;
@@ -4191,6 +4236,8 @@
lastLoggedInTime = parser.getAttributeLong(null, ATTR_LAST_LOGGED_IN_TIME, 0);
lastLoggedInFingerprint = parser.getAttributeValue(null,
ATTR_LAST_LOGGED_IN_FINGERPRINT);
+ lastEnteredForegroundTime =
+ parser.getAttributeLong(null, ATTR_LAST_ENTERED_FOREGROUND_TIME, 0L);
profileGroupId = parser.getAttributeInt(null, ATTR_PROFILE_GROUP_ID,
UserInfo.NO_PROFILE_GROUP_ID);
profileBadge = parser.getAttributeInt(null, ATTR_PROFILE_BADGE, 0);
@@ -4285,6 +4332,7 @@
userData.seedAccountOptions = seedAccountOptions;
userData.userProperties = userProperties;
userData.setLastRequestQuietModeEnabledMillis(lastRequestQuietModeEnabledTimestamp);
+ userData.mLastEnteredForegroundTimeMillis = lastEnteredForegroundTime;
if (ignorePrepareStorageErrors) {
userData.setIgnorePrepareStorageErrors();
}
@@ -6204,6 +6252,11 @@
|| someUserHasSeedAccountNoChecks(accountName, accountType));
}
+ private void setLastEnteredForegroundTimeToNow(@NonNull UserData userData) {
+ userData.mLastEnteredForegroundTimeMillis = System.currentTimeMillis();
+ scheduleWriteUser(userData);
+ }
+
@Override
public void onShellCommand(FileDescriptor in, FileDescriptor out,
FileDescriptor err, String[] args, ShellCallback callback,
@@ -6428,6 +6481,9 @@
pw.print(" Unlock time: ");
dumpTimeAgo(pw, tempStringBuilder, nowRealtime, userData.unlockRealtime);
+ pw.print(" Last entered foreground: ");
+ dumpTimeAgo(pw, tempStringBuilder, now, userData.mLastEnteredForegroundTimeMillis);
+
pw.print(" Has profile owner: ");
pw.println(mIsUserManaged.get(userId));
pw.println(" Restrictions:");
diff --git a/services/core/java/com/android/server/utils/Slogf.java b/services/core/java/com/android/server/utils/Slogf.java
index e88ac63..6efbd89 100644
--- a/services/core/java/com/android/server/utils/Slogf.java
+++ b/services/core/java/com/android/server/utils/Slogf.java
@@ -162,7 +162,7 @@
}
/**
- * Logs a {@link Log.VEBOSE} message with an exception
+ * Logs a {@link Log.VEBOSE} message with a throwable
*
* <p><strong>Note: </strong>the message will only be formatted if {@link Log#VERBOSE} logging
* is enabled for the given {@code tag}, but the compiler will still create an intermediate
@@ -170,10 +170,10 @@
* you're calling this method in a critical path, make sure to explicitly do the check before
* calling it.
*/
- public static void v(String tag, Exception exception, String format, @Nullable Object... args) {
+ public static void v(String tag, Throwable throwable, String format, @Nullable Object... args) {
if (!isLoggable(tag, Log.VERBOSE)) return;
- v(tag, getMessage(format, args), exception);
+ v(tag, getMessage(format, args), throwable);
}
/**
@@ -192,7 +192,7 @@
}
/**
- * Logs a {@link Log.DEBUG} message with an exception
+ * Logs a {@link Log.DEBUG} message with a throwable
*
* <p><strong>Note: </strong>the message will only be formatted if {@link Log#DEBUG} logging
* is enabled for the given {@code tag}, but the compiler will still create an intermediate
@@ -200,10 +200,10 @@
* you're calling this method in a critical path, make sure to explicitly do the check before
* calling it.
*/
- public static void d(String tag, Exception exception, String format, @Nullable Object... args) {
+ public static void d(String tag, Throwable throwable, String format, @Nullable Object... args) {
if (!isLoggable(tag, Log.DEBUG)) return;
- d(tag, getMessage(format, args), exception);
+ d(tag, getMessage(format, args), throwable);
}
/**
@@ -222,7 +222,7 @@
}
/**
- * Logs a {@link Log.INFO} message with an exception
+ * Logs a {@link Log.INFO} message with a throwable
*
* <p><strong>Note: </strong>the message will only be formatted if {@link Log#INFO} logging
* is enabled for the given {@code tag}, but the compiler will still create an intermediate
@@ -230,10 +230,10 @@
* you're calling this method in a critical path, make sure to explicitly do the check before
* calling it.
*/
- public static void i(String tag, Exception exception, String format, @Nullable Object... args) {
+ public static void i(String tag, Throwable throwable, String format, @Nullable Object... args) {
if (!isLoggable(tag, Log.INFO)) return;
- i(tag, getMessage(format, args), exception);
+ i(tag, getMessage(format, args), throwable);
}
/**
@@ -252,7 +252,7 @@
}
/**
- * Logs a {@link Log.WARN} message with an exception
+ * Logs a {@link Log.WARN} message with a throwable
*
* <p><strong>Note: </strong>the message will only be formatted if {@link Log#WARN} logging is
* enabled for the given {@code tag}, but the compiler will still create an intermediate array
@@ -260,10 +260,10 @@
* calling this method in a critical path, make sure to explicitly do the check before calling
* it.
*/
- public static void w(String tag, Exception exception, String format, @Nullable Object... args) {
+ public static void w(String tag, Throwable throwable, String format, @Nullable Object... args) {
if (!isLoggable(tag, Log.WARN)) return;
- w(tag, getMessage(format, args), exception);
+ w(tag, getMessage(format, args), throwable);
}
/**
@@ -282,7 +282,7 @@
}
/**
- * Logs a {@link Log.ERROR} message with an exception
+ * Logs a {@link Log.ERROR} message with a throwable
*
* <p><strong>Note: </strong>the message will only be formatted if {@link Log#ERROR} logging is
* enabled for the given {@code tag}, but the compiler will still create an intermediate array
@@ -290,10 +290,10 @@
* calling this method in a critical path, make sure to explicitly do the check before calling
* it.
*/
- public static void e(String tag, Exception exception, String format, @Nullable Object... args) {
+ public static void e(String tag, Throwable throwable, String format, @Nullable Object... args) {
if (!isLoggable(tag, Log.ERROR)) return;
- e(tag, getMessage(format, args), exception);
+ e(tag, getMessage(format, args), throwable);
}
/**
@@ -304,11 +304,11 @@
}
/**
- * Logs a {@code wtf} message with an exception.
+ * Logs a {@code wtf} message with a throwable.
*/
- public static void wtf(String tag, Exception exception, String format,
+ public static void wtf(String tag, Throwable throwable, String format,
@Nullable Object... args) {
- wtf(tag, getMessage(format, args), exception);
+ wtf(tag, getMessage(format, args), throwable);
}
private static String getMessage(String format, @Nullable Object... args) {
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 9c920f53..d1122e1 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -207,6 +207,7 @@
import android.view.Display;
import android.view.DisplayCutout;
import android.view.DisplayInfo;
+import android.view.DisplayShape;
import android.view.Gravity;
import android.view.IDisplayWindowInsetsController;
import android.view.ISystemGestureExclusionListener;
@@ -391,6 +392,10 @@
mPrivacyIndicatorBoundsCache = new
RotationCache<>(this::calculatePrivacyIndicatorBoundsForRotationUncached);
+ DisplayShape mInitialDisplayShape;
+ private final RotationCache<DisplayShape, DisplayShape> mDisplayShapeCache =
+ new RotationCache<>(this::calculateDisplayShapeForRotationUncached);
+
/**
* Overridden display size. Initialized with {@link #mInitialDisplayWidth}
* and {@link #mInitialDisplayHeight}, but can be set via shell command "adb shell wm size".
@@ -1095,7 +1100,8 @@
mDisplayFrames = new DisplayFrames(mInsetsStateController.getRawInsetsState(),
mDisplayInfo, calculateDisplayCutoutForRotation(mDisplayInfo.rotation),
calculateRoundedCornersForRotation(mDisplayInfo.rotation),
- calculatePrivacyIndicatorBoundsForRotation(mDisplayInfo.rotation));
+ calculatePrivacyIndicatorBoundsForRotation(mDisplayInfo.rotation),
+ calculateDisplayShapeForRotation(mDisplayInfo.rotation));
initializeDisplayBaseInfo();
mHoldScreenWakeLock = mWmService.mPowerManager.newWakeLock(
@@ -1969,8 +1975,9 @@
final RoundedCorners roundedCorners = calculateRoundedCornersForRotation(rotation);
final PrivacyIndicatorBounds indicatorBounds =
calculatePrivacyIndicatorBoundsForRotation(rotation);
+ final DisplayShape displayShape = calculateDisplayShapeForRotation(rotation);
final DisplayFrames displayFrames = new DisplayFrames(new InsetsState(), info,
- cutout, roundedCorners, indicatorBounds);
+ cutout, roundedCorners, indicatorBounds, displayShape);
token.applyFixedRotationTransform(info, displayFrames, mTmpConfiguration);
}
@@ -2178,6 +2185,7 @@
// Update application display metrics.
final DisplayCutout displayCutout = calculateDisplayCutoutForRotation(rotation);
final RoundedCorners roundedCorners = calculateRoundedCornersForRotation(rotation);
+ final DisplayShape displayShape = calculateDisplayShapeForRotation(rotation);
final Rect appFrame = mDisplayPolicy.getDecorInsetsInfo(rotation, dw, dh).mNonDecorFrame;
mDisplayInfo.rotation = rotation;
@@ -2194,6 +2202,7 @@
}
mDisplayInfo.displayCutout = displayCutout.isEmpty() ? null : displayCutout;
mDisplayInfo.roundedCorners = roundedCorners;
+ mDisplayInfo.displayShape = displayShape;
mDisplayInfo.getAppMetrics(mDisplayMetrics);
if (mDisplayScalingDisabled) {
mDisplayInfo.flags |= Display.FLAG_SCALING_DISABLED;
@@ -2280,6 +2289,23 @@
return bounds.rotate(rotation);
}
+ DisplayShape calculateDisplayShapeForRotation(int rotation) {
+ return mDisplayShapeCache.getOrCompute(mInitialDisplayShape, rotation);
+ }
+
+ private DisplayShape calculateDisplayShapeForRotationUncached(
+ DisplayShape displayShape, int rotation) {
+ if (displayShape == null) {
+ return DisplayShape.NONE;
+ }
+
+ if (rotation == ROTATION_0) {
+ return displayShape;
+ }
+
+ return displayShape.setRotation(rotation);
+ }
+
/**
* Compute display info and configuration according to the given rotation without changing
* current display.
@@ -2781,7 +2807,8 @@
return displayFrames.update(rotation, w, h,
calculateDisplayCutoutForRotation(rotation),
calculateRoundedCornersForRotation(rotation),
- calculatePrivacyIndicatorBoundsForRotation(rotation));
+ calculatePrivacyIndicatorBoundsForRotation(rotation),
+ calculateDisplayShapeForRotation(rotation));
}
@Override
@@ -2821,6 +2848,7 @@
mInitialRoundedCorners = mDisplayInfo.roundedCorners;
mCurrentPrivacyIndicatorBounds = new PrivacyIndicatorBounds(new Rect[4],
mDisplayInfo.rotation);
+ mInitialDisplayShape = mDisplayInfo.displayShape;
final Display.Mode maxDisplayMode =
DisplayUtils.getMaximumResolutionDisplayMode(mDisplayInfo.supportedModes);
mPhysicalDisplaySize = new Point(
@@ -2848,6 +2876,7 @@
? DisplayCutout.NO_CUTOUT : mDisplayInfo.displayCutout;
final String newUniqueId = mDisplayInfo.uniqueId;
final RoundedCorners newRoundedCorners = mDisplayInfo.roundedCorners;
+ final DisplayShape newDisplayShape = mDisplayInfo.displayShape;
final boolean displayMetricsChanged = mInitialDisplayWidth != newWidth
|| mInitialDisplayHeight != newHeight
@@ -2855,7 +2884,8 @@
|| mInitialPhysicalXDpi != newXDpi
|| mInitialPhysicalYDpi != newYDpi
|| !Objects.equals(mInitialDisplayCutout, newCutout)
- || !Objects.equals(mInitialRoundedCorners, newRoundedCorners);
+ || !Objects.equals(mInitialRoundedCorners, newRoundedCorners)
+ || !Objects.equals(mInitialDisplayShape, newDisplayShape);
final boolean physicalDisplayChanged = !newUniqueId.equals(mCurrentUniqueDisplayId);
if (displayMetricsChanged || physicalDisplayChanged) {
@@ -2893,6 +2923,7 @@
mInitialPhysicalYDpi = newYDpi;
mInitialDisplayCutout = newCutout;
mInitialRoundedCorners = newRoundedCorners;
+ mInitialDisplayShape = newDisplayShape;
mCurrentUniqueDisplayId = newUniqueId;
reconfigureDisplayLocked();
diff --git a/services/core/java/com/android/server/wm/DisplayFrames.java b/services/core/java/com/android/server/wm/DisplayFrames.java
index 33641f7..e984456 100644
--- a/services/core/java/com/android/server/wm/DisplayFrames.java
+++ b/services/core/java/com/android/server/wm/DisplayFrames.java
@@ -26,6 +26,7 @@
import android.util.proto.ProtoOutputStream;
import android.view.DisplayCutout;
import android.view.DisplayInfo;
+import android.view.DisplayShape;
import android.view.InsetsState;
import android.view.PrivacyIndicatorBounds;
import android.view.RoundedCorners;
@@ -56,10 +57,11 @@
public int mRotation;
public DisplayFrames(InsetsState insetsState, DisplayInfo info, DisplayCutout cutout,
- RoundedCorners roundedCorners, PrivacyIndicatorBounds indicatorBounds) {
+ RoundedCorners roundedCorners, PrivacyIndicatorBounds indicatorBounds,
+ DisplayShape displayShape) {
mInsetsState = insetsState;
update(info.rotation, info.logicalWidth, info.logicalHeight, cutout, roundedCorners,
- indicatorBounds);
+ indicatorBounds, displayShape);
}
DisplayFrames() {
@@ -73,7 +75,8 @@
*/
public boolean update(int rotation, int w, int h, @NonNull DisplayCutout displayCutout,
@NonNull RoundedCorners roundedCorners,
- @NonNull PrivacyIndicatorBounds indicatorBounds) {
+ @NonNull PrivacyIndicatorBounds indicatorBounds,
+ @NonNull DisplayShape displayShape) {
final InsetsState state = mInsetsState;
final Rect safe = mDisplayCutoutSafe;
if (mRotation == rotation && mWidth == w && mHeight == h
@@ -91,6 +94,7 @@
state.setDisplayCutout(displayCutout);
state.setRoundedCorners(roundedCorners);
state.setPrivacyIndicatorBounds(indicatorBounds);
+ state.setDisplayShape(displayShape);
state.getDisplayCutoutSafe(safe);
if (safe.left > unrestricted.left) {
state.getSource(ITYPE_LEFT_DISPLAY_CUTOUT).setFrame(
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/AsyncProcessStartTest.java b/services/tests/mockingservicestests/src/com/android/server/am/AsyncProcessStartTest.java
new file mode 100644
index 0000000..ea14ffb
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/am/AsyncProcessStartTest.java
@@ -0,0 +1,282 @@
+/*
+ * 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.am;
+
+import static android.os.Process.myPid;
+import static android.os.Process.myUid;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+
+import android.app.ActivityManagerInternal;
+import android.app.IApplicationThread;
+import android.app.usage.UsageStatsManagerInternal;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManagerInternal;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.os.SystemClock;
+import android.util.Log;
+
+import androidx.test.filters.MediumTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.server.DropBoxManagerInternal;
+import com.android.server.LocalServices;
+import com.android.server.am.ActivityManagerService.Injector;
+import com.android.server.appop.AppOpsService;
+import com.android.server.wm.ActivityTaskManagerInternal;
+import com.android.server.wm.ActivityTaskManagerService;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.io.File;
+import java.util.Arrays;
+
+
+/**
+ * Tests to verify process starts are completed or timeout correctly
+ */
+@MediumTest
+@SuppressWarnings("GuardedBy")
+public class AsyncProcessStartTest {
+ private static final String TAG = "AsyncProcessStartTest";
+
+ private static final String PACKAGE = "com.foo";
+
+ @Rule
+ public final ApplicationExitInfoTest.ServiceThreadRule
+ mServiceThreadRule = new ApplicationExitInfoTest.ServiceThreadRule();
+
+ private Context mContext;
+ private HandlerThread mHandlerThread;
+
+ @Mock
+ private AppOpsService mAppOpsService;
+ @Mock
+ private DropBoxManagerInternal mDropBoxManagerInt;
+ @Mock
+ private PackageManagerInternal mPackageManagerInt;
+ @Mock
+ private UsageStatsManagerInternal mUsageStatsManagerInt;
+ @Mock
+ private ActivityManagerInternal mActivityManagerInt;
+ @Mock
+ private ActivityTaskManagerInternal mActivityTaskManagerInt;
+ @Mock
+ private BatteryStatsService mBatteryStatsService;
+
+ private ActivityManagerService mRealAms;
+ private ActivityManagerService mAms;
+
+ private ProcessList mRealProcessList = new ProcessList();
+ private ProcessList mProcessList;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+
+ mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+
+ mHandlerThread = new HandlerThread(TAG);
+ mHandlerThread.start();
+
+ LocalServices.removeServiceForTest(DropBoxManagerInternal.class);
+ LocalServices.addService(DropBoxManagerInternal.class, mDropBoxManagerInt);
+
+ LocalServices.removeServiceForTest(PackageManagerInternal.class);
+ LocalServices.addService(PackageManagerInternal.class, mPackageManagerInt);
+
+ LocalServices.removeServiceForTest(ActivityManagerInternal.class);
+ LocalServices.addService(ActivityManagerInternal.class, mActivityManagerInt);
+
+ LocalServices.removeServiceForTest(ActivityTaskManagerInternal.class);
+ LocalServices.addService(ActivityTaskManagerInternal.class, mActivityTaskManagerInt);
+
+ doReturn(new ComponentName("", "")).when(mPackageManagerInt).getSystemUiServiceComponent();
+ doReturn(true).when(mActivityTaskManagerInt).attachApplication(any());
+ doNothing().when(mActivityTaskManagerInt).onProcessMapped(anyInt(), any());
+
+ mRealAms = new ActivityManagerService(
+ new TestInjector(mContext), mServiceThreadRule.getThread());
+ mRealAms.mActivityTaskManager = new ActivityTaskManagerService(mContext);
+ mRealAms.mActivityTaskManager.initialize(null, null, mContext.getMainLooper());
+ mRealAms.mAtmInternal = mActivityTaskManagerInt;
+ mRealAms.mPackageManagerInt = mPackageManagerInt;
+ mRealAms.mUsageStatsService = mUsageStatsManagerInt;
+ mRealAms.mProcessesReady = true;
+ mAms = spy(mRealAms);
+ mRealProcessList.mService = mAms;
+ mProcessList = spy(mRealProcessList);
+
+ doAnswer((invocation) -> {
+ Log.v(TAG, "Intercepting isProcStartValidLocked() for "
+ + Arrays.toString(invocation.getArguments()));
+ return null;
+ }).when(mProcessList).isProcStartValidLocked(any(), anyLong());
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ mHandlerThread.quit();
+ }
+
+ private class TestInjector extends Injector {
+ TestInjector(Context context) {
+ super(context);
+ }
+
+ @Override
+ public AppOpsService getAppOpsService(File file, Handler handler) {
+ return mAppOpsService;
+ }
+
+ @Override
+ public Handler getUiHandler(ActivityManagerService service) {
+ return mHandlerThread.getThreadHandler();
+ }
+
+ @Override
+ public ProcessList getProcessList(ActivityManagerService service) {
+ return mRealProcessList;
+ }
+
+ @Override
+ public BatteryStatsService getBatteryStatsService() {
+ return mBatteryStatsService;
+ }
+ }
+
+ private ProcessRecord makeActiveProcessRecord(String packageName, boolean wedge)
+ throws Exception {
+ final ApplicationInfo ai = makeApplicationInfo(packageName);
+ return makeActiveProcessRecord(ai, wedge);
+ }
+
+ private ProcessRecord makeActiveProcessRecord(ApplicationInfo ai, boolean wedge)
+ throws Exception {
+ final IApplicationThread thread = mock(IApplicationThread.class);
+ final IBinder threadBinder = new Binder();
+ doReturn(threadBinder).when(thread).asBinder();
+ doAnswer((invocation) -> {
+ Log.v(TAG, "Intercepting bindApplication() for "
+ + Arrays.toString(invocation.getArguments()));
+ if (!wedge) {
+ mRealAms.finishAttachApplication(0);
+ }
+ return null;
+ }).when(thread).bindApplication(
+ any(), any(),
+ any(), any(),
+ any(), any(),
+ any(), any(),
+ any(),
+ any(), anyInt(),
+ anyBoolean(), anyBoolean(),
+ anyBoolean(), anyBoolean(), any(),
+ any(), any(), any(),
+ any(), any(),
+ any(), any(),
+ any(),
+ anyLong(), anyLong());
+
+ final ProcessRecord r = spy(new ProcessRecord(mAms, ai, ai.processName, ai.uid));
+ r.setPid(myPid());
+ r.setStartUid(myUid());
+ r.setHostingRecord(new HostingRecord(HostingRecord.HOSTING_TYPE_BROADCAST));
+ r.makeActive(thread, mAms.mProcessStats);
+ doNothing().when(r).killLocked(any(), any(), anyInt(), anyInt(), anyBoolean(),
+ anyBoolean());
+
+ return r;
+ }
+
+ static ApplicationInfo makeApplicationInfo(String packageName) {
+ final ApplicationInfo ai = new ApplicationInfo();
+ ai.packageName = packageName;
+ ai.processName = packageName;
+ ai.uid = myUid();
+ return ai;
+ }
+
+ /**
+ * Verify that we don't kill a normal process
+ */
+ @Test
+ public void testNormal() throws Exception {
+ ProcessRecord app = startProcessAndWait(false);
+
+ verify(app, never()).killLocked(any(), anyInt(), anyBoolean());
+ }
+
+ /**
+ * Verify that we kill a wedged process after the process start timeout
+ */
+ @Test
+ public void testWedged() throws Exception {
+ ProcessRecord app = startProcessAndWait(true);
+
+ verify(app).killLocked(any(), anyInt(), anyBoolean());
+ }
+
+ private ProcessRecord startProcessAndWait(boolean wedge) throws Exception {
+ final ProcessRecord app = makeActiveProcessRecord(PACKAGE, wedge);
+ final ApplicationInfo appInfo = makeApplicationInfo(PACKAGE);
+
+ mProcessList.handleProcessStartedLocked(app, app.getPid(), /* usingWrapper */ false,
+ /* expectedStartSeq */ 0, /* procAttached */ false);
+
+ app.getThread().bindApplication(PACKAGE, appInfo,
+ null, null,
+ null,
+ null,
+ null, null,
+ null,
+ null, 0,
+ false, false,
+ true, false,
+ null,
+ null, null,
+ null,
+ null, null, null,
+ null, null,
+ 0, 0);
+
+ // Sleep until timeout should have triggered
+ SystemClock.sleep(ActivityManagerService.PROC_START_TIMEOUT + 1000);
+
+ return app;
+ }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java b/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java
index d41ac70..395e6ac 100644
--- a/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java
@@ -161,6 +161,10 @@
when(mMockedResources.getIntArray(
com.android.internal.R.array.config_autoBrightnessLevels))
.thenReturn(new int[]{});
+ when(mMockedResources.obtainTypedArray(R.array.config_displayShapeArray))
+ .thenReturn(mockArray);
+ when(mMockedResources.obtainTypedArray(R.array.config_builtInDisplayIsRoundArray))
+ .thenReturn(mockArray);
}
@After
diff --git a/services/tests/mockingservicestests/src/com/android/server/utils/SlogfTest.java b/services/tests/mockingservicestests/src/com/android/server/utils/SlogfTest.java
index ae25c1b..cb59d37 100644
--- a/services/tests/mockingservicestests/src/com/android/server/utils/SlogfTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/utils/SlogfTest.java
@@ -44,7 +44,7 @@
private MockitoSession mSession;
- private final Exception mException = new Exception("D'OH!");
+ private final Throwable mThrowable = new Throwable("D'OH!");
@Before
public void setup() {
@@ -78,10 +78,10 @@
}
@Test
- public void testV_msgAndException() {
- Slogf.v(TAG, "msg", mException);
+ public void testV_msgAndThrowable() {
+ Slogf.v(TAG, "msg", mThrowable);
- verify(()-> Slog.v(TAG, "msg", mException));
+ verify(()-> Slog.v(TAG, "msg", mThrowable));
}
@Test
@@ -103,12 +103,12 @@
}
@Test
- public void testV_msgFormattedWithException_enabled() {
+ public void testV_msgFormattedWithThrowable_enabled() {
enableLogging(Log.VERBOSE);
- Slogf.v(TAG, mException, "msg in a %s", "bottle");
+ Slogf.v(TAG, mThrowable, "msg in a %s", "bottle");
- verify(()-> Slog.v(TAG, "msg in a bottle", mException));
+ verify(()-> Slog.v(TAG, "msg in a bottle", mThrowable));
}
@Test
@@ -128,10 +128,10 @@
}
@Test
- public void testD_msgAndException() {
- Slogf.d(TAG, "msg", mException);
+ public void testD_msgAndThrowable() {
+ Slogf.d(TAG, "msg", mThrowable);
- verify(()-> Slog.d(TAG, "msg", mException));
+ verify(()-> Slog.d(TAG, "msg", mThrowable));
}
@Test
@@ -153,19 +153,19 @@
}
@Test
- public void testD_msgFormattedWithException_enabled() {
+ public void testD_msgFormattedWithThrowable_enabled() {
enableLogging(Log.DEBUG);
- Slogf.d(TAG, mException, "msg in a %s", "bottle");
+ Slogf.d(TAG, mThrowable, "msg in a %s", "bottle");
- verify(()-> Slog.d(TAG, "msg in a bottle", mException));
+ verify(()-> Slog.d(TAG, "msg in a bottle", mThrowable));
}
@Test
public void testD_msgFormattedWithException_disabled() {
disableLogging(Log.DEBUG);
- Slogf.d(TAG, mException, "msg in a %s", "bottle");
+ Slogf.d(TAG, mThrowable, "msg in a %s", "bottle");
verify(()-> Slog.d(eq(TAG), any(String.class), any(Throwable.class)), never());
}
@@ -178,10 +178,10 @@
}
@Test
- public void testI_msgAndException() {
- Slogf.i(TAG, "msg", mException);
+ public void testI_msgAndThrowable() {
+ Slogf.i(TAG, "msg", mThrowable);
- verify(()-> Slog.i(TAG, "msg", mException));
+ verify(()-> Slog.i(TAG, "msg", mThrowable));
}
@Test
@@ -203,19 +203,19 @@
}
@Test
- public void testI_msgFormattedWithException_enabled() {
+ public void testI_msgFormattedWithThrowable_enabled() {
enableLogging(Log.INFO);
- Slogf.i(TAG, mException, "msg in a %s", "bottle");
+ Slogf.i(TAG, mThrowable, "msg in a %s", "bottle");
- verify(()-> Slog.i(TAG, "msg in a bottle", mException));
+ verify(()-> Slog.i(TAG, "msg in a bottle", mThrowable));
}
@Test
public void testI_msgFormattedWithException_disabled() {
disableLogging(Log.INFO);
- Slogf.i(TAG, mException, "msg in a %s", "bottle");
+ Slogf.i(TAG, mThrowable, "msg in a %s", "bottle");
verify(()-> Slog.i(eq(TAG), any(String.class), any(Throwable.class)), never());
}
@@ -228,17 +228,17 @@
}
@Test
- public void testW_msgAndException() {
- Slogf.w(TAG, "msg", mException);
+ public void testW_msgAndThrowable() {
+ Slogf.w(TAG, "msg", mThrowable);
- verify(()-> Slog.w(TAG, "msg", mException));
+ verify(()-> Slog.w(TAG, "msg", mThrowable));
}
@Test
- public void testW_exception() {
- Slogf.w(TAG, mException);
+ public void testW_Throwable() {
+ Slogf.w(TAG, mThrowable);
- verify(()-> Slog.w(TAG, mException));
+ verify(()-> Slog.w(TAG, mThrowable));
}
@Test
@@ -260,19 +260,19 @@
}
@Test
- public void testW_msgFormattedWithException_enabled() {
+ public void testW_msgFormattedWithThrowable_enabled() {
enableLogging(Log.WARN);
- Slogf.w(TAG, mException, "msg in a %s", "bottle");
+ Slogf.w(TAG, mThrowable, "msg in a %s", "bottle");
- verify(()-> Slog.w(TAG, "msg in a bottle", mException));
+ verify(()-> Slog.w(TAG, "msg in a bottle", mThrowable));
}
@Test
public void testW_msgFormattedWithException_disabled() {
disableLogging(Log.WARN);
- Slogf.w(TAG, mException, "msg in a %s", "bottle");
+ Slogf.w(TAG, mThrowable, "msg in a %s", "bottle");
verify(()-> Slog.w(eq(TAG), any(String.class), any(Throwable.class)), never());
}
@@ -285,10 +285,10 @@
}
@Test
- public void testE_msgAndException() {
- Slogf.e(TAG, "msg", mException);
+ public void testE_msgAndThrowable() {
+ Slogf.e(TAG, "msg", mThrowable);
- verify(()-> Slog.e(TAG, "msg", mException));
+ verify(()-> Slog.e(TAG, "msg", mThrowable));
}
@Test
@@ -310,19 +310,19 @@
}
@Test
- public void testE_msgFormattedWithException_enabled() {
+ public void testE_msgFormattedWithThrowable_enabled() {
enableLogging(Log.ERROR);
- Slogf.e(TAG, mException, "msg in a %s", "bottle");
+ Slogf.e(TAG, mThrowable, "msg in a %s", "bottle");
- verify(()-> Slog.e(TAG, "msg in a bottle", mException));
+ verify(()-> Slog.e(TAG, "msg in a bottle", mThrowable));
}
@Test
public void testE_msgFormattedWithException_disabled() {
disableLogging(Log.ERROR);
- Slogf.e(TAG, mException, "msg in a %s", "bottle");
+ Slogf.e(TAG, mThrowable, "msg in a %s", "bottle");
verify(()-> Slog.e(eq(TAG), any(String.class), any(Throwable.class)), never());
}
@@ -335,17 +335,17 @@
}
@Test
- public void testWtf_msgAndException() {
- Slogf.wtf(TAG, "msg", mException);
+ public void testWtf_msgAndThrowable() {
+ Slogf.wtf(TAG, "msg", mThrowable);
- verify(()-> Slog.wtf(TAG, "msg", mException));
+ verify(()-> Slog.wtf(TAG, "msg", mThrowable));
}
@Test
- public void testWtf_exception() {
- Slogf.wtf(TAG, mException);
+ public void testWtf_Throwable() {
+ Slogf.wtf(TAG, mThrowable);
- verify(()-> Slog.wtf(TAG, mException));
+ verify(()-> Slog.wtf(TAG, mThrowable));
}
@Test
@@ -377,10 +377,10 @@
}
@Test
- public void testWtf_msgFormattedWithException() {
- Slogf.wtf(TAG, mException, "msg in a %s", "bottle");
+ public void testWtf_msgFormattedWithThrowable() {
+ Slogf.wtf(TAG, mThrowable, "msg in a %s", "bottle");
- verify(()-> Slog.wtf(TAG, "msg in a bottle", mException));
+ verify(()-> Slog.wtf(TAG, "msg in a bottle", mThrowable));
}
private void enableLogging(@Log.Level int level) {
diff --git a/services/tests/servicestests/src/com/android/server/DockObserverTest.java b/services/tests/servicestests/src/com/android/server/DockObserverTest.java
index c325778..ee09074 100644
--- a/services/tests/servicestests/src/com/android/server/DockObserverTest.java
+++ b/services/tests/servicestests/src/com/android/server/DockObserverTest.java
@@ -20,6 +20,7 @@
import android.content.Intent;
import android.os.Looper;
+import android.provider.Settings;
import android.testing.AndroidTestingRunner;
import android.testing.TestableContext;
import android.testing.TestableLooper;
@@ -74,6 +75,11 @@
.isEqualTo(Intent.EXTRA_DOCK_STATE_UNDOCKED);
}
+ void setDeviceProvisioned(boolean provisioned) {
+ Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.DEVICE_PROVISIONED,
+ provisioned ? 1 : 0);
+ }
+
@Before
public void setUp() {
if (Looper.myLooper() == null) {
@@ -131,4 +137,25 @@
assertDockEventIntentWithExtraThenUndock(observer, "DOCK=1\nKEY5=5",
Intent.EXTRA_DOCK_STATE_HE_DESK);
}
+
+ @Test
+ public void testDockIntentBroadcast_deviceNotProvisioned()
+ throws ExecutionException, InterruptedException {
+ DockObserver observer = new DockObserver(mInterceptingContext);
+ // Set the device as not provisioned.
+ setDeviceProvisioned(false);
+ observer.onBootPhase(SystemService.PHASE_ACTIVITY_MANAGER_READY);
+
+ BroadcastInterceptingContext.FutureIntent futureIntent =
+ updateExtconDockState(observer, "DOCK=1");
+ TestableLooper.get(this).processAllMessages();
+ // Verify no broadcast was sent as device was not provisioned.
+ futureIntent.assertNotReceived();
+
+ // Ensure we send the broadcast when the device is provisioned.
+ setDeviceProvisioned(true);
+ TestableLooper.get(this).processAllMessages();
+ assertThat(futureIntent.get().getIntExtra(Intent.EXTRA_DOCK_STATE, -1))
+ .isEqualTo(Intent.EXTRA_DOCK_STATE_DESK);
+ }
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryJobServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryJobServiceTest.java
index af10b9d..d758e71 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryJobServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryJobServiceTest.java
@@ -52,9 +52,9 @@
@RunWith(AndroidTestingRunner.class)
public class NotificationHistoryJobServiceTest extends UiServiceTestCase {
private NotificationHistoryJobService mJobService;
- private JobParameters mJobParams = new JobParameters(null,
- NotificationHistoryJobService.BASE_JOB_ID, null, null, null,
- 0, false, false, null, null, null);
+
+ @Mock
+ private JobParameters mJobParams;
@Captor
ArgumentCaptor<JobInfo> mJobInfoCaptor;
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ReviewNotificationPermissionsJobServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ReviewNotificationPermissionsJobServiceTest.java
index 3a6c0eb..a83eb00 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ReviewNotificationPermissionsJobServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ReviewNotificationPermissionsJobServiceTest.java
@@ -44,9 +44,9 @@
@RunWith(AndroidTestingRunner.class)
public class ReviewNotificationPermissionsJobServiceTest extends UiServiceTestCase {
private ReviewNotificationPermissionsJobService mJobService;
- private JobParameters mJobParams = new JobParameters(null,
- ReviewNotificationPermissionsJobService.JOB_ID, null, null, null,
- 0, false, false, null, null, null);
+
+ @Mock
+ private JobParameters mJobParams;
@Captor
ArgumentCaptor<JobInfo> mJobInfoCaptor;
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
index 70b68c7..6733470 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
@@ -133,8 +133,8 @@
final RoundedCorners roundedCorners = mHasRoundedCorners
? mDisplayContent.calculateRoundedCornersForRotation(mRotation)
: RoundedCorners.NO_ROUNDED_CORNERS;
- return new DisplayFrames(insetsState, info,
- info.displayCutout, roundedCorners, new PrivacyIndicatorBounds());
+ return new DisplayFrames(insetsState, info, info.displayCutout, roundedCorners,
+ new PrivacyIndicatorBounds(), info.displayShape);
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
index d99946f..10f2270 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
@@ -58,6 +58,7 @@
import android.graphics.Rect;
import android.platform.test.annotations.Presubmit;
import android.view.DisplayInfo;
+import android.view.DisplayShape;
import android.view.InsetsSource;
import android.view.InsetsState;
import android.view.PrivacyIndicatorBounds;
@@ -321,7 +322,8 @@
final InsetsState state = mDisplayContent.getInsetsStateController().getRawInsetsState();
mImeWindow.mAboveInsetsState.set(state);
mDisplayContent.mDisplayFrames = new DisplayFrames(
- state, displayInfo, NO_CUTOUT, NO_ROUNDED_CORNERS, new PrivacyIndicatorBounds());
+ state, displayInfo, NO_CUTOUT, NO_ROUNDED_CORNERS, new PrivacyIndicatorBounds(),
+ DisplayShape.NONE);
mDisplayContent.setInputMethodWindowLocked(mImeWindow);
mImeWindow.mAttrs.setFitInsetsSides(Side.all() & ~Side.BOTTOM);
diff --git a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
index aab70b5..94b5b93 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
@@ -54,6 +54,7 @@
import android.platform.test.annotations.Presubmit;
import android.view.DisplayCutout;
import android.view.DisplayInfo;
+import android.view.DisplayShape;
import android.view.Gravity;
import android.view.InsetsState;
import android.view.PrivacyIndicatorBounds;
@@ -165,7 +166,8 @@
final DisplayInfo info = dc.computeScreenConfiguration(config, Surface.ROTATION_0);
final DisplayCutout cutout = dc.calculateDisplayCutoutForRotation(Surface.ROTATION_0);
final DisplayFrames displayFrames = new DisplayFrames(new InsetsState(),
- info, cutout, RoundedCorners.NO_ROUNDED_CORNERS, new PrivacyIndicatorBounds());
+ info, cutout, RoundedCorners.NO_ROUNDED_CORNERS, new PrivacyIndicatorBounds(),
+ DisplayShape.NONE);
wallpaperWindow.mToken.applyFixedRotationTransform(info, displayFrames, config);
// Check that the wallpaper has the same frame in landscape than in portrait
diff --git a/services/usb/Android.bp b/services/usb/Android.bp
index 52cfe25..133f924 100644
--- a/services/usb/Android.bp
+++ b/services/usb/Android.bp
@@ -33,5 +33,6 @@
"android.hardware.usb.gadget-V1.0-java",
"android.hardware.usb.gadget-V1.1-java",
"android.hardware.usb.gadget-V1.2-java",
+ "android.hardware.usb.gadget-V1-java",
],
}
diff --git a/services/usb/java/com/android/server/usb/UsbDeviceManager.java b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
index 1c081c1..ffdb07b 100644
--- a/services/usb/java/com/android/server/usb/UsbDeviceManager.java
+++ b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
@@ -44,6 +44,7 @@
import android.debug.AdbNotifications;
import android.debug.AdbTransportType;
import android.debug.IAdbTransport;
+import android.hardware.usb.IUsbOperationInternal;
import android.hardware.usb.ParcelableUsbPort;
import android.hardware.usb.UsbAccessory;
import android.hardware.usb.UsbConfiguration;
@@ -54,9 +55,7 @@
import android.hardware.usb.UsbPort;
import android.hardware.usb.UsbPortStatus;
import android.hardware.usb.gadget.V1_0.GadgetFunction;
-import android.hardware.usb.gadget.V1_0.IUsbGadget;
import android.hardware.usb.gadget.V1_0.Status;
-import android.hardware.usb.gadget.V1_2.IUsbGadgetCallback;
import android.hardware.usb.gadget.V1_2.UsbSpeed;
import android.hidl.manager.V1_0.IServiceManager;
import android.hidl.manager.V1_0.IServiceNotification;
@@ -88,9 +87,12 @@
import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
import com.android.internal.notification.SystemNotificationChannels;
import com.android.internal.os.SomeArgs;
+import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.dump.DualDumpOutputStream;
import com.android.server.FgThread;
import com.android.server.LocalServices;
+import com.android.server.usb.hal.gadget.UsbGadgetHal;
+import com.android.server.usb.hal.gadget.UsbGadgetHalInstance;
import com.android.server.utils.EventLogger;
import com.android.server.wm.ActivityTaskManagerInternal;
@@ -106,6 +108,7 @@
import java.util.NoSuchElementException;
import java.util.Scanner;
import java.util.Set;
+import java.util.concurrent.atomic.AtomicInteger;
/**
* UsbDeviceManager manages USB state in device mode.
@@ -216,6 +219,13 @@
private static EventLogger sEventLogger;
+ private UsbGadgetHal mUsbGadgetHal;
+
+ /**
+ * Counter for tracking UsbOperation operations.
+ */
+ private static final AtomicInteger sUsbOperationCount = new AtomicInteger();
+
static {
sDenyInterfaces = new HashSet<>();
sDenyInterfaces.add(UsbConstants.USB_CLASS_AUDIO);
@@ -298,15 +308,11 @@
mHasUsbAccessory = pm.hasSystemFeature(PackageManager.FEATURE_USB_ACCESSORY);
initRndisAddress();
+ int operationId = sUsbOperationCount.incrementAndGet();
boolean halNotPresent = false;
- try {
- IUsbGadget.getService(true);
- } catch (RemoteException e) {
- Slog.e(TAG, "USB GADGET HAL present but exception thrown", e);
- } catch (NoSuchElementException e) {
- halNotPresent = true;
- Slog.i(TAG, "USB GADGET HAL not present in the device", e);
- }
+
+ mUsbGadgetHal = UsbGadgetHalInstance.getInstance(this, null);
+ Slog.d(TAG, "getInstance done");
mControlFds = new HashMap<>();
FileDescriptor mtpFd = nativeOpenControl(UsbManager.USB_FUNCTION_MTP);
@@ -320,7 +326,7 @@
}
mControlFds.put(UsbManager.FUNCTION_PTP, ptpFd);
- if (halNotPresent) {
+ if (mUsbGadgetHal == null) {
/**
* Initialze the legacy UsbHandler
*/
@@ -334,6 +340,8 @@
alsaManager, permissionManager);
}
+ mHandler.handlerInitDone(operationId);
+
if (nativeIsStartRequested()) {
if (DEBUG) Slog.d(TAG, "accessory attached at boot");
startAccessoryMode();
@@ -455,6 +463,8 @@
private void startAccessoryMode() {
if (!mHasUsbAccessory) return;
+ int operationId = sUsbOperationCount.incrementAndGet();
+
mAccessoryStrings = nativeGetAccessoryStrings();
boolean enableAudio = (nativeGetAudioMode() == AUDIO_MODE_SOURCE);
// don't start accessory mode if our mandatory strings have not been set
@@ -475,7 +485,7 @@
ACCESSORY_REQUEST_TIMEOUT);
mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_ACCESSORY_HANDSHAKE_TIMEOUT),
ACCESSORY_HANDSHAKE_TIMEOUT);
- setCurrentFunctions(functions);
+ setCurrentFunctions(functions, operationId);
}
}
@@ -504,6 +514,20 @@
}
}
+ public static void logAndPrint(int priority, IndentingPrintWriter pw, String msg) {
+ Slog.println(priority, TAG, msg);
+ if (pw != null) {
+ pw.println(msg);
+ }
+ }
+
+ public static void logAndPrintException(IndentingPrintWriter pw, String msg, Exception e) {
+ Slog.e(TAG, msg, e);
+ if (pw != null) {
+ pw.println(msg + e);
+ }
+ }
+
abstract static class UsbHandler extends Handler {
// current USB state
@@ -608,6 +632,19 @@
sendMessage(m);
}
+ public boolean sendMessage(int what) {
+ removeMessages(what);
+ Message m = Message.obtain(this, what);
+ return sendMessageDelayed(m,0);
+ }
+
+ public void sendMessage(int what, int operationId) {
+ removeMessages(what);
+ Message m = Message.obtain(this, what);
+ m.arg1 = operationId;
+ sendMessage(m);
+ }
+
public void sendMessage(int what, Object arg) {
removeMessages(what);
Message m = Message.obtain(this, what);
@@ -615,6 +652,22 @@
sendMessage(m);
}
+ public void sendMessage(int what, Object arg, int operationId) {
+ removeMessages(what);
+ Message m = Message.obtain(this, what);
+ m.obj = arg;
+ m.arg1 = operationId;
+ sendMessage(m);
+ }
+
+ public void sendMessage(int what, boolean arg, int operationId) {
+ removeMessages(what);
+ Message m = Message.obtain(this, what);
+ m.arg1 = (arg ? 1 : 0);
+ m.arg2 = operationId;
+ sendMessage(m);
+ }
+
public void sendMessage(int what, Object arg, boolean arg1) {
removeMessages(what);
Message m = Message.obtain(this, what);
@@ -623,6 +676,15 @@
sendMessage(m);
}
+ public void sendMessage(int what, long arg, boolean arg1, int operationId) {
+ removeMessages(what);
+ Message m = Message.obtain(this, what);
+ m.obj = arg;
+ m.arg1 = (arg1 ? 1 : 0);
+ m.arg2 = operationId;
+ sendMessage(m);
+ }
+
public void sendMessage(int what, boolean arg1, boolean arg2) {
removeMessages(what);
Message m = Message.obtain(this, what);
@@ -680,7 +742,7 @@
sendMessageDelayed(msg, HOST_STATE_UPDATE_DELAY);
}
- private void setAdbEnabled(boolean enable) {
+ private void setAdbEnabled(boolean enable, int operationId) {
if (DEBUG) Slog.d(TAG, "setAdbEnabled: " + enable);
if (enable) {
@@ -689,7 +751,7 @@
setSystemProperty(USB_PERSISTENT_CONFIG_PROPERTY, "");
}
- setEnabledFunctions(mCurrentFunctions, true);
+ setEnabledFunctions(mCurrentFunctions, true, operationId);
updateAdbNotification(false);
}
@@ -701,6 +763,8 @@
private void updateCurrentAccessory() {
// We are entering accessory mode if we have received a request from the host
// and the request has not timed out yet.
+ int operationId = sUsbOperationCount.incrementAndGet();
+
boolean enteringAccessoryMode = hasMessages(MSG_ACCESSORY_MODE_ENTER_TIMEOUT);
if (mConfigured && enteringAccessoryMode) {
@@ -732,18 +796,18 @@
}
} else {
if (!enteringAccessoryMode) {
- notifyAccessoryModeExit();
+ notifyAccessoryModeExit(operationId);
} else if (DEBUG) {
Slog.v(TAG, "Debouncing accessory mode exit");
}
}
}
- private void notifyAccessoryModeExit() {
+ private void notifyAccessoryModeExit(int operationId) {
// make sure accessory mode is off
// and restore default functions
Slog.d(TAG, "exited USB accessory mode");
- setEnabledFunctions(UsbManager.FUNCTION_NONE, false);
+ setEnabledFunctions(UsbManager.FUNCTION_NONE, false, operationId);
if (mCurrentAccessory != null) {
if (mBootCompleted) {
@@ -869,8 +933,8 @@
mMidiEnabled && mConfigured, mMidiCard, mMidiDevice);
}
- private void setScreenUnlockedFunctions() {
- setEnabledFunctions(mScreenUnlockedFunctions, false);
+ private void setScreenUnlockedFunctions(int operationId) {
+ setEnabledFunctions(mScreenUnlockedFunctions, false, operationId);
}
private static class AdbTransport extends IAdbTransport.Stub {
@@ -883,7 +947,8 @@
@Override
public void onAdbEnabled(boolean enabled, byte transportType) {
if (transportType == AdbTransportType.USB) {
- mHandler.sendMessage(MSG_ENABLE_ADB, enabled);
+ int operationId = sUsbOperationCount.incrementAndGet();
+ mHandler.sendMessage(MSG_ENABLE_ADB, enabled, operationId);
}
}
}
@@ -906,6 +971,7 @@
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_UPDATE_STATE:
+ int operationId = sUsbOperationCount.incrementAndGet();
mConnected = (msg.arg1 == 1);
mConfigured = (msg.arg2 == 1);
@@ -923,9 +989,9 @@
// restore defaults when USB is disconnected
if (!mScreenLocked
&& mScreenUnlockedFunctions != UsbManager.FUNCTION_NONE) {
- setScreenUnlockedFunctions();
+ setScreenUnlockedFunctions(operationId);
} else {
- setEnabledFunctions(UsbManager.FUNCTION_NONE, false);
+ setEnabledFunctions(UsbManager.FUNCTION_NONE, false, operationId);
}
}
updateUsbFunctions();
@@ -1036,13 +1102,15 @@
updateUsbNotification(false);
break;
case MSG_ENABLE_ADB:
- setAdbEnabled(msg.arg1 == 1);
+ setAdbEnabled(msg.arg1 == 1, msg.arg2);
break;
case MSG_SET_CURRENT_FUNCTIONS:
long functions = (Long) msg.obj;
- setEnabledFunctions(functions, false);
+ operationId = (int) msg.arg1;
+ setEnabledFunctions(functions, false, operationId);
break;
case MSG_SET_SCREEN_UNLOCKED_FUNCTIONS:
+ operationId = sUsbOperationCount.incrementAndGet();
mScreenUnlockedFunctions = (Long) msg.obj;
if (mSettings != null) {
SharedPreferences.Editor editor = mSettings.edit();
@@ -1053,12 +1121,13 @@
}
if (!mScreenLocked && mScreenUnlockedFunctions != UsbManager.FUNCTION_NONE) {
// If the screen is unlocked, also set current functions.
- setScreenUnlockedFunctions();
+ setScreenUnlockedFunctions(operationId);
} else {
- setEnabledFunctions(UsbManager.FUNCTION_NONE, false);
+ setEnabledFunctions(UsbManager.FUNCTION_NONE, false, operationId);
}
break;
case MSG_UPDATE_SCREEN_LOCK:
+ operationId = sUsbOperationCount.incrementAndGet();
if (msg.arg1 == 1 == mScreenLocked) {
break;
}
@@ -1068,23 +1137,25 @@
}
if (mScreenLocked) {
if (!mConnected) {
- setEnabledFunctions(UsbManager.FUNCTION_NONE, false);
+ setEnabledFunctions(UsbManager.FUNCTION_NONE, false, operationId);
}
} else {
if (mScreenUnlockedFunctions != UsbManager.FUNCTION_NONE
&& mCurrentFunctions == UsbManager.FUNCTION_NONE) {
// Set the screen unlocked functions if current function is charging.
- setScreenUnlockedFunctions();
+ setScreenUnlockedFunctions(operationId);
}
}
break;
case MSG_UPDATE_USER_RESTRICTIONS:
+ operationId = sUsbOperationCount.incrementAndGet();
// Restart the USB stack if USB transfer is enabled but no longer allowed.
if (isUsbDataTransferActive(mCurrentFunctions) && !isUsbTransferAllowed()) {
- setEnabledFunctions(UsbManager.FUNCTION_NONE, true);
+ setEnabledFunctions(UsbManager.FUNCTION_NONE, true, operationId);
}
break;
case MSG_SYSTEM_READY:
+ operationId = sUsbOperationCount.incrementAndGet();
mNotificationManager = (NotificationManager)
mContext.getSystemService(Context.NOTIFICATION_SERVICE);
@@ -1102,17 +1173,19 @@
NotificationManager.IMPORTANCE_HIGH));
}
mSystemReady = true;
- finishBoot();
+ finishBoot(operationId);
break;
case MSG_LOCALE_CHANGED:
updateAdbNotification(true);
updateUsbNotification(true);
break;
case MSG_BOOT_COMPLETED:
+ operationId = sUsbOperationCount.incrementAndGet();
mBootCompleted = true;
- finishBoot();
+ finishBoot(operationId);
break;
case MSG_USER_SWITCHED: {
+ operationId = sUsbOperationCount.incrementAndGet();
if (mCurrentUser != msg.arg1) {
if (DEBUG) {
Slog.v(TAG, "Current user switched to " + msg.arg1);
@@ -1125,16 +1198,18 @@
mSettings.getString(String.format(Locale.ENGLISH,
UNLOCKED_CONFIG_PREF, mCurrentUser), ""));
}
- setEnabledFunctions(UsbManager.FUNCTION_NONE, false);
+ setEnabledFunctions(UsbManager.FUNCTION_NONE, false, operationId);
}
break;
}
case MSG_ACCESSORY_MODE_ENTER_TIMEOUT: {
+ operationId = sUsbOperationCount.incrementAndGet();
if (DEBUG) {
- Slog.v(TAG, "Accessory mode enter timeout: " + mConnected);
+ Slog.v(TAG, "Accessory mode enter timeout: " + mConnected
+ + " ,operationId: " + operationId);
}
if (!mConnected || (mCurrentFunctions & UsbManager.FUNCTION_ACCESSORY) == 0) {
- notifyAccessoryModeExit();
+ notifyAccessoryModeExit(operationId);
}
break;
}
@@ -1157,7 +1232,9 @@
}
}
- protected void finishBoot() {
+ public abstract void handlerInitDone(int operationId);
+
+ protected void finishBoot(int operationId) {
if (mBootCompleted && mCurrentUsbFunctionsReceived && mSystemReady) {
if (mPendingBootBroadcast) {
updateUsbStateBroadcastIfNeeded(getAppliedFunctions(mCurrentFunctions));
@@ -1165,9 +1242,9 @@
}
if (!mScreenLocked
&& mScreenUnlockedFunctions != UsbManager.FUNCTION_NONE) {
- setScreenUnlockedFunctions();
+ setScreenUnlockedFunctions(operationId);
} else {
- setEnabledFunctions(UsbManager.FUNCTION_NONE, false);
+ setEnabledFunctions(UsbManager.FUNCTION_NONE, false, operationId);
}
if (mCurrentAccessory != null) {
mUsbDeviceManager.getCurrentSettings().accessoryAttached(mCurrentAccessory);
@@ -1507,7 +1584,8 @@
/**
* Evaluates USB function policies and applies the change accordingly.
*/
- protected abstract void setEnabledFunctions(long functions, boolean forceRestart);
+ protected abstract void setEnabledFunctions(long functions,
+ boolean forceRestart, int operationId);
public void setAccessoryUEventTime(long accessoryConnectionStartTime) {
mAccessoryConnectionStartTime = accessoryConnectionStartTime;
@@ -1522,6 +1600,11 @@
mSendStringCount = 0;
mStartAccessory = false;
}
+
+ public abstract void setCurrentUsbFunctionsCb(long functions,
+ int status, int mRequest, long mFunctions, boolean mChargingFunctions);
+
+ public abstract void getUsbSpeedCb(int speed);
}
private static final class UsbHandlerLegacy extends UsbHandler {
@@ -1540,6 +1623,11 @@
private String mCurrentFunctionsStr;
private boolean mUsbDataUnlocked;
+ /**
+ * Keeps track of the latest setCurrentUsbFunctions request number.
+ */
+ private int mCurrentRequest = 0;
+
UsbHandlerLegacy(Looper looper, Context context, UsbDeviceManager deviceManager,
UsbAlsaManager alsaManager, UsbPermissionManager permissionManager) {
super(looper, context, deviceManager, alsaManager, permissionManager);
@@ -1573,6 +1661,10 @@
}
}
+ @Override
+ public void handlerInitDone(int operationId) {
+ }
+
private void readOemUsbOverrideConfig(Context context) {
String[] configList = context.getResources().getStringArray(
com.android.internal.R.array.config_oemUsbModeOverride);
@@ -1675,11 +1767,14 @@
}
@Override
- protected void setEnabledFunctions(long usbFunctions, boolean forceRestart) {
+ protected void setEnabledFunctions(long usbFunctions,
+ boolean forceRestart, int operationId) {
boolean usbDataUnlocked = isUsbDataTransferActive(usbFunctions);
if (DEBUG) {
- Slog.d(TAG, "setEnabledFunctions functions=" + usbFunctions + ", "
- + "forceRestart=" + forceRestart + ", usbDataUnlocked=" + usbDataUnlocked);
+ Slog.d(TAG, "setEnabledFunctions functions=" + usbFunctions +
+ " ,forceRestart=" + forceRestart +
+ " ,usbDataUnlocked=" + usbDataUnlocked +
+ " ,operationId=" + operationId);
}
if (usbDataUnlocked != mUsbDataUnlocked) {
@@ -1775,7 +1870,6 @@
|| !mCurrentFunctionsStr.equals(functions)
|| !mCurrentFunctionsApplied
|| forceRestart) {
- Slog.i(TAG, "Setting USB config to " + functions);
mCurrentFunctionsStr = functions;
mCurrentOemFunctions = oemFunctions;
mCurrentFunctionsApplied = false;
@@ -1871,15 +1965,18 @@
if (charAfter < functions.length() && functions.charAt(charAfter) != ',') return false;
return true;
}
+
+ @Override
+ public void setCurrentUsbFunctionsCb(long functions,
+ int status, int mRequest, long mFunctions, boolean mChargingFunctions){
+ }
+
+ @Override
+ public void getUsbSpeedCb(int speed){
+ }
}
- private static final class UsbHandlerHal extends UsbHandler {
-
- /**
- * Proxy object for the usb gadget hal daemon.
- */
- @GuardedBy("mGadgetProxyLock")
- private IUsbGadget mGadgetProxy;
+ private final class UsbHandlerHal extends UsbHandler {
private final Object mGadgetProxyLock = new Object();
@@ -1926,33 +2023,20 @@
UsbHandlerHal(Looper looper, Context context, UsbDeviceManager deviceManager,
UsbAlsaManager alsaManager, UsbPermissionManager permissionManager) {
super(looper, context, deviceManager, alsaManager, permissionManager);
+ int operationId = sUsbOperationCount.incrementAndGet();
try {
- ServiceNotification serviceNotification = new ServiceNotification();
-
- boolean ret = IServiceManager.getService()
- .registerForNotifications(GADGET_HAL_FQ_NAME, "", serviceNotification);
- if (!ret) {
- Slog.e(TAG, "Failed to register usb gadget service start notification");
- return;
- }
synchronized (mGadgetProxyLock) {
- mGadgetProxy = IUsbGadget.getService(true);
- mGadgetProxy.linkToDeath(new UsbGadgetDeathRecipient(),
- USB_GADGET_HAL_DEATH_COOKIE);
mCurrentFunctions = UsbManager.FUNCTION_NONE;
mCurrentUsbFunctionsRequested = true;
mUsbSpeed = UsbSpeed.UNKNOWN;
mCurrentGadgetHalVersion = UsbManager.GADGET_HAL_V1_0;
- mGadgetProxy.getCurrentUsbFunctions(new UsbGadgetCallback());
+ updateUsbGadgetHalVersion();
}
String state = FileUtils.readTextFile(new File(STATE_PATH), 0, null).trim();
updateState(state);
- updateUsbGadgetHalVersion();
} catch (NoSuchElementException e) {
Slog.e(TAG, "Usb gadget hal not found", e);
- } catch (RemoteException e) {
- Slog.e(TAG, "Usb Gadget hal not responding", e);
} catch (Exception e) {
Slog.e(TAG, "Error initializing UsbHandler", e);
}
@@ -1965,7 +2049,7 @@
if (cookie == USB_GADGET_HAL_DEATH_COOKIE) {
Slog.e(TAG, "Usb Gadget hal service died cookie: " + cookie);
synchronized (mGadgetProxyLock) {
- mGadgetProxy = null;
+ mUsbGadgetHal = null;
}
}
}
@@ -1988,18 +2072,22 @@
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_SET_CHARGING_FUNCTIONS:
- setEnabledFunctions(UsbManager.FUNCTION_NONE, false);
+ int operationId = sUsbOperationCount.incrementAndGet();
+ setEnabledFunctions(UsbManager.FUNCTION_NONE, false, operationId);
break;
case MSG_SET_FUNCTIONS_TIMEOUT:
- Slog.e(TAG, "Set functions timed out! no reply from usb hal");
+ operationId = sUsbOperationCount.incrementAndGet();
+ Slog.e(TAG, "Set functions timed out! no reply from usb hal"
+ + " ,operationId:" + operationId);
if (msg.arg1 != 1) {
// Set this since default function may be selected from Developer options
- setEnabledFunctions(mScreenUnlockedFunctions, false);
+ setEnabledFunctions(mScreenUnlockedFunctions, false, operationId);
}
break;
case MSG_GET_CURRENT_USB_FUNCTIONS:
Slog.i(TAG, "processing MSG_GET_CURRENT_USB_FUNCTIONS");
mCurrentUsbFunctionsReceived = true;
+ operationId = msg.arg2;
if (mCurrentUsbFunctionsRequested) {
Slog.i(TAG, "updating mCurrentFunctions");
@@ -2009,91 +2097,71 @@
"mCurrentFunctions:" + mCurrentFunctions + "applied:" + msg.arg1);
mCurrentFunctionsApplied = msg.arg1 == 1;
}
- finishBoot();
+ finishBoot(operationId);
break;
case MSG_FUNCTION_SWITCH_TIMEOUT:
/**
* Dont force to default when the configuration is already set to default.
*/
+ operationId = sUsbOperationCount.incrementAndGet();
if (msg.arg1 != 1) {
// Set this since default function may be selected from Developer options
- setEnabledFunctions(mScreenUnlockedFunctions, false);
+ setEnabledFunctions(mScreenUnlockedFunctions, false, operationId);
}
break;
case MSG_GADGET_HAL_REGISTERED:
boolean preexisting = msg.arg1 == 1;
+ operationId = sUsbOperationCount.incrementAndGet();
synchronized (mGadgetProxyLock) {
try {
- mGadgetProxy = IUsbGadget.getService();
- mGadgetProxy.linkToDeath(new UsbGadgetDeathRecipient(),
- USB_GADGET_HAL_DEATH_COOKIE);
+ mUsbGadgetHal = UsbGadgetHalInstance.getInstance(mUsbDeviceManager,
+ null);
if (!mCurrentFunctionsApplied && !preexisting) {
- setEnabledFunctions(mCurrentFunctions, false);
+ setEnabledFunctions(mCurrentFunctions, false, operationId);
}
} catch (NoSuchElementException e) {
Slog.e(TAG, "Usb gadget hal not found", e);
- } catch (RemoteException e) {
- Slog.e(TAG, "Usb Gadget hal not responding", e);
}
}
break;
case MSG_RESET_USB_GADGET:
synchronized (mGadgetProxyLock) {
- if (mGadgetProxy == null) {
- Slog.e(TAG, "reset Usb Gadget mGadgetProxy is null");
+ if (mUsbGadgetHal == null) {
+ Slog.e(TAG, "reset Usb Gadget mUsbGadgetHal is null");
break;
}
try {
- android.hardware.usb.gadget.V1_1.IUsbGadget gadgetProxy =
- android.hardware.usb.gadget.V1_1.IUsbGadget
- .castFrom(mGadgetProxy);
- gadgetProxy.reset();
- } catch (RemoteException e) {
+ mUsbGadgetHal.reset();
+ } catch (Exception e) {
Slog.e(TAG, "reset Usb Gadget failed", e);
}
}
break;
case MSG_UPDATE_USB_SPEED:
- synchronized (mGadgetProxyLock) {
- if (mGadgetProxy == null) {
- Slog.e(TAG, "mGadgetProxy is null");
- break;
- }
+ operationId = sUsbOperationCount.incrementAndGet();
+ if (mUsbGadgetHal == null) {
+ Slog.e(TAG, "mGadgetHal is null, operationId:" + operationId);
+ break;
+ }
- try {
- android.hardware.usb.gadget.V1_2.IUsbGadget gadgetProxy =
- android.hardware.usb.gadget.V1_2.IUsbGadget
- .castFrom(mGadgetProxy);
- if (gadgetProxy != null) {
- gadgetProxy.getUsbSpeed(new UsbGadgetCallback());
- }
- } catch (RemoteException e) {
- Slog.e(TAG, "get UsbSpeed failed", e);
- }
+ try {
+ mUsbGadgetHal.getUsbSpeed(operationId);
+ } catch (Exception e) {
+ Slog.e(TAG, "get UsbSpeed failed", e);
}
break;
case MSG_UPDATE_HAL_VERSION:
- synchronized (mGadgetProxyLock) {
- if (mGadgetProxy == null) {
- Slog.e(TAG, "mGadgetProxy is null");
- break;
+ if (mUsbGadgetHal == null) {
+ Slog.e(TAG, "mUsbGadgetHal is null");
+ break;
+ }
+ else {
+ try {
+ mCurrentGadgetHalVersion = mUsbGadgetHal.getGadgetHalVersion();
+ } catch (RemoteException e) {
+ Slog.e(TAG, "update Usb gadget version failed", e);
}
-
- android.hardware.usb.gadget.V1_2.IUsbGadget gadgetProxy =
- android.hardware.usb.gadget.V1_2.IUsbGadget.castFrom(mGadgetProxy);
- if (gadgetProxy == null) {
- android.hardware.usb.gadget.V1_1.IUsbGadget gadgetProxyV1By1 =
- android.hardware.usb.gadget.V1_1.IUsbGadget
- .castFrom(mGadgetProxy);
- if (gadgetProxyV1By1 == null) {
- mCurrentGadgetHalVersion = UsbManager.GADGET_HAL_V1_0;
- break;
- }
- mCurrentGadgetHalVersion = UsbManager.GADGET_HAL_V1_1;
- break;
- }
- mCurrentGadgetHalVersion = UsbManager.GADGET_HAL_V1_2;
}
break;
default:
@@ -2101,56 +2169,31 @@
}
}
- private class UsbGadgetCallback extends IUsbGadgetCallback.Stub {
- int mRequest;
- long mFunctions;
- boolean mChargingFunctions;
+ @Override
+ public void setCurrentUsbFunctionsCb(long functions,
+ int status, int mRequest, long mFunctions, boolean mChargingFunctions) {
- UsbGadgetCallback() {
+ if ((mCurrentRequest != mRequest) || !hasMessages(MSG_SET_FUNCTIONS_TIMEOUT)
+ || (mFunctions != functions)) {
+ return;
}
- UsbGadgetCallback(int request, long functions,
- boolean chargingFunctions) {
- mRequest = request;
- mFunctions = functions;
- mChargingFunctions = chargingFunctions;
- }
-
- @Override
- public void setCurrentUsbFunctionsCb(long functions,
- int status) {
- /**
- * Callback called for a previous setCurrenUsbFunction
- */
- if ((mCurrentRequest != mRequest) || !hasMessages(MSG_SET_FUNCTIONS_TIMEOUT)
- || (mFunctions != functions)) {
- return;
- }
-
- removeMessages(MSG_SET_FUNCTIONS_TIMEOUT);
- Slog.e(TAG, "notifyCurrentFunction request:" + mRequest + " status:" + status);
- if (status == Status.SUCCESS) {
- mCurrentFunctionsApplied = true;
- } else if (!mChargingFunctions) {
- Slog.e(TAG, "Setting default fuctions");
- sendEmptyMessage(MSG_SET_CHARGING_FUNCTIONS);
- }
- }
-
- @Override
- public void getCurrentUsbFunctionsCb(long functions,
- int status) {
- sendMessage(MSG_GET_CURRENT_USB_FUNCTIONS, functions,
- status == Status.FUNCTIONS_APPLIED);
- }
-
- @Override
- public void getUsbSpeedCb(int speed) {
- mUsbSpeed = speed;
+ removeMessages(MSG_SET_FUNCTIONS_TIMEOUT);
+ Slog.i(TAG, "notifyCurrentFunction request:" + mRequest + " status:" + status);
+ if (status == Status.SUCCESS) {
+ mCurrentFunctionsApplied = true;
+ } else if (!mChargingFunctions) {
+ Slog.e(TAG, "Setting default fuctions");
+ sendEmptyMessage(MSG_SET_CHARGING_FUNCTIONS);
}
}
- private void setUsbConfig(long config, boolean chargingFunctions) {
+ @Override
+ public void getUsbSpeedCb(int speed) {
+ mUsbSpeed = speed;
+ }
+
+ private void setUsbConfig(long config, boolean chargingFunctions, int operationId) {
if (true) Slog.d(TAG, "setUsbConfig(" + config + ") request:" + ++mCurrentRequest);
/**
* Cancel any ongoing requests, if present.
@@ -2160,8 +2203,8 @@
removeMessages(MSG_SET_CHARGING_FUNCTIONS);
synchronized (mGadgetProxyLock) {
- if (mGadgetProxy == null) {
- Slog.e(TAG, "setUsbConfig mGadgetProxy is null");
+ if (mUsbGadgetHal == null) {
+ Slog.e(TAG, "setUsbConfig mUsbGadgetHal is null");
return;
}
try {
@@ -2178,10 +2221,9 @@
LocalServices.getService(AdbManagerInternal.class)
.stopAdbdForTransport(AdbTransportType.USB);
}
- UsbGadgetCallback usbGadgetCallback = new UsbGadgetCallback(mCurrentRequest,
- config, chargingFunctions);
- mGadgetProxy.setCurrentUsbFunctions(config, usbGadgetCallback,
- SET_FUNCTIONS_TIMEOUT_MS - SET_FUNCTIONS_LEEWAY_MS);
+ mUsbGadgetHal.setCurrentUsbFunctions(mCurrentRequest,
+ config, chargingFunctions,
+ SET_FUNCTIONS_TIMEOUT_MS - SET_FUNCTIONS_LEEWAY_MS, operationId);
sendMessageDelayed(MSG_SET_FUNCTIONS_TIMEOUT, chargingFunctions,
SET_FUNCTIONS_TIMEOUT_MS);
if (mConnected) {
@@ -2190,17 +2232,19 @@
SET_FUNCTIONS_TIMEOUT_MS + ENUMERATION_TIME_OUT_MS);
}
if (DEBUG) Slog.d(TAG, "timeout message queued");
- } catch (RemoteException e) {
+ } catch (Exception e) {//RemoteException e) {
Slog.e(TAG, "Remoteexception while calling setCurrentUsbFunctions", e);
}
}
}
@Override
- protected void setEnabledFunctions(long functions, boolean forceRestart) {
+ protected void setEnabledFunctions(long functions, boolean forceRestart, int operationId) {
if (DEBUG) {
- Slog.d(TAG, "setEnabledFunctions functions=" + functions + ", "
- + "forceRestart=" + forceRestart);
+ Slog.d(TAG, "setEnabledFunctionsi " +
+ "functions=" + functions +
+ ", forceRestart=" + forceRestart +
+ ", operationId=" + operationId);
}
if (mCurrentGadgetHalVersion < UsbManager.GADGET_HAL_V1_2) {
if ((functions & UsbManager.FUNCTION_NCM) != 0) {
@@ -2221,7 +2265,7 @@
functions = getAppliedFunctions(functions);
// Set the new USB configuration.
- setUsbConfig(functions, chargingFunctions);
+ setUsbConfig(functions, chargingFunctions, operationId);
if (mBootCompleted && isUsbDataTransferActive(functions)) {
// Start up dependent services.
@@ -2229,6 +2273,11 @@
}
}
}
+
+ @Override
+ public void handlerInitDone(int operationId) {
+ mUsbGadgetHal.getCurrentUsbFunctions(operationId);
+ }
}
/* returns the currently attached USB accessory */
@@ -2270,6 +2319,21 @@
return mHandler.getGadgetHalVersion();
}
+ public void setCurrentUsbFunctionsCb(long functions,
+ int status, int mRequest, long mFunctions, boolean mChargingFunctions) {
+ mHandler.setCurrentUsbFunctionsCb(functions, status,
+ mRequest, mFunctions, mChargingFunctions);
+ }
+
+ public void getCurrentUsbFunctionsCb(long functions, int status) {
+ mHandler.sendMessage(MSG_GET_CURRENT_USB_FUNCTIONS, functions,
+ status == Status.FUNCTIONS_APPLIED);
+ }
+
+ public void getUsbSpeedCb(int speed) {
+ mHandler.getUsbSpeedCb(speed);
+ }
+
/**
* Returns a dup of the control file descriptor for the given function.
*/
@@ -2295,7 +2359,7 @@
*
* @param functions The functions to set, or empty to set the charging function.
*/
- public void setCurrentFunctions(long functions) {
+ public void setCurrentFunctions(long functions, int operationId) {
if (DEBUG) {
Slog.d(TAG, "setCurrentFunctions(" + UsbManager.usbFunctionsToString(functions) + ")");
}
@@ -2312,7 +2376,7 @@
} else if (functions == UsbManager.FUNCTION_ACCESSORY) {
MetricsLogger.action(mContext, MetricsEvent.ACTION_USB_CONFIG_ACCESSORY);
}
- mHandler.sendMessage(MSG_SET_CURRENT_FUNCTIONS, functions);
+ mHandler.sendMessage(MSG_SET_CURRENT_FUNCTIONS, functions, operationId);
}
/**
@@ -2340,7 +2404,8 @@
}
private void onAdbEnabled(boolean enabled) {
- mHandler.sendMessage(MSG_ENABLE_ADB, enabled);
+ int operationId = sUsbOperationCount.incrementAndGet();
+ mHandler.sendMessage(MSG_ENABLE_ADB, enabled, operationId);
}
/**
diff --git a/services/usb/java/com/android/server/usb/UsbService.java b/services/usb/java/com/android/server/usb/UsbService.java
index d821dee..d09f729 100644
--- a/services/usb/java/com/android/server/usb/UsbService.java
+++ b/services/usb/java/com/android/server/usb/UsbService.java
@@ -622,16 +622,16 @@
}
@Override
- public void setCurrentFunctions(long functions) {
+ public void setCurrentFunctions(long functions, int operationId) {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null);
Preconditions.checkArgument(UsbManager.areSettableFunctions(functions));
Preconditions.checkState(mDeviceManager != null);
- mDeviceManager.setCurrentFunctions(functions);
+ mDeviceManager.setCurrentFunctions(functions, operationId);
}
@Override
- public void setCurrentFunction(String functions, boolean usbDataUnlocked) {
- setCurrentFunctions(UsbManager.usbFunctionsFromString(functions));
+ public void setCurrentFunction(String functions, boolean usbDataUnlocked, int operationId) {
+ setCurrentFunctions(UsbManager.usbFunctionsFromString(functions), operationId);
}
@Override
diff --git a/services/usb/java/com/android/server/usb/hal/gadget/UsbGadgetAidl.java b/services/usb/java/com/android/server/usb/hal/gadget/UsbGadgetAidl.java
new file mode 100644
index 0000000..bdfe60a
--- /dev/null
+++ b/services/usb/java/com/android/server/usb/hal/gadget/UsbGadgetAidl.java
@@ -0,0 +1,220 @@
+/*
+ * 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.usb.hal.gadget;
+
+import static android.hardware.usb.UsbManager.GADGET_HAL_V2_0;
+
+import static com.android.server.usb.UsbDeviceManager.logAndPrint;
+import static com.android.server.usb.UsbDeviceManager.logAndPrintException;
+
+import android.annotation.Nullable;
+import android.hardware.usb.gadget.IUsbGadget;
+import android.hardware.usb.gadget.IUsbGadgetCallback;
+import android.hardware.usb.UsbManager.UsbGadgetHalVersion;
+import android.os.ServiceManager;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+import android.util.LongSparseArray;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.IndentingPrintWriter;
+import com.android.server.usb.UsbDeviceManager;
+
+import java.util.ArrayList;
+import java.util.concurrent.ThreadLocalRandom;
+import java.util.NoSuchElementException;
+import java.util.Objects;
+
+/**
+ * Implements the methods to interact with AIDL USB HAL.
+ */
+public final class UsbGadgetAidl implements UsbGadgetHal {
+ private static final String TAG = UsbGadgetAidl.class.getSimpleName();
+ private static final String USB_GADGET_AIDL_SERVICE = IUsbGadget.DESCRIPTOR + "/default";
+ // Proxy object for the usb gadget hal daemon.
+ @GuardedBy("mGadgetProxyLock")
+ private IUsbGadget mGadgetProxy;
+ private final UsbDeviceManager mDeviceManager;
+ public final IndentingPrintWriter mPw;
+ // Mutex for all mutable shared state.
+ private final Object mGadgetProxyLock = new Object();
+ // Callback when the UsbDevice status is changed by the kernel.
+ private UsbGadgetCallback mUsbGadgetCallback;
+
+ public @UsbGadgetHalVersion int getGadgetHalVersion() throws RemoteException {
+ synchronized (mGadgetProxyLock) {
+ if (mGadgetProxy == null) {
+ throw new RemoteException("IUsb not initialized yet");
+ }
+ }
+ Slog.i(TAG, "USB Gadget HAL AIDL version: GADGET_HAL_V2_0");
+ return GADGET_HAL_V2_0;
+ }
+
+ @Override
+ public void systemReady() {
+ }
+
+ public void serviceDied() {
+ logAndPrint(Log.ERROR, mPw, "Usb Gadget AIDL hal service died");
+ synchronized (mGadgetProxyLock) {
+ mGadgetProxy = null;
+ }
+ connectToProxy(null);
+ }
+
+ private void connectToProxy(IndentingPrintWriter pw) {
+ synchronized (mGadgetProxyLock) {
+ if (mGadgetProxy != null) {
+ return;
+ }
+
+ try {
+ mGadgetProxy = IUsbGadget.Stub.asInterface(
+ ServiceManager.waitForService(USB_GADGET_AIDL_SERVICE));
+ } catch (NoSuchElementException e) {
+ logAndPrintException(pw, "connectToProxy: usb gadget hal service not found."
+ + " Did the service fail to start?", e);
+ }
+ }
+ }
+
+ static boolean isServicePresent(IndentingPrintWriter pw) {
+ try {
+ return ServiceManager.isDeclared(USB_GADGET_AIDL_SERVICE);
+ } catch (NoSuchElementException e) {
+ logAndPrintException(pw, "connectToProxy: usb gadget Aidl hal service not found.", e);
+ }
+
+ return false;
+ }
+
+ public UsbGadgetAidl(UsbDeviceManager deviceManager, IndentingPrintWriter pw) {
+ mDeviceManager = Objects.requireNonNull(deviceManager);
+ mPw = pw;
+ connectToProxy(mPw);
+ }
+
+ @Override
+ public void getCurrentUsbFunctions(long operationId) {
+ synchronized (mGadgetProxyLock) {
+ try {
+ mGadgetProxy.getCurrentUsbFunctions(new UsbGadgetCallback(), operationId);
+ } catch (RemoteException e) {
+ logAndPrintException(mPw,
+ "RemoteException while calling getCurrentUsbFunctions"
+ + ", opID:" + operationId, e);
+ return;
+ }
+ }
+ }
+
+ @Override
+ public void getUsbSpeed(long operationId) {
+ try {
+ synchronized (mGadgetProxyLock) {
+ mGadgetProxy.getUsbSpeed(new UsbGadgetCallback(), operationId);
+ }
+ } catch (RemoteException e) {
+ logAndPrintException(mPw,
+ "RemoteException while calling getUsbSpeed"
+ + ", opID:" + operationId, e);
+ return;
+ }
+ }
+
+ @Override
+ public void reset() {
+ try {
+ synchronized (mGadgetProxyLock) {
+ mGadgetProxy.reset();
+ }
+ } catch (RemoteException e) {
+ logAndPrintException(mPw,
+ "RemoteException while calling getUsbSpeed", e);
+ return;
+ }
+ }
+
+ @Override
+ public void setCurrentUsbFunctions(int mRequest, long mFunctions,
+ boolean mChargingFunctions, int timeout, long operationId) {
+ try {
+ mUsbGadgetCallback = new UsbGadgetCallback(mRequest,
+ mFunctions, mChargingFunctions);
+ synchronized (mGadgetProxyLock) {
+ mGadgetProxy.setCurrentUsbFunctions(mFunctions, mUsbGadgetCallback,
+ timeout, operationId);
+ }
+ } catch (RemoteException e) {
+ logAndPrintException(mPw,
+ "RemoteException while calling setCurrentUsbFunctions: "
+ + "mRequest=" + mRequest
+ + ", mFunctions=" + mFunctions
+ + ", mChargingFunctions=" + mChargingFunctions
+ + ", timeout=" + timeout
+ + ", opID:" + operationId, e);
+ return;
+ }
+ }
+
+ private class UsbGadgetCallback extends IUsbGadgetCallback.Stub {
+ public int mRequest;
+ public long mFunctions;
+ public boolean mChargingFunctions;
+
+ UsbGadgetCallback() {
+ }
+
+ UsbGadgetCallback(int request, long functions,
+ boolean chargingFunctions) {
+ mRequest = request;
+ mFunctions = functions;
+ mChargingFunctions = chargingFunctions;
+ }
+
+ @Override
+ public void setCurrentUsbFunctionsCb(long functions,
+ int status, long transactionId) {
+ mDeviceManager.setCurrentUsbFunctionsCb(functions, status,
+ mRequest, mFunctions, mChargingFunctions);
+ }
+
+ @Override
+ public void getCurrentUsbFunctionsCb(long functions,
+ int status, long transactionId) {
+ mDeviceManager.getCurrentUsbFunctionsCb(functions, status);
+ }
+
+ @Override
+ public void getUsbSpeedCb(int speed, long transactionId) {
+ mDeviceManager.getUsbSpeedCb(speed);
+ }
+
+ @Override
+ public String getInterfaceHash() {
+ return IUsbGadgetCallback.HASH;
+ }
+
+ @Override
+ public int getInterfaceVersion() {
+ return IUsbGadgetCallback.VERSION;
+ }
+ }
+}
+
diff --git a/services/usb/java/com/android/server/usb/hal/gadget/UsbGadgetHal.java b/services/usb/java/com/android/server/usb/hal/gadget/UsbGadgetHal.java
new file mode 100644
index 0000000..267247b
--- /dev/null
+++ b/services/usb/java/com/android/server/usb/hal/gadget/UsbGadgetHal.java
@@ -0,0 +1,138 @@
+/*
+ * 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.usb.hal.gadget;
+
+import android.annotation.IntDef;
+import android.hardware.usb.gadget.IUsbGadgetCallback;
+import android.hardware.usb.IUsbOperationInternal;
+import android.hardware.usb.UsbManager.UsbHalVersion;
+import android.os.RemoteException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.String;
+
+/**
+ * @hide
+ */
+public interface UsbGadgetHal {
+ /**
+ * Power role: This USB port can act as a source (provide power).
+ * @hide
+ */
+ public static final int HAL_POWER_ROLE_SOURCE = 1;
+
+ /**
+ * Power role: This USB port can act as a sink (receive power).
+ * @hide
+ */
+ public static final int HAL_POWER_ROLE_SINK = 2;
+
+ @IntDef(prefix = { "HAL_POWER_ROLE_" }, value = {
+ HAL_POWER_ROLE_SOURCE,
+ HAL_POWER_ROLE_SINK
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @interface HalUsbPowerRole{}
+
+ /**
+ * Data role: This USB port can act as a host (access data services).
+ * @hide
+ */
+ public static final int HAL_DATA_ROLE_HOST = 1;
+
+ /**
+ * Data role: This USB port can act as a device (offer data services).
+ * @hide
+ */
+ public static final int HAL_DATA_ROLE_DEVICE = 2;
+
+ @IntDef(prefix = { "HAL_DATA_ROLE_" }, value = {
+ HAL_DATA_ROLE_HOST,
+ HAL_DATA_ROLE_DEVICE
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @interface HalUsbDataRole{}
+
+ /**
+ * This USB port can act as a downstream facing port (host).
+ *
+ * @hide
+ */
+ public static final int HAL_MODE_DFP = 1;
+
+ /**
+ * This USB port can act as an upstream facing port (device).
+ *
+ * @hide
+ */
+ public static final int HAL_MODE_UFP = 2;
+ @IntDef(prefix = { "HAL_MODE_" }, value = {
+ HAL_MODE_DFP,
+ HAL_MODE_UFP,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @interface HalUsbPortMode{}
+
+ /**
+ * UsbPortManager would call this when the system is done booting.
+ */
+ public void systemReady();
+
+ /**
+ * This function is used to query the USB functions included in the
+ * current USB configuration.
+ *
+ * @param transactionId Used for tracking the current request and is passed down to the HAL
+ * implementation as needed.
+ */
+ public void getCurrentUsbFunctions(long transactionId);
+
+ /**
+ * The function is used to query current USB speed.
+ *
+ * @param transactionId Used for tracking the current request and is passed down to the HAL
+ * implementation as needed.
+ */
+ public void getUsbSpeed(long transactionId);
+
+ /**
+ * This function is used to reset USB gadget driver.
+ * Performs USB data connection reset. The connection will disconnect and
+ * reconnect.
+ */
+ public void reset();
+
+ /**
+ * Invoked to query the version of current gadget hal implementation.
+ */
+ public @UsbHalVersion int getGadgetHalVersion() throws RemoteException;
+
+ /**
+ * This function is used to set the current USB gadget configuration.
+ * The USB gadget needs to be torn down if a USB configuration is already
+ * active.
+ *
+ * @param functions list of functions defined by GadgetFunction to be
+ * included in the gadget composition.
+ * @param timeout The maximum time (in milliseconds) within which the
+ * IUsbGadgetCallback needs to be returned.
+ * @param transactionId Used for tracking the current request and is passed down to the HAL
+ * implementation as needed.
+ */
+ public void setCurrentUsbFunctions(int request, long functions,
+ boolean chargingFunctions, int timeout, long transactionId);
+}
+
diff --git a/services/usb/java/com/android/server/usb/hal/gadget/UsbGadgetHalInstance.java b/services/usb/java/com/android/server/usb/hal/gadget/UsbGadgetHalInstance.java
new file mode 100644
index 0000000..d268315
--- /dev/null
+++ b/services/usb/java/com/android/server/usb/hal/gadget/UsbGadgetHalInstance.java
@@ -0,0 +1,48 @@
+/*
+ * 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.usb.hal.gadget;
+
+import static com.android.server.usb.UsbPortManager.logAndPrint;
+
+import com.android.internal.util.IndentingPrintWriter;
+import com.android.server.usb.hal.gadget.UsbGadgetHidl;
+import com.android.server.usb.hal.gadget.UsbGadgetAidl;
+import com.android.server.usb.UsbDeviceManager;
+
+import android.util.Log;
+/**
+ * Helper class that queries the underlying hal layer to populate UsbPortHal instance.
+ */
+public final class UsbGadgetHalInstance {
+
+ public static UsbGadgetHal getInstance(UsbDeviceManager deviceManager,
+ IndentingPrintWriter pw) {
+
+ logAndPrint(Log.DEBUG, pw, "Querying USB Gadget HAL version");
+ if (UsbGadgetAidl.isServicePresent(null)) {
+ logAndPrint(Log.INFO, pw, "USB Gadget HAL AIDL present");
+ return new UsbGadgetAidl(deviceManager, pw);
+ }
+ if (UsbGadgetHidl.isServicePresent(null)) {
+ logAndPrint(Log.INFO, pw, "USB Gadget HAL HIDL present");
+ return new UsbGadgetHidl(deviceManager, pw);
+ }
+
+ logAndPrint(Log.ERROR, pw, "USB Gadget HAL AIDL/HIDL not present");
+ return null;
+ }
+}
+
diff --git a/services/usb/java/com/android/server/usb/hal/gadget/UsbGadgetHidl.java b/services/usb/java/com/android/server/usb/hal/gadget/UsbGadgetHidl.java
new file mode 100644
index 0000000..3e5ecc5
--- /dev/null
+++ b/services/usb/java/com/android/server/usb/hal/gadget/UsbGadgetHidl.java
@@ -0,0 +1,261 @@
+/*
+ * 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.usb.hal.gadget;
+
+import static android.hardware.usb.UsbManager.GADGET_HAL_NOT_SUPPORTED;
+import static android.hardware.usb.UsbManager.GADGET_HAL_V1_0;
+import static android.hardware.usb.UsbManager.GADGET_HAL_V1_1;
+import static android.hardware.usb.UsbManager.GADGET_HAL_V1_2;
+
+import static com.android.server.usb.UsbDeviceManager.logAndPrint;
+import static com.android.server.usb.UsbDeviceManager.logAndPrintException;
+
+import android.annotation.Nullable;
+import android.hardware.usb.gadget.V1_0.Status;
+import android.hardware.usb.gadget.V1_0.IUsbGadget;
+import android.hardware.usb.gadget.V1_2.IUsbGadgetCallback;
+import android.hardware.usb.gadget.V1_2.UsbSpeed;
+import android.hardware.usb.UsbAccessory;
+import android.hardware.usb.UsbManager;
+import android.hardware.usb.UsbManager.UsbGadgetHalVersion;
+import android.hardware.usb.UsbManager.UsbHalVersion;
+import android.hidl.manager.V1_0.IServiceManager;
+import android.hidl.manager.V1_0.IServiceNotification;
+import android.os.IHwBinder;
+import android.os.RemoteException;
+import android.util.Log;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.IndentingPrintWriter;
+import com.android.server.usb.UsbDeviceManager;
+
+import java.util.ArrayList;
+import java.util.NoSuchElementException;
+import java.util.Objects;
+/**
+ *
+ */
+public final class UsbGadgetHidl implements UsbGadgetHal {
+ // Cookie sent for usb gadget hal death notification.
+ private static final int USB_GADGET_HAL_DEATH_COOKIE = 2000;
+ // Proxy object for the usb gadget hal daemon.
+ @GuardedBy("mGadgetProxyLock")
+ private IUsbGadget mGadgetProxy;
+ private UsbDeviceManager mDeviceManager;
+ private final IndentingPrintWriter mPw;
+ // Mutex for all mutable shared state.
+ private final Object mGadgetProxyLock = new Object();
+ private UsbGadgetCallback mUsbGadgetCallback;
+
+ public @UsbGadgetHalVersion int getGadgetHalVersion() throws RemoteException {
+ int version;
+ synchronized(mGadgetProxyLock) {
+ if (mGadgetProxy == null) {
+ throw new RemoteException("IUsbGadget not initialized yet");
+ }
+ if (android.hardware.usb.gadget.V1_2.IUsbGadget.castFrom(mGadgetProxy) != null) {
+ version = UsbManager.GADGET_HAL_V1_2;
+ } else if (android.hardware.usb.gadget.V1_1.IUsbGadget.castFrom(mGadgetProxy) != null) {
+ version = UsbManager.GADGET_HAL_V1_1;
+ } else {
+ version = UsbManager.GADGET_HAL_V1_0;
+ }
+ logAndPrint(Log.INFO, mPw, "USB Gadget HAL HIDL version: " + version);
+ return version;
+ }
+ }
+
+ final class DeathRecipient implements IHwBinder.DeathRecipient {
+ private final IndentingPrintWriter mPw;
+
+ DeathRecipient(IndentingPrintWriter pw) {
+ mPw = pw;
+ }
+
+ @Override
+ public void serviceDied(long cookie) {
+ if (cookie == USB_GADGET_HAL_DEATH_COOKIE) {
+ logAndPrint(Log.ERROR, mPw, "Usb Gadget hal service died cookie: " + cookie);
+ synchronized (mGadgetProxyLock) {
+ mGadgetProxy = null;
+ }
+ }
+ }
+ }
+
+ final class ServiceNotification extends IServiceNotification.Stub {
+ @Override
+ public void onRegistration(String fqName, String name, boolean preexisting) {
+ logAndPrint(Log.INFO, mPw, "Usb gadget hal service started " + fqName + " " + name);
+ connectToProxy(null);
+ }
+ }
+
+ private void connectToProxy(IndentingPrintWriter pw) {
+ synchronized (mGadgetProxyLock) {
+ if (mGadgetProxy != null) {
+ return;
+ }
+
+ try {
+ mGadgetProxy = IUsbGadget.getService();
+ mGadgetProxy.linkToDeath(new DeathRecipient(pw), USB_GADGET_HAL_DEATH_COOKIE);
+ } catch (NoSuchElementException e) {
+ logAndPrintException(pw, "connectToProxy: usb gadget hal service not found."
+ + " Did the service fail to start?", e);
+ } catch (RemoteException e) {
+ logAndPrintException(pw, "connectToProxy: usb gadget hal service not responding"
+ , e);
+ }
+ }
+ }
+
+ @Override
+ public void systemReady() {
+ }
+
+ static boolean isServicePresent(IndentingPrintWriter pw) {
+ try {
+ IUsbGadget.getService(true);
+ } catch (NoSuchElementException e) {
+ logAndPrintException(pw, "connectToProxy: usb gadget hidl hal service not found.", e);
+ return false;
+ } catch (RemoteException e) {
+ logAndPrintException(pw, "IUSBGadget hal service present but failed to get service", e);
+ }
+
+ return true;
+ }
+
+ public UsbGadgetHidl(UsbDeviceManager deviceManager, IndentingPrintWriter pw) {
+ mDeviceManager = Objects.requireNonNull(deviceManager);
+ mPw = pw;
+ try {
+ ServiceNotification serviceNotification = new ServiceNotification();
+
+ boolean ret = IServiceManager.getService()
+ .registerForNotifications("android.hardware.usb.gadget@1.0::IUsbGadget",
+ "", serviceNotification);
+ if (!ret) {
+ logAndPrint(Log.ERROR, pw, "Failed to register service start notification");
+ }
+ } catch (RemoteException e) {
+ logAndPrintException(pw, "Failed to register service start notification", e);
+ return;
+ }
+ connectToProxy(mPw);
+ }
+
+ @Override
+ public void getCurrentUsbFunctions(long transactionId) {
+ try {
+ synchronized(mGadgetProxyLock) {
+ mGadgetProxy.getCurrentUsbFunctions(new UsbGadgetCallback());
+ }
+ } catch (RemoteException e) {
+ logAndPrintException(mPw,
+ "RemoteException while calling getCurrentUsbFunctions", e);
+ return;
+ }
+ }
+
+ @Override
+ public void getUsbSpeed(long transactionId) {
+ try {
+ synchronized(mGadgetProxyLock) {
+ if (android.hardware.usb.gadget.V1_2.IUsbGadget.castFrom(mGadgetProxy) != null) {
+ android.hardware.usb.gadget.V1_2.IUsbGadget gadgetProxy =
+ android.hardware.usb.gadget.V1_2.IUsbGadget.castFrom(mGadgetProxy);
+ gadgetProxy.getUsbSpeed(new UsbGadgetCallback());
+ }
+ }
+ } catch (RemoteException e) {
+ logAndPrintException(mPw, "get UsbSpeed failed", e);
+ }
+ }
+
+ @Override
+ public void reset() {
+ try {
+ synchronized(mGadgetProxyLock) {
+ if (android.hardware.usb.gadget.V1_2.IUsbGadget.castFrom(mGadgetProxy) != null) {
+ android.hardware.usb.gadget.V1_2.IUsbGadget gadgetProxy =
+ android.hardware.usb.gadget.V1_2.IUsbGadget.castFrom(mGadgetProxy);
+ gadgetProxy.reset();
+ }
+ }
+ } catch (RemoteException e) {
+ logAndPrintException(mPw,
+ "RemoteException while calling getUsbSpeed", e);
+ return;
+ }
+ }
+
+ @Override
+ public void setCurrentUsbFunctions(int mRequest, long mFunctions,
+ boolean mChargingFunctions, int timeout, long operationId) {
+ try {
+ mUsbGadgetCallback = new UsbGadgetCallback(null, mRequest,
+ mFunctions, mChargingFunctions);
+ synchronized(mGadgetProxyLock) {
+ mGadgetProxy.setCurrentUsbFunctions(mFunctions, mUsbGadgetCallback, timeout);
+ }
+ } catch (RemoteException e) {
+ logAndPrintException(mPw,
+ "RemoteException while calling setCurrentUsbFunctions"
+ + " mRequest = " + mRequest
+ + ", mFunctions = " + mFunctions
+ + ", timeout = " + timeout
+ + ", mChargingFunctions = " + mChargingFunctions
+ + ", operationId =" + operationId, e);
+ return;
+ }
+ }
+
+ private class UsbGadgetCallback extends IUsbGadgetCallback.Stub {
+ public int mRequest;
+ public long mFunctions;
+ public boolean mChargingFunctions;
+
+ UsbGadgetCallback() {
+ }
+ UsbGadgetCallback(IndentingPrintWriter pw, int request,
+ long functions, boolean chargingFunctions) {
+ mRequest = request;
+ mFunctions = functions;
+ mChargingFunctions = chargingFunctions;
+ }
+
+ @Override
+ public void setCurrentUsbFunctionsCb(long functions,
+ int status) {
+ mDeviceManager.setCurrentUsbFunctionsCb(functions, status,
+ mRequest, mFunctions, mChargingFunctions);
+ }
+
+ @Override
+ public void getCurrentUsbFunctionsCb(long functions,
+ int status) {
+ mDeviceManager.getCurrentUsbFunctionsCb(functions, status);
+ }
+
+ @Override
+ public void getUsbSpeedCb(int speed) {
+ mDeviceManager.getUsbSpeedCb(speed);
+ }
+ }
+}
+
diff --git a/telephony/java/android/telephony/Annotation.java b/telephony/java/android/telephony/Annotation.java
index 86b98f1..2435243 100644
--- a/telephony/java/android/telephony/Annotation.java
+++ b/telephony/java/android/telephony/Annotation.java
@@ -5,6 +5,7 @@
import android.net.NetworkCapabilities;
import android.telecom.Connection;
import android.telephony.data.ApnSetting;
+import android.telephony.ims.ImsCallProfile;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -494,7 +495,7 @@
PreciseCallState.PRECISE_CALL_STATE_HOLDING,
PreciseCallState.PRECISE_CALL_STATE_DIALING,
PreciseCallState.PRECISE_CALL_STATE_ALERTING,
- PreciseCallState. PRECISE_CALL_STATE_INCOMING,
+ PreciseCallState.PRECISE_CALL_STATE_INCOMING,
PreciseCallState.PRECISE_CALL_STATE_WAITING,
PreciseCallState.PRECISE_CALL_STATE_DISCONNECTED,
PreciseCallState.PRECISE_CALL_STATE_DISCONNECTING})
@@ -727,6 +728,36 @@
})
public @interface ValidationStatus {}
+ /**
+ * IMS call Service types
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = { "SERVICE_TYPE_" }, value = {
+ ImsCallProfile.SERVICE_TYPE_NONE,
+ ImsCallProfile.SERVICE_TYPE_NORMAL,
+ ImsCallProfile.SERVICE_TYPE_EMERGENCY,
+ })
+ public @interface ImsCallServiceType {}
+
+ /**
+ * IMS call types
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = { "CALL_TYPE_" }, value = {
+ ImsCallProfile.CALL_TYPE_NONE,
+ ImsCallProfile.CALL_TYPE_VOICE_N_VIDEO,
+ ImsCallProfile.CALL_TYPE_VOICE,
+ ImsCallProfile.CALL_TYPE_VIDEO_N_VOICE,
+ ImsCallProfile.CALL_TYPE_VT,
+ ImsCallProfile.CALL_TYPE_VT_TX,
+ ImsCallProfile.CALL_TYPE_VT_RX,
+ ImsCallProfile.CALL_TYPE_VT_NODIR,
+ ImsCallProfile.CALL_TYPE_VS,
+ ImsCallProfile.CALL_TYPE_VS_TX,
+ ImsCallProfile.CALL_TYPE_VS_RX,
+ })
+ public @interface ImsCallType {}
+
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@IntDef(prefix = { "NET_CAPABILITY_ENTERPRISE_SUB_LEVEL" }, value = {
diff --git a/telephony/java/android/telephony/CallAttributes.java b/telephony/java/android/telephony/CallAttributes.java
index b7bef39..1dc64a9 100644
--- a/telephony/java/android/telephony/CallAttributes.java
+++ b/telephony/java/android/telephony/CallAttributes.java
@@ -29,8 +29,10 @@
* Contains information about a call's attributes as passed up from the HAL. If there are multiple
* ongoing calls, the CallAttributes will pertain to the call in the foreground.
* @hide
+ * @deprecated use {@link CallState} for call information for each call.
*/
@SystemApi
+@Deprecated
public final class CallAttributes implements Parcelable {
private PreciseCallState mPreciseCallState;
@NetworkType
diff --git a/telephony/java/android/telephony/CallState.aidl b/telephony/java/android/telephony/CallState.aidl
new file mode 100644
index 0000000..dd5af8e
--- /dev/null
+++ b/telephony/java/android/telephony/CallState.aidl
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony;
+
+parcelable CallState;
+
diff --git a/telephony/java/android/telephony/CallState.java b/telephony/java/android/telephony/CallState.java
new file mode 100644
index 0000000..51ecfb0
--- /dev/null
+++ b/telephony/java/android/telephony/CallState.java
@@ -0,0 +1,409 @@
+/*
+ * 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.telephony;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.telephony.Annotation.ImsCallServiceType;
+import android.telephony.Annotation.ImsCallType;
+import android.telephony.Annotation.NetworkType;
+import android.telephony.Annotation.PreciseCallStates;
+import android.telephony.ims.ImsCallProfile;
+import android.telephony.ims.ImsCallSession;
+
+import java.util.Objects;
+
+/**
+ * Contains information about various states for a call.
+ * @hide
+ */
+@SystemApi
+public final class CallState implements Parcelable {
+
+ /**
+ * Call classifications are just used for backward compatibility of deprecated API {@link
+ * TelephonyCallback#CallAttributesListener#onCallAttributesChanged}, Since these will be
+ * removed when the deprecated API is removed, they should not be opened.
+ */
+ /**
+ * Call classification is not valid. It should not be opened.
+ * @hide
+ */
+ public static final int CALL_CLASSIFICATION_UNKNOWN = -1;
+
+ /**
+ * Call classification indicating foreground call
+ * @hide
+ */
+ public static final int CALL_CLASSIFICATION_RINGING = 0;
+
+ /**
+ * Call classification indicating background call
+ * @hide
+ */
+ public static final int CALL_CLASSIFICATION_FOREGROUND = 1;
+
+ /**
+ * Call classification indicating ringing call
+ * @hide
+ */
+ public static final int CALL_CLASSIFICATION_BACKGROUND = 2;
+
+ /**
+ * Call classification Max value.
+ * @hide
+ */
+ public static final int CALL_CLASSIFICATION_MAX = CALL_CLASSIFICATION_BACKGROUND + 1;
+
+ @PreciseCallStates
+ private final int mPreciseCallState;
+
+ @NetworkType
+ private final int mNetworkType; // TelephonyManager.NETWORK_TYPE_* ints
+ private final CallQuality mCallQuality;
+
+ private final int mCallClassification;
+ /**
+ * IMS call session ID. {@link ImsCallSession#getCallId()}
+ */
+ @Nullable
+ private String mImsCallId;
+
+ /**
+ * IMS call service type of this call
+ */
+ @ImsCallServiceType
+ private int mImsCallServiceType;
+
+ /**
+ * IMS call type of this call.
+ */
+ @ImsCallType
+ private int mImsCallType;
+
+ /**
+ * Constructor of CallAttributes
+ *
+ * @param callState call state defined in {@link PreciseCallState}
+ * @param networkType network type for this call attributes
+ * @param callQuality call quality for this call attributes, only CallState in
+ * {@link PreciseCallState#PRECISE_CALL_STATE_ACTIVE} will have valid call
+ * quality.
+ * @param callClassification call classification
+ * @param imsCallId IMS call session ID for this call attributes
+ * @param imsCallServiceType IMS call service type for this call attributes
+ * @param imsCallType IMS call type for this call attributes
+ */
+ private CallState(@PreciseCallStates int callState, @NetworkType int networkType,
+ @NonNull CallQuality callQuality, int callClassification, @Nullable String imsCallId,
+ @ImsCallServiceType int imsCallServiceType, @ImsCallType int imsCallType) {
+ this.mPreciseCallState = callState;
+ this.mNetworkType = networkType;
+ this.mCallQuality = callQuality;
+ this.mCallClassification = callClassification;
+ this.mImsCallId = imsCallId;
+ this.mImsCallServiceType = imsCallServiceType;
+ this.mImsCallType = imsCallType;
+ }
+
+ @NonNull
+ @Override
+ public String toString() {
+ return "mPreciseCallState=" + mPreciseCallState + " mNetworkType=" + mNetworkType
+ + " mCallQuality=" + mCallQuality + " mCallClassification" + mCallClassification
+ + " mImsCallId=" + mImsCallId + " mImsCallServiceType=" + mImsCallServiceType
+ + " mImsCallType=" + mImsCallType;
+ }
+
+ private CallState(Parcel in) {
+ this.mPreciseCallState = in.readInt();
+ this.mNetworkType = in.readInt();
+ this.mCallQuality = in.readParcelable(
+ CallQuality.class.getClassLoader(), CallQuality.class);
+ this.mCallClassification = in.readInt();
+ this.mImsCallId = in.readString();
+ this.mImsCallServiceType = in.readInt();
+ this.mImsCallType = in.readInt();
+ }
+
+ // getters
+ /**
+ * Returns the precise call state of the call.
+ */
+ @PreciseCallStates
+ public int getCallState() {
+ return mPreciseCallState;
+ }
+
+ /**
+ * Returns the {@link TelephonyManager#NetworkType} of the call.
+ *
+ * @see TelephonyManager#NETWORK_TYPE_UNKNOWN
+ * @see TelephonyManager#NETWORK_TYPE_GPRS
+ * @see TelephonyManager#NETWORK_TYPE_EDGE
+ * @see TelephonyManager#NETWORK_TYPE_UMTS
+ * @see TelephonyManager#NETWORK_TYPE_CDMA
+ * @see TelephonyManager#NETWORK_TYPE_EVDO_0
+ * @see TelephonyManager#NETWORK_TYPE_EVDO_A
+ * @see TelephonyManager#NETWORK_TYPE_1xRTT
+ * @see TelephonyManager#NETWORK_TYPE_HSDPA
+ * @see TelephonyManager#NETWORK_TYPE_HSUPA
+ * @see TelephonyManager#NETWORK_TYPE_HSPA
+ * @see TelephonyManager#NETWORK_TYPE_IDEN
+ * @see TelephonyManager#NETWORK_TYPE_EVDO_B
+ * @see TelephonyManager#NETWORK_TYPE_LTE
+ * @see TelephonyManager#NETWORK_TYPE_EHRPD
+ * @see TelephonyManager#NETWORK_TYPE_HSPAP
+ * @see TelephonyManager#NETWORK_TYPE_GSM
+ * @see TelephonyManager#NETWORK_TYPE_TD_SCDMA
+ * @see TelephonyManager#NETWORK_TYPE_IWLAN
+ * @see TelephonyManager#NETWORK_TYPE_LTE_CA
+ * @see TelephonyManager#NETWORK_TYPE_NR
+ */
+ @NetworkType
+ public int getNetworkType() {
+ return mNetworkType;
+ }
+
+ /**
+ * Returns the {#link CallQuality} of the call.
+ * @return call quality for this call attributes, only CallState in {@link
+ * PreciseCallState#PRECISE_CALL_STATE_ACTIVE} will have valid call quality. It will be
+ * null for the call which is not in {@link PreciseCallState#PRECISE_CALL_STATE_ACTIVE}.
+ */
+ @Nullable
+ public CallQuality getCallQuality() {
+ return mCallQuality;
+ }
+
+ /**
+ * Returns the call classification.
+ * @hide
+ */
+ public int getCallClassification() {
+ return mCallClassification;
+ }
+
+ /**
+ * Returns the IMS call session ID.
+ */
+ @Nullable
+ public String getImsCallSessionId() {
+ return mImsCallId;
+ }
+
+ /**
+ * Returns the IMS call service type.
+ */
+ @ImsCallServiceType
+ public int getImsCallServiceType() {
+ return mImsCallServiceType;
+ }
+
+ /**
+ * Returns the IMS call type.
+ */
+ @ImsCallType
+ public int getImsCallType() {
+ return mImsCallType;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mPreciseCallState, mNetworkType, mCallQuality, mCallClassification,
+ mImsCallId, mImsCallServiceType, mImsCallType);
+ }
+
+ @Override
+ public boolean equals(@Nullable Object o) {
+ if (o == null || !(o instanceof CallState) || hashCode() != o.hashCode()) {
+ return false;
+ }
+
+ if (this == o) {
+ return true;
+ }
+
+ CallState s = (CallState) o;
+
+ return (mPreciseCallState == s.mPreciseCallState
+ && mNetworkType == s.mNetworkType
+ && Objects.equals(mCallQuality, s.mCallQuality)
+ && mCallClassification == s.mCallClassification
+ && Objects.equals(mImsCallId, s.mImsCallId)
+ && mImsCallType == s.mImsCallType
+ && mImsCallServiceType == s.mImsCallServiceType);
+ }
+
+ /**
+ * {@link Parcelable#describeContents}
+ */
+ public int describeContents() {
+ return 0;
+ }
+
+ /**
+ * {@link Parcelable#writeToParcel}
+ */
+ public void writeToParcel(@Nullable Parcel dest, int flags) {
+ dest.writeInt(mPreciseCallState);
+ dest.writeInt(mNetworkType);
+ dest.writeParcelable(mCallQuality, flags);
+ dest.writeInt(mCallClassification);
+ dest.writeString(mImsCallId);
+ dest.writeInt(mImsCallServiceType);
+ dest.writeInt(mImsCallType);
+ }
+
+ public static final @NonNull Creator<CallState> CREATOR = new Creator() {
+ public CallState createFromParcel(Parcel in) {
+ return new CallState(in);
+ }
+
+ public CallState[] newArray(int size) {
+ return new CallState[size];
+ }
+ };
+
+ /**
+ * Builder of {@link CallState}
+ *
+ * <p>The example below shows how you might create a new {@code CallState}:
+ *
+ * <pre><code>
+ *
+ * CallState = new CallState.Builder()
+ * .setCallState(3)
+ * .setNetworkType({@link TelephonyManager#NETWORK_TYPE_LTE})
+ * .setCallQuality({@link CallQuality})
+ * .setImsCallSessionId({@link String})
+ * .setImsCallServiceType({@link ImsCallProfile#SERVICE_TYPE_NORMAL})
+ * .setImsCallType({@link ImsCallProfile#CALL_TYPE_VOICE})
+ * .build();
+ * </code></pre>
+ */
+ public static final class Builder {
+ private @PreciseCallStates int mPreciseCallState;
+ private @NetworkType int mNetworkType = TelephonyManager.NETWORK_TYPE_UNKNOWN;
+ private CallQuality mCallQuality = null;
+ private int mCallClassification = CALL_CLASSIFICATION_UNKNOWN;
+ private String mImsCallId;
+ private @ImsCallServiceType int mImsCallServiceType = ImsCallProfile.SERVICE_TYPE_NONE;
+ private @ImsCallType int mImsCallType = ImsCallProfile.CALL_TYPE_NONE;
+
+
+ /**
+ * Default constructor for the Builder.
+ */
+ public Builder(@PreciseCallStates int preciseCallState) {
+ mPreciseCallState = preciseCallState;
+ }
+
+ /**
+ * Set network type of this call.
+ *
+ * @param networkType the transport type.
+ * @return The same instance of the builder.
+ */
+ @NonNull
+ public CallState.Builder setNetworkType(@NetworkType int networkType) {
+ this.mNetworkType = networkType;
+ return this;
+ }
+
+ /**
+ * Set the call quality {@link CallQuality} of this call.
+ *
+ * @param callQuality call quality of active call.
+ * @return The same instance of the builder.
+ */
+ @NonNull
+ public CallState.Builder setCallQuality(@Nullable CallQuality callQuality) {
+ this.mCallQuality = callQuality;
+ return this;
+ }
+
+ /**
+ * Set call classification for this call.
+ *
+ * @param classification call classification type defined in this class.
+ * @return The same instance of the builder.
+ * @hide
+ */
+ @NonNull
+ public CallState.Builder setCallClassification(int classification) {
+ this.mCallClassification = classification;
+ return this;
+ }
+
+ /**
+ * Set IMS call session ID of this call.
+ *
+ * @param imsCallId IMS call session ID.
+ * @return The same instance of the builder.
+ */
+ @NonNull
+ public CallState.Builder setImsCallSessionId(@Nullable String imsCallId) {
+ this.mImsCallId = imsCallId;
+ return this;
+ }
+
+ /**
+ * Set IMS call service type of this call.
+ *
+ * @param serviceType IMS call service type defined in {@link ImsCallProfile}.
+ * @return The same instance of the builder.
+ */
+ @NonNull
+ public CallState.Builder setImsCallServiceType(@ImsCallServiceType int serviceType) {
+ this.mImsCallServiceType = serviceType;
+ return this;
+ }
+
+ /**
+ * Set IMS call type of this call.
+ *
+ * @param callType IMS call type defined in {@link ImsCallProfile}.
+ * @return The same instance of the builder.
+ */
+ @NonNull
+ public CallState.Builder setImsCallType(@ImsCallType int callType) {
+ this.mImsCallType = callType;
+ return this;
+ }
+
+ /**
+ * Build the {@link CallState}
+ *
+ * @return the {@link CallState} object
+ */
+ @NonNull
+ public CallState build() {
+ return new CallState(
+ mPreciseCallState,
+ mNetworkType,
+ mCallQuality,
+ mCallClassification,
+ mImsCallId,
+ mImsCallServiceType,
+ mImsCallType);
+ }
+ }
+}
diff --git a/telephony/java/android/telephony/ims/ImsCallProfile.java b/telephony/java/android/telephony/ims/ImsCallProfile.java
index e6d7df3..1ea7fdc 100644
--- a/telephony/java/android/telephony/ims/ImsCallProfile.java
+++ b/telephony/java/android/telephony/ims/ImsCallProfile.java
@@ -78,8 +78,9 @@
public static final int SERVICE_TYPE_EMERGENCY = 2;
/**
- * Call types
+ * Call type none
*/
+ public static final int CALL_TYPE_NONE = 0;
/**
* IMSPhone to support IR.92 & IR.94 (voice + video upgrade/downgrade)
*/
diff --git a/tests/UsbManagerTests/lib/src/com/android/server/usblib/UsbManagerTestLib.java b/tests/UsbManagerTests/lib/src/com/android/server/usblib/UsbManagerTestLib.java
index d133f6f..e2099e6 100644
--- a/tests/UsbManagerTests/lib/src/com/android/server/usblib/UsbManagerTestLib.java
+++ b/tests/UsbManagerTests/lib/src/com/android/server/usblib/UsbManagerTestLib.java
@@ -24,12 +24,15 @@
import android.content.Context;
import android.hardware.usb.UsbManager;
+import android.os.Binder;
import android.os.RemoteException;
import android.util.Log;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.concurrent.atomic.AtomicInteger;
+
/**
* Unit tests lib for {@link android.hardware.usb.UsbManager}.
*/
@@ -42,6 +45,11 @@
private UsbManager mUsbManagerMock;
@Mock private android.hardware.usb.IUsbManager mMockUsbService;
+ /**
+ * Counter for tracking UsbOperation operations.
+ */
+ private static final AtomicInteger sUsbOperationCount = new AtomicInteger();
+
public UsbManagerTestLib(Context context) {
MockitoAnnotations.initMocks(this);
mContext = context;
@@ -82,10 +90,11 @@
}
private void testSetCurrentFunctionsMock_Matched(long functions) {
+ int operationId = sUsbOperationCount.incrementAndGet() + Binder.getCallingUid();
try {
setCurrentFunctions(functions);
- verify(mMockUsbService).setCurrentFunctions(eq(functions));
+ verify(mMockUsbService).setCurrentFunctions(eq(functions), operationId);
} catch (RemoteException remEx) {
Log.w(TAG, "RemoteException");
}
@@ -106,9 +115,10 @@
}
public void testSetCurrentFunctionsEx(long functions) throws Exception {
+ int operationId = sUsbOperationCount.incrementAndGet() + Binder.getCallingUid();
setCurrentFunctions(functions);
- verify(mMockUsbService).setCurrentFunctions(eq(functions));
+ verify(mMockUsbService).setCurrentFunctions(eq(functions), operationId);
}
public void testGetCurrentFunctions_shouldMatched() {
diff --git a/tests/UsbTests/src/com/android/server/usb/UsbHandlerTest.java b/tests/UsbTests/src/com/android/server/usb/UsbHandlerTest.java
index 86bcb72..4103ca7 100644
--- a/tests/UsbTests/src/com/android/server/usb/UsbHandlerTest.java
+++ b/tests/UsbTests/src/com/android/server/usb/UsbHandlerTest.java
@@ -98,7 +98,7 @@
}
@Override
- protected void setEnabledFunctions(long functions, boolean force) {
+ protected void setEnabledFunctions(long functions, boolean force, int operationId) {
mCurrentFunctions = functions;
}
@@ -134,6 +134,20 @@
protected void sendStickyBroadcast(Intent intent) {
mBroadcastedIntent = intent;
}
+
+ @Override
+ public void handlerInitDone(int operationId) {
+ }
+
+ @Override
+ public void setCurrentUsbFunctionsCb(long functions,
+ int status, int mRequest, long mFunctions, boolean mChargingFunctions){
+ }
+
+ @Override
+ public void getUsbSpeedCb(int speed){
+ }
+
}
@Before
diff --git a/tools/aapt2/cmd/Optimize.cpp b/tools/aapt2/cmd/Optimize.cpp
index 9c1a2f6..f7f2f22 100644
--- a/tools/aapt2/cmd/Optimize.cpp
+++ b/tools/aapt2/cmd/Optimize.cpp
@@ -154,8 +154,8 @@
return 1;
}
- if (options_.shorten_resource_paths) {
- Obfuscator obfuscator(options_.table_flattener_options.shortened_path_map);
+ Obfuscator obfuscator(options_);
+ if (obfuscator.IsEnabled()) {
if (!obfuscator.Consume(context_, apk->GetResourceTable())) {
context_->GetDiagnostics()->Error(android::DiagMessage()
<< "failed shortening resource paths");
diff --git a/tools/aapt2/format/binary/TableFlattener.cpp b/tools/aapt2/format/binary/TableFlattener.cpp
index f192234..8c594ba 100644
--- a/tools/aapt2/format/binary/TableFlattener.cpp
+++ b/tools/aapt2/format/binary/TableFlattener.cpp
@@ -32,6 +32,7 @@
#include "format/binary/ChunkWriter.h"
#include "format/binary/ResEntryWriter.h"
#include "format/binary/ResourceTypeExtensions.h"
+#include "optimize/Obfuscator.h"
#include "trace/TraceBuffer.h"
using namespace android;
@@ -466,9 +467,6 @@
// table.
std::map<ConfigDescription, std::vector<FlatEntry>> config_to_entry_list_map;
- // hardcoded string uses characters which make it an invalid resource name
- const std::string obfuscated_resource_name = "0_resource_name_obfuscated";
-
for (const ResourceTableEntryView& entry : type.entries) {
if (entry.staged_id) {
aliases_.insert(std::make_pair(
@@ -477,30 +475,31 @@
}
uint32_t local_key_index;
- ResourceName resource_name({}, type.named_type, entry.name);
- if (!collapse_key_stringpool_ ||
- name_collapse_exemptions_.find(resource_name) != name_collapse_exemptions_.end()) {
- local_key_index = (uint32_t)key_pool_.MakeRef(entry.name).index();
- } else {
- // resource isn't exempt from collapse, add it as obfuscated value
- if (entry.overlayable_item) {
+ auto onObfuscate = [this, &local_key_index, &entry](Obfuscator::Result obfuscatedResult,
+ const ResourceName& resource_name) {
+ if (obfuscatedResult == Obfuscator::Result::Keep_ExemptionList) {
+ local_key_index = (uint32_t)key_pool_.MakeRef(entry.name).index();
+ } else if (obfuscatedResult == Obfuscator::Result::Keep_Overlayable) {
// if the resource name of the specific entry is obfuscated and this
// entry is in the overlayable list, the overlay can't work on this
// overlayable at runtime because the name has been obfuscated in
// resources.arsc during flatten operation.
const OverlayableItem& item = entry.overlayable_item.value();
context_->GetDiagnostics()->Warn(android::DiagMessage(item.overlayable->source)
- << "The resource name of overlayable entry "
- << resource_name.to_string() << "'"
- << " shouldn't be obfuscated in resources.arsc");
+ << "The resource name of overlayable entry '"
+ << resource_name.to_string()
+ << "' shouldn't be obfuscated in resources.arsc");
local_key_index = (uint32_t)key_pool_.MakeRef(entry.name).index();
} else {
- // TODO(b/228192695): output the entry.name and Resource id to make
- // de-obfuscated possible.
- local_key_index = (uint32_t)key_pool_.MakeRef(obfuscated_resource_name).index();
+ local_key_index =
+ (uint32_t)key_pool_.MakeRef(Obfuscator::kObfuscatedResourceName).index();
}
- }
+ };
+
+ Obfuscator::ObfuscateResourceName(collapse_key_stringpool_, name_collapse_exemptions_,
+ type.named_type, entry, onObfuscate);
+
// Group values by configuration.
for (auto& config_value : entry.values) {
config_to_entry_list_map[config_value->config].push_back(
diff --git a/tools/aapt2/format/binary/TableFlattener.h b/tools/aapt2/format/binary/TableFlattener.h
index 35254ba..60605d2 100644
--- a/tools/aapt2/format/binary/TableFlattener.h
+++ b/tools/aapt2/format/binary/TableFlattener.h
@@ -14,8 +14,13 @@
* limitations under the License.
*/
-#ifndef AAPT_FORMAT_BINARY_TABLEFLATTENER_H
-#define AAPT_FORMAT_BINARY_TABLEFLATTENER_H
+#ifndef TOOLS_AAPT2_FORMAT_BINARY_TABLEFLATTENER_H_
+#define TOOLS_AAPT2_FORMAT_BINARY_TABLEFLATTENER_H_
+
+#include <map>
+#include <set>
+#include <string>
+#include <unordered_map>
#include "Resource.h"
#include "ResourceTable.h"
@@ -71,6 +76,9 @@
//
// This applies only to simple entries (entry->flags & ResTable_entry::FLAG_COMPLEX == 0).
bool deduplicate_entry_values = false;
+
+ // Map from original resource ids to obfuscated names.
+ std::unordered_map<uint32_t, std::string> id_resource_map;
};
class TableFlattener : public IResourceTableConsumer {
@@ -82,12 +90,12 @@
bool Consume(IAaptContext* context, ResourceTable* table) override;
private:
- DISALLOW_COPY_AND_ASSIGN(TableFlattener);
-
TableFlattenerOptions options_;
android::BigBuffer* buffer_;
+
+ DISALLOW_COPY_AND_ASSIGN(TableFlattener);
};
} // namespace aapt
-#endif /* AAPT_FORMAT_BINARY_TABLEFLATTENER_H */
+#endif // TOOLS_AAPT2_FORMAT_BINARY_TABLEFLATTENER_H_
diff --git a/tools/aapt2/format/proto/ProtoSerialize.cpp b/tools/aapt2/format/proto/ProtoSerialize.cpp
index a6d58fd..0e40124 100644
--- a/tools/aapt2/format/proto/ProtoSerialize.cpp
+++ b/tools/aapt2/format/proto/ProtoSerialize.cpp
@@ -18,6 +18,7 @@
#include "ValueVisitor.h"
#include "androidfw/BigBuffer.h"
+#include "optimize/Obfuscator.h"
using android::ConfigDescription;
@@ -366,21 +367,21 @@
}
pb_type->set_name(type.named_type.to_string());
- // hardcoded string uses characters which make it an invalid resource name
- static const char* obfuscated_resource_name = "0_resource_name_obfuscated";
for (const auto& entry : type.entries) {
pb::Entry* pb_entry = pb_type->add_entry();
if (entry.id) {
pb_entry->mutable_entry_id()->set_id(entry.id.value());
}
- ResourceName resource_name({}, type.named_type, entry.name);
- if (options.collapse_key_stringpool &&
- options.name_collapse_exemptions.find(resource_name) ==
- options.name_collapse_exemptions.end()) {
- pb_entry->set_name(obfuscated_resource_name);
- } else {
- pb_entry->set_name(entry.name);
- }
+ auto onObfuscate = [pb_entry, &entry](Obfuscator::Result obfuscatedResult,
+ const ResourceName& resource_name) {
+ pb_entry->set_name(obfuscatedResult == Obfuscator::Result::Obfuscated
+ ? Obfuscator::kObfuscatedResourceName
+ : entry.name);
+ };
+
+ Obfuscator::ObfuscateResourceName(options.collapse_key_stringpool,
+ options.name_collapse_exemptions, type.named_type, entry,
+ onObfuscate);
// Write the Visibility struct.
pb::Visibility* pb_visibility = pb_entry->mutable_visibility();
diff --git a/tools/aapt2/optimize/Obfuscator.cpp b/tools/aapt2/optimize/Obfuscator.cpp
index 1fdd728..2533f80 100644
--- a/tools/aapt2/optimize/Obfuscator.cpp
+++ b/tools/aapt2/optimize/Obfuscator.cpp
@@ -16,6 +16,7 @@
#include "optimize/Obfuscator.h"
+#include <map>
#include <set>
#include <string>
#include <unordered_set>
@@ -32,7 +33,10 @@
namespace aapt {
-Obfuscator::Obfuscator(std::map<std::string, std::string>& path_map_out) : path_map_(path_map_out) {
+Obfuscator::Obfuscator(OptimizeOptions& optimizeOptions)
+ : options_(optimizeOptions.table_flattener_options),
+ shorten_resource_paths_(optimizeOptions.shorten_resource_paths),
+ collapse_key_stringpool_(optimizeOptions.table_flattener_options.collapse_key_stringpool) {
}
std::string ShortenFileName(android::StringPiece file_path, int output_length) {
@@ -77,7 +81,8 @@
}
};
-bool Obfuscator::Consume(IAaptContext* context, ResourceTable* table) {
+static bool HandleShortenFilePaths(ResourceTable* table,
+ std::map<std::string, std::string>& shortened_path_map) {
// used to detect collisions
std::unordered_set<std::string> shortened_paths;
std::set<FileReference*, PathComparator> file_refs;
@@ -109,10 +114,94 @@
shortened_path = GetShortenedPath(shortened_filename, extension, collision_count);
}
shortened_paths.insert(shortened_path);
- path_map_.insert({*file_ref->path, shortened_path});
+ shortened_path_map.insert({*file_ref->path, shortened_path});
file_ref->path = table->string_pool.MakeRef(shortened_path, file_ref->path.GetContext());
}
return true;
}
+void Obfuscator::ObfuscateResourceName(
+ const bool collapse_key_stringpool, const std::set<ResourceName>& name_collapse_exemptions,
+ const ResourceNamedType& type_name, const ResourceTableEntryView& entry,
+ const android::base::function_ref<void(Result obfuscatedResult, const ResourceName&)>
+ onObfuscate) {
+ ResourceName resource_name({}, type_name, entry.name);
+ if (!collapse_key_stringpool ||
+ name_collapse_exemptions.find(resource_name) != name_collapse_exemptions.end()) {
+ onObfuscate(Result::Keep_ExemptionList, resource_name);
+ } else {
+ // resource isn't exempt from collapse, add it as obfuscated value
+ if (entry.overlayable_item) {
+ // if the resource name of the specific entry is obfuscated and this
+ // entry is in the overlayable list, the overlay can't work on this
+ // overlayable at runtime because the name has been obfuscated in
+ // resources.arsc during flatten operation.
+ onObfuscate(Result::Keep_Overlayable, resource_name);
+ } else {
+ onObfuscate(Result::Obfuscated, resource_name);
+ }
+ }
+}
+
+static bool HandleCollapseKeyStringPool(
+ const ResourceTable* table, const bool collapse_key_string_pool,
+ const std::set<ResourceName>& name_collapse_exemptions,
+ std::unordered_map<uint32_t, std::string>& id_resource_map) {
+ if (!collapse_key_string_pool) {
+ return true;
+ }
+
+ int entryResId = 0;
+ auto onObfuscate = [&entryResId, &id_resource_map](const Obfuscator::Result obfuscatedResult,
+ const ResourceName& resource_name) {
+ if (obfuscatedResult == Obfuscator::Result::Obfuscated) {
+ id_resource_map.insert({entryResId, resource_name.entry});
+ }
+ };
+
+ for (auto& package : table->packages) {
+ for (auto& type : package->types) {
+ for (auto& entry : type->entries) {
+ if (!entry->id.has_value() || entry->name.empty()) {
+ continue;
+ }
+ entryResId = entry->id->id;
+ ResourceTableEntryView entry_view{
+ .name = entry->name,
+ .id = entry->id ? entry->id.value().entry_id() : (std::optional<uint16_t>)std::nullopt,
+ .visibility = entry->visibility,
+ .allow_new = entry->allow_new,
+ .overlayable_item = entry->overlayable_item,
+ .staged_id = entry->staged_id};
+
+ Obfuscator::ObfuscateResourceName(collapse_key_string_pool, name_collapse_exemptions,
+ type->named_type, entry_view, onObfuscate);
+ }
+ }
+ }
+
+ return true;
+}
+
+bool Obfuscator::Consume(IAaptContext* context, ResourceTable* table) {
+ HandleCollapseKeyStringPool(table, options_.collapse_key_stringpool,
+ options_.name_collapse_exemptions, options_.id_resource_map);
+ if (shorten_resource_paths_) {
+ return HandleShortenFilePaths(table, options_.shortened_path_map);
+ }
+ return true;
+}
+
+/**
+ * Tell the optimizer whether it's needed to dump information for de-obfuscating.
+ *
+ * There are two conditions need to dump the information for de-obfuscating.
+ * * the option of shortening file paths is enabled.
+ * * the option of collapsing resource names is enabled.
+ * @return true if the information needed for de-obfuscating, otherwise false
+ */
+bool Obfuscator::IsEnabled() const {
+ return shorten_resource_paths_ || collapse_key_stringpool_;
+}
+
} // namespace aapt
diff --git a/tools/aapt2/optimize/Obfuscator.h b/tools/aapt2/optimize/Obfuscator.h
index 1ea32db..786bf8c 100644
--- a/tools/aapt2/optimize/Obfuscator.h
+++ b/tools/aapt2/optimize/Obfuscator.h
@@ -17,10 +17,14 @@
#ifndef TOOLS_AAPT2_OPTIMIZE_OBFUSCATOR_H_
#define TOOLS_AAPT2_OPTIMIZE_OBFUSCATOR_H_
-#include <map>
+#include <set>
#include <string>
+#include "ResourceTable.h"
+#include "android-base/function_ref.h"
#include "android-base/macros.h"
+#include "cmd/Optimize.h"
+#include "format/binary/TableFlattener.h"
#include "process/IResourceTableConsumer.h"
namespace aapt {
@@ -30,12 +34,26 @@
// Maps resources in the apk to shortened paths.
class Obfuscator : public IResourceTableConsumer {
public:
- explicit Obfuscator(std::map<std::string, std::string>& path_map_out);
+ explicit Obfuscator(OptimizeOptions& optimizeOptions);
bool Consume(IAaptContext* context, ResourceTable* table) override;
+ bool IsEnabled() const;
+
+ enum class Result { Obfuscated, Keep_ExemptionList, Keep_Overlayable };
+
+ // hardcoded string uses characters which make it an invalid resource name
+ static constexpr char kObfuscatedResourceName[] = "0_resource_name_obfuscated";
+
+ static void ObfuscateResourceName(
+ const bool collapse_key_stringpool, const std::set<ResourceName>& name_collapse_exemptions,
+ const ResourceNamedType& type_name, const ResourceTableEntryView& entry,
+ const android::base::function_ref<void(Result, const ResourceName&)> onObfuscate);
+
private:
- std::map<std::string, std::string>& path_map_;
+ TableFlattenerOptions& options_;
+ const bool shorten_resource_paths_;
+ const bool collapse_key_stringpool_;
DISALLOW_COPY_AND_ASSIGN(Obfuscator);
};
diff --git a/tools/aapt2/optimize/Obfuscator_test.cpp b/tools/aapt2/optimize/Obfuscator_test.cpp
index a3339d4..17d1e52 100644
--- a/tools/aapt2/optimize/Obfuscator_test.cpp
+++ b/tools/aapt2/optimize/Obfuscator_test.cpp
@@ -16,6 +16,7 @@
#include "optimize/Obfuscator.h"
+#include <map>
#include <memory>
#include <string>
@@ -51,8 +52,9 @@
.AddString("android:string/string", "res/should/still/be/the/same.png")
.Build();
- std::map<std::string, std::string> path_map;
- ASSERT_TRUE(Obfuscator(path_map).Consume(context.get(), table.get()));
+ OptimizeOptions options{.shorten_resource_paths = true};
+ std::map<std::string, std::string>& path_map = options.table_flattener_options.shortened_path_map;
+ ASSERT_TRUE(Obfuscator(options).Consume(context.get(), table.get()));
// Expect that the path map is populated
ASSERT_THAT(path_map.find("res/drawables/xmlfile.xml"), Not(Eq(path_map.end())));
@@ -87,8 +89,9 @@
test::ParseConfigOrDie("mdp-v21"))
.Build();
- std::map<std::string, std::string> path_map;
- ASSERT_TRUE(Obfuscator(path_map).Consume(context.get(), table.get()));
+ OptimizeOptions options{.shorten_resource_paths = true};
+ std::map<std::string, std::string>& path_map = options.table_flattener_options.shortened_path_map;
+ ASSERT_TRUE(Obfuscator(options).Consume(context.get(), table.get()));
// Expect that the path map to not contain the ColorStateList
ASSERT_THAT(path_map.find("res/color/colorlist.xml"), Eq(path_map.end()));
@@ -107,8 +110,9 @@
.AddFileReference("android:color/pngfile", original_png_path)
.Build();
- std::map<std::string, std::string> path_map;
- ASSERT_TRUE(Obfuscator(path_map).Consume(context.get(), table.get()));
+ OptimizeOptions options{.shorten_resource_paths = true};
+ std::map<std::string, std::string>& path_map = options.table_flattener_options.shortened_path_map;
+ ASSERT_TRUE(Obfuscator(options).Consume(context.get(), table.get()));
// Expect that the path map is populated
ASSERT_THAT(path_map.find("res/drawable/xmlfile.xml"), Not(Eq(path_map.end())));
@@ -133,8 +137,10 @@
test::ResourceTableBuilder builder1;
FillTable(builder1, 0, kNumResources);
std::unique_ptr<ResourceTable> table1 = builder1.Build();
- std::map<std::string, std::string> expected_mapping;
- ASSERT_TRUE(Obfuscator(expected_mapping).Consume(context.get(), table1.get()));
+ OptimizeOptions options{.shorten_resource_paths = true};
+ std::map<std::string, std::string>& expected_mapping =
+ options.table_flattener_options.shortened_path_map;
+ ASSERT_TRUE(Obfuscator(options).Consume(context.get(), table1.get()));
// We are trying to ensure lack of non-determinism, it is not simple to prove
// a negative, thus we must try the test a few times so that the test itself
@@ -153,8 +159,10 @@
FillTable(builder2, 0, start_index);
std::unique_ptr<ResourceTable> table2 = builder2.Build();
- std::map<std::string, std::string> actual_mapping;
- ASSERT_TRUE(Obfuscator(actual_mapping).Consume(context.get(), table2.get()));
+ OptimizeOptions actualOptimizerOptions{.shorten_resource_paths = true};
+ TableFlattenerOptions& actual_options = actualOptimizerOptions.table_flattener_options;
+ std::map<std::string, std::string>& actual_mapping = actual_options.shortened_path_map;
+ ASSERT_TRUE(Obfuscator(actualOptimizerOptions).Consume(context.get(), table2.get()));
for (auto& item : actual_mapping) {
ASSERT_THAT(expected_mapping[item.first], Eq(item.second));
@@ -162,4 +170,70 @@
}
}
+TEST(ObfuscatorTest, DumpIdResourceMap) {
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
+
+ OverlayableItem overlayable_item(std::make_shared<Overlayable>("TestName", "overlay://theme"));
+ overlayable_item.policies |= PolicyFlags::PRODUCT_PARTITION;
+ overlayable_item.policies |= PolicyFlags::SYSTEM_PARTITION;
+ overlayable_item.policies |= PolicyFlags::VENDOR_PARTITION;
+
+ std::string original_xml_path = "res/drawable/xmlfile.xml";
+ std::string original_png_path = "res/drawable/pngfile.png";
+
+ std::string name = "com.app.test:string/overlayable";
+ std::unique_ptr<ResourceTable> table =
+ test::ResourceTableBuilder()
+ .AddFileReference("android:color/xmlfile", original_xml_path)
+ .AddFileReference("android:color/pngfile", original_png_path)
+ .AddValue("com.app.test:color/mycolor", aapt::ResourceId(0x7f020000),
+ aapt::util::make_unique<aapt::BinaryPrimitive>(
+ uint8_t(android::Res_value::TYPE_INT_COLOR_ARGB8), 0xffaabbcc))
+ .AddString("com.app.test:string/mystring", ResourceId(0x7f030000), "hi")
+ .AddString("com.app.test:string/in_exemption", ResourceId(0x7f030001), "Hi")
+ .AddString(name, ResourceId(0x7f030002), "HI")
+ .SetOverlayable(name, overlayable_item)
+ .Build();
+
+ OptimizeOptions options{.shorten_resource_paths = true};
+ TableFlattenerOptions& flattenerOptions = options.table_flattener_options;
+ flattenerOptions.collapse_key_stringpool = true;
+ flattenerOptions.name_collapse_exemptions.insert(
+ ResourceName({}, ResourceType::kString, "in_exemption"));
+ auto& id_resource_map = flattenerOptions.id_resource_map;
+ ASSERT_TRUE(Obfuscator(options).Consume(context.get(), table.get()));
+
+ // Expect that the id resource name map is populated
+ EXPECT_THAT(id_resource_map.at(0x7f020000), Eq("mycolor"));
+ EXPECT_THAT(id_resource_map.at(0x7f030000), Eq("mystring"));
+ EXPECT_THAT(id_resource_map.find(0x7f030001), Eq(id_resource_map.end()));
+ EXPECT_THAT(id_resource_map.find(0x7f030002), Eq(id_resource_map.end()));
+}
+
+TEST(ObfuscatorTest, IsEnabledWithDefaultOption) {
+ OptimizeOptions options;
+ Obfuscator obfuscatorWithDefaultOption(options);
+ ASSERT_THAT(obfuscatorWithDefaultOption.IsEnabled(), Eq(false));
+}
+
+TEST(ObfuscatorTest, IsEnabledWithShortenPathOption) {
+ OptimizeOptions options{.shorten_resource_paths = true};
+ Obfuscator obfuscatorWithShortenPathOption(options);
+ ASSERT_THAT(obfuscatorWithShortenPathOption.IsEnabled(), Eq(true));
+}
+
+TEST(ObfuscatorTest, IsEnabledWithCollapseStringPoolOption) {
+ OptimizeOptions options;
+ options.table_flattener_options.collapse_key_stringpool = true;
+ Obfuscator obfuscatorWithCollapseStringPoolOption(options);
+ ASSERT_THAT(obfuscatorWithCollapseStringPoolOption.IsEnabled(), Eq(true));
+}
+
+TEST(ObfuscatorTest, IsEnabledWithShortenPathAndCollapseStringPoolOption) {
+ OptimizeOptions options{.shorten_resource_paths = true};
+ options.table_flattener_options.collapse_key_stringpool = true;
+ Obfuscator obfuscatorWithCollapseStringPoolOption(options);
+ ASSERT_THAT(obfuscatorWithCollapseStringPoolOption.IsEnabled(), Eq(true));
+}
+
} // namespace aapt