Merge "Revert "Add TEST_MAPPING file for ExternalStorageProvider package"" into main
diff --git a/cmds/screencap/screencap.cpp b/cmds/screencap/screencap.cpp
index 7e4f95b..01b20f4 100644
--- a/cmds/screencap/screencap.cpp
+++ b/cmds/screencap/screencap.cpp
@@ -14,6 +14,9 @@
* limitations under the License.
*/
+#include <android/bitmap.h>
+#include <android/gui/DisplayCaptureArgs.h>
+#include <binder/ProcessState.h>
#include <errno.h>
#include <unistd.h>
#include <stdio.h>
@@ -33,7 +36,6 @@
#include <ftl/concat.h>
#include <ftl/optional.h>
-#include <gui/DisplayCaptureArgs.h>
#include <gui/ISurfaceComposer.h>
#include <gui/SurfaceComposerClient.h>
#include <gui/SyncScreenCaptureListener.h>
diff --git a/core/api/current.txt b/core/api/current.txt
index c1366a1..66a1d30 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -8741,6 +8741,31 @@
method @NonNull public android.app.appfunctions.ExecuteAppFunctionRequest.Builder setParameters(@NonNull android.app.appsearch.GenericDocument);
}
+ @FlaggedApi("android.app.appfunctions.flags.enable_app_function_manager") public final class ExecuteAppFunctionResponse implements android.os.Parcelable {
+ method public int describeContents();
+ method @Nullable public String getErrorMessage();
+ method @NonNull public android.os.Bundle getExtras();
+ method public int getResultCode();
+ method @NonNull public android.app.appsearch.GenericDocument getResultDocument();
+ method public boolean isSuccess();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.app.appfunctions.ExecuteAppFunctionResponse> CREATOR;
+ field public static final String PROPERTY_RETURN_VALUE = "returnValue";
+ field public static final int RESULT_APP_UNKNOWN_ERROR = 2; // 0x2
+ field public static final int RESULT_DENIED = 1; // 0x1
+ field public static final int RESULT_INTERNAL_ERROR = 3; // 0x3
+ field public static final int RESULT_INVALID_ARGUMENT = 4; // 0x4
+ field public static final int RESULT_OK = 0; // 0x0
+ field public static final int RESULT_TIMED_OUT = 5; // 0x5
+ }
+
+ @FlaggedApi("android.app.appfunctions.flags.enable_app_function_manager") public static final class ExecuteAppFunctionResponse.Builder {
+ ctor public ExecuteAppFunctionResponse.Builder(@NonNull android.app.appsearch.GenericDocument);
+ ctor public ExecuteAppFunctionResponse.Builder(int, @NonNull String);
+ method @NonNull public android.app.appfunctions.ExecuteAppFunctionResponse build();
+ method @NonNull public android.app.appfunctions.ExecuteAppFunctionResponse.Builder setExtras(@NonNull android.os.Bundle);
+ }
+
}
package android.app.assist {
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index 69b5222..3d1a785 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -393,7 +393,7 @@
* changes.
*
* <p>This broadcast is only sent to registered receivers and receivers in packages that have
- * been granted Do Not Disturb access (see {@link #isNotificationPolicyAccessGranted()}).
+ * been granted Notification Policy access (see {@link #isNotificationPolicyAccessGranted()}).
*/
@FlaggedApi(Flags.FLAG_MODES_API)
@SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION)
@@ -1627,7 +1627,7 @@
}
/**
- * Checks the ability to modify notification do not disturb policy for the calling package.
+ * Checks the ability to modify Notification Policy for the calling package.
*
* <p>
* Returns true if the calling package can modify notification policy.
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index 114a2c4..cb38cf2 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -475,7 +475,7 @@
if (service == null
&& ctx.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH)
&& android.server.Flags.allowRemovingVpnService()) {
- throw new ServiceNotFoundException(Context.VPN_MANAGEMENT_SERVICE);
+ return null;
}
return new VpnManager(ctx, service);
}});
diff --git a/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.aidl b/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.aidl
new file mode 100644
index 0000000..5194e7a
--- /dev/null
+++ b/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.aidl
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2024 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.app.appfunctions;
+
+import android.app.appfunctions.ExecuteAppFunctionResponse;
+
+parcelable ExecuteAppFunctionResponse;
\ No newline at end of file
diff --git a/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java b/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java
new file mode 100644
index 0000000..72205d9
--- /dev/null
+++ b/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java
@@ -0,0 +1,294 @@
+/*
+ * Copyright (C) 2024 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.app.appfunctions;
+
+import static android.app.appfunctions.flags.Flags.FLAG_ENABLE_APP_FUNCTION_MANAGER;
+
+import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.appsearch.GenericDocument;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+
+/**
+ * The response to an app function execution.
+ */
+@FlaggedApi(FLAG_ENABLE_APP_FUNCTION_MANAGER)
+public final class ExecuteAppFunctionResponse implements Parcelable {
+ @NonNull
+ public static final Creator<ExecuteAppFunctionResponse> CREATOR =
+ new Creator<ExecuteAppFunctionResponse>() {
+ @Override
+ public ExecuteAppFunctionResponse createFromParcel(Parcel parcel) {
+ GenericDocument result =
+ Objects.requireNonNull(GenericDocument.createFromParcel(parcel));
+ Bundle extras = Objects.requireNonNull(
+ parcel.readBundle(Bundle.class.getClassLoader()));
+ int resultCode = parcel.readInt();
+ String errorMessage = parcel.readString8();
+ return new ExecuteAppFunctionResponse(result, extras, resultCode, errorMessage);
+ }
+
+ @Override
+ public ExecuteAppFunctionResponse[] newArray(int size) {
+ return new ExecuteAppFunctionResponse[size];
+ }
+ };
+ /**
+ * The name of the property that stores the function return value within the
+ * {@code resultDocument}.
+ *
+ * <p>See {@link GenericDocument#getProperty(String)} for more information.
+ *
+ * <p>If the function returns {@code void} or throws an error, the {@code resultDocument}
+ * will be empty {@link GenericDocument}.
+ *
+ * <p>If the {@code resultDocument} is empty, {@link GenericDocument#getProperty(String)} will
+ * return {@code null}.
+ *
+ * <p>See {@link #getResultDocument} for more information on extracting the return value.
+ */
+ public static final String PROPERTY_RETURN_VALUE = "returnValue";
+
+ /**
+ * The call was successful.
+ */
+ public static final int RESULT_OK = 0;
+
+ /**
+ * The caller does not have the permission to execute an app function.
+ */
+ public static final int RESULT_DENIED = 1;
+
+ /**
+ * An unknown error occurred while processing the call in the AppFunctionService.
+ */
+ public static final int RESULT_APP_UNKNOWN_ERROR = 2;
+
+ /**
+ * An internal error occurred within AppFunctionManagerService.
+ *
+ * <p>This error may be considered similar to {@link IllegalStateException}
+ */
+ public static final int RESULT_INTERNAL_ERROR = 3;
+
+ /**
+ * The caller supplied invalid arguments to the call.
+ *
+ * <p>This error may be considered similar to {@link IllegalArgumentException}.
+ */
+ public static final int RESULT_INVALID_ARGUMENT = 4;
+
+ /**
+ * The operation was timed out.
+ */
+ public static final int RESULT_TIMED_OUT = 5;
+
+ /**
+ * The result code of the app function execution.
+ */
+ @ResultCode
+ private final int mResultCode;
+
+ /**
+ * The error message associated with the result, if any. This is {@code null} if the result code
+ * is {@link #RESULT_OK}.
+ */
+ @Nullable
+ private final String mErrorMessage;
+
+ /**
+ * Returns the return value of the executed function.
+ *
+ * <p>The return value is stored in a {@link GenericDocument} with the key
+ * {@link #PROPERTY_RETURN_VALUE}.
+ *
+ * <p>See {@link #getResultDocument} for more information on extracting the return value.
+ */
+ @NonNull
+ private final GenericDocument mResultDocument;
+
+ /**
+ * Returns the additional metadata data relevant to this function execution response.
+ */
+ @NonNull
+ private final Bundle mExtras;
+
+ private ExecuteAppFunctionResponse(@NonNull GenericDocument resultDocument,
+ @NonNull Bundle extras,
+ @ResultCode int resultCode,
+ @Nullable String errorMessage) {
+ mResultDocument = Objects.requireNonNull(resultDocument);
+ mExtras = Objects.requireNonNull(extras);
+ mResultCode = resultCode;
+ mErrorMessage = errorMessage;
+ }
+
+ /**
+ * Returns a generic document containing the return value of the executed function.
+ *
+ * <p>The {@link #PROPERTY_RETURN_VALUE} key can be used to obtain the return value.</p>
+ *
+ * <p>An empty document is returned if {@link #isSuccess} is {@code false} or if the executed
+ * function does not produce a return value.
+ *
+ * <p>Sample code for extracting the return value:
+ * <pre>
+ * GenericDocument resultDocument = response.getResultDocument();
+ * Object returnValue = resultDocument.getProperty(PROPERTY_RETURN_VALUE);
+ * if (returnValue != null) {
+ * // Cast returnValue to expected type, or use {@link GenericDocument#getPropertyString},
+ * // {@link GenericDocument#getPropertyLong} etc.
+ * // Do something with the returnValue
+ * }
+ * </pre>
+ */
+ @NonNull
+ public GenericDocument getResultDocument() {
+ return mResultDocument;
+ }
+
+ /**
+ * Returns the extras of the app function execution.
+ */
+ @NonNull
+ public Bundle getExtras() {
+ return mExtras;
+ }
+
+ /**
+ * Returns {@code true} if {@link #getResultCode} equals
+ * {@link ExecuteAppFunctionResponse#RESULT_OK}.
+ */
+ public boolean isSuccess() {
+ return getResultCode() == RESULT_OK;
+ }
+
+ /**
+ * Returns one of the {@code RESULT} constants defined in {@link ExecuteAppFunctionResponse}.
+ */
+ @ResultCode
+ public int getResultCode() {
+ return mResultCode;
+ }
+
+ /**
+ * Returns the error message associated with this result.
+ *
+ * <p>If {@link #isSuccess} is {@code true}, the error message is always {@code null}.
+ */
+ @Nullable
+ public String getErrorMessage() {
+ return mErrorMessage;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ mResultDocument.writeToParcel(dest, flags);
+ dest.writeBundle(mExtras);
+ dest.writeInt(mResultCode);
+ dest.writeString8(mErrorMessage);
+ }
+
+ /**
+ * Result codes.
+ *
+ * @hide
+ */
+ @IntDef(
+ prefix = {"RESULT_"},
+ value = {
+ RESULT_OK,
+ RESULT_DENIED,
+ RESULT_APP_UNKNOWN_ERROR,
+ RESULT_INTERNAL_ERROR,
+ RESULT_INVALID_ARGUMENT,
+ RESULT_TIMED_OUT,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ResultCode {
+ }
+
+ /**
+ * The builder for creating {@link ExecuteAppFunctionResponse} instances.
+ */
+ @FlaggedApi(FLAG_ENABLE_APP_FUNCTION_MANAGER)
+ public static final class Builder {
+ @NonNull
+ private GenericDocument mResultDocument = new GenericDocument.Builder<>("", "", "").build();
+ @NonNull
+ private Bundle mExtras = Bundle.EMPTY;
+ private int mResultCode;
+ @Nullable
+ private String mErrorMessage;
+
+ /**
+ * Creates a new builder for {@link ExecuteAppFunctionResponse}.
+ */
+ private Builder() {
+ }
+
+ /**
+ * Creates a new builder for {@link ExecuteAppFunctionResponse} to build a success response
+ * with a result code of {@link #RESULT_OK} and a resultDocument.
+ */
+ public Builder(@NonNull GenericDocument resultDocument) {
+ mResultDocument = Objects.requireNonNull(resultDocument);
+ mResultCode = RESULT_OK;
+ }
+
+ /**
+ * Creates a new builder for {@link ExecuteAppFunctionResponse} to build an error response
+ * with a result code and an error message.
+ */
+ public Builder(@ResultCode int resultCode,
+ @NonNull String errorMessage) {
+ mResultCode = resultCode;
+ mErrorMessage = Objects.requireNonNull(errorMessage);
+ }
+
+ /**
+ * Sets the extras of the app function execution.
+ */
+ @NonNull
+ public Builder setExtras(@NonNull Bundle extras) {
+ mExtras = Objects.requireNonNull(extras);
+ return this;
+ }
+
+ /**
+ * Builds the {@link ExecuteAppFunctionResponse} instance.
+ */
+ @NonNull
+ public ExecuteAppFunctionResponse build() {
+ return new ExecuteAppFunctionResponse(
+ mResultDocument, mExtras, mResultCode, mErrorMessage);
+ }
+ }
+}
diff --git a/core/java/android/app/notification.aconfig b/core/java/android/app/notification.aconfig
index f751a23..f05c24f 100644
--- a/core/java/android/app/notification.aconfig
+++ b/core/java/android/app/notification.aconfig
@@ -21,6 +21,13 @@
}
flag {
+ name: "modes_ui_test"
+ namespace: "systemui"
+ description: "Guards new CTS tests for Modes; dependent on flags modes_api and modes_ui"
+ bug: "360862012"
+}
+
+flag {
name: "api_tvextender"
is_exported: true
namespace: "systemui"
diff --git a/core/java/android/hardware/input/input_framework.aconfig b/core/java/android/hardware/input/input_framework.aconfig
index 6f1d63d8..83c4de3 100644
--- a/core/java/android/hardware/input/input_framework.aconfig
+++ b/core/java/android/hardware/input/input_framework.aconfig
@@ -92,3 +92,10 @@
description: "Dump keyboard shortcuts in dumpsys window"
bug: "351963350"
}
+
+flag {
+ name: "modifier_shortcut_manager_refactor"
+ namespace: "input"
+ description: "Refactor ModifierShortcutManager internal representation of shortcuts."
+ bug: "358603902"
+}
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 28f2c25..536eca6 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -969,9 +969,7 @@
/**
* Specifies if a user is disallowed from adding new users. This can only be set by device
- * owners or profile owners on the primary user. The default value is <code>false</code>.
- * <p>This restriction has no effect on secondary users and managed profiles since only the
- * primary user can add other users.
+ * owners or profile owners on the main user. The default value is <code>false</code>.
* <p> When the device is an organization-owned device provisioned with a managed profile,
* this restriction will be set as a base restriction which cannot be removed by any admin.
*
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 184bac4..85d2325 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -1972,10 +1972,10 @@
"android.provider.extra.NOTIFICATION_LISTENER_COMPONENT_NAME";
/**
- * Activity Action: Show Do Not Disturb access settings.
+ * Activity Action: Show Notification Policy access settings.
* <p>
- * Users can grant and deny access to Do Not Disturb configuration from here. Managed
- * profiles cannot grant Do Not Disturb access.
+ * Users can grant and deny access to Notification Policy (DND / Priority Modes) configuration
+ * from here. Managed profiles cannot grant Notification Policy access.
* See {@link android.app.NotificationManager#isNotificationPolicyAccessGranted()} for more
* details.
* <p>
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 017e004..67a207e 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -3462,6 +3462,15 @@
public static final int PRIVATE_FLAG_NOT_MAGNIFIABLE = 1 << 22;
/**
+ * Indicates that the window should receive key events including Action/Meta key.
+ * They will not be intercepted as usual and instead will be passed to the window with other
+ * key events.
+ * TODO(b/358569822) Remove this once we have nicer API for listening to shortcuts
+ * @hide
+ */
+ public static final int PRIVATE_FLAG_ALLOW_ACTION_KEY_EVENTS = 1 << 23;
+
+ /**
* Flag to indicate that the window is color space agnostic, and the color can be
* interpreted to any color space.
* @hide
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index eb35817..1922327 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -83,6 +83,7 @@
import android.os.Process;
import android.os.RemoteException;
import android.os.StrictMode;
+import android.os.Trace;
import android.os.UserHandle;
import android.system.Os;
import android.text.TextUtils;
@@ -6246,6 +6247,18 @@
private View inflateView(Context context, RemoteViews rv, @Nullable ViewGroup parent,
@StyleRes int applyThemeResId, @Nullable ColorResources colorResources) {
+ try {
+ Trace.beginSection(rv.hasDrawInstructions()
+ ? "RemoteViews#inflateViewWithDrawInstructions"
+ : "RemoteViews#inflateView");
+ return inflateViewInternal(context, rv, parent, applyThemeResId, colorResources);
+ } finally {
+ Trace.endSection();
+ }
+ }
+
+ private View inflateViewInternal(Context context, RemoteViews rv, @Nullable ViewGroup parent,
+ @StyleRes int applyThemeResId, @Nullable ColorResources colorResources) {
// RemoteViews may be built by an application installed in another
// user. So build a context that loads resources from that user but
// still returns the current users userId so settings like data / time formats
@@ -6384,7 +6397,7 @@
private View mResult;
private ViewTree mTree;
- private Action[] mActions;
+ private List<Action> mActions;
private Exception mError;
private AsyncApplyTask(
@@ -6411,11 +6424,20 @@
if (mRV.mActions != null) {
int count = mRV.mActions.size();
- mActions = new Action[count];
- for (int i = 0; i < count && !isCancelled(); i++) {
- // TODO: check if isCancelled in nested views.
- mActions[i] = mRV.mActions.get(i)
- .initActionAsync(mTree, mParent, mApplyParams);
+ mActions = new ArrayList<>(count);
+ try {
+ Trace.beginSection(hasDrawInstructions()
+ ? "RemoteViews#initActionAsyncWithDrawInstructions"
+ : "RemoteViews#initActionAsync");
+ for (Action action : mRV.mActions) {
+ if (isCancelled()) {
+ break;
+ }
+ // TODO: check if isCancelled in nested views.
+ mActions.add(action.initActionAsync(mTree, mParent, mApplyParams));
+ }
+ } finally {
+ Trace.endSection();
}
} else {
mActions = null;
@@ -6437,14 +6459,7 @@
try {
if (mActions != null) {
-
- ActionApplyParams applyParams = mApplyParams.clone();
- if (applyParams.handler == null) {
- applyParams.handler = DEFAULT_INTERACTION_HANDLER;
- }
- for (Action a : mActions) {
- a.apply(viewTree.mRoot, mParent, applyParams);
- }
+ mRV.performApply(viewTree.mRoot, mParent, mApplyParams, mActions);
}
// If the parent of the view is has is a root, resolve the recycling.
if (mTopLevel && mResult instanceof ViewGroup) {
@@ -6620,6 +6635,11 @@
}
private void performApply(View v, ViewGroup parent, ActionApplyParams params) {
+ performApply(v, parent, params, mActions);
+ }
+
+ private void performApply(
+ View v, ViewGroup parent, ActionApplyParams params, List<Action> actions) {
params = params.clone();
if (params.handler == null) {
params.handler = DEFAULT_INTERACTION_HANDLER;
@@ -6630,8 +6650,15 @@
}
if (mActions != null) {
final int count = mActions.size();
- for (int i = 0; i < count; i++) {
- mActions.get(i).apply(v, parent, params);
+ try {
+ Trace.beginSection(hasDrawInstructions()
+ ? "RemoteViews#applyActionsWithDrawInstructions"
+ : "RemoteViews#applyActions");
+ for (int i = 0; i < count; i++) {
+ mActions.get(i).apply(v, parent, params);
+ }
+ } finally {
+ Trace.endSection();
}
}
}
diff --git a/core/java/android/window/TransitionRequestInfo.java b/core/java/android/window/TransitionRequestInfo.java
index 253337b..fe936f7 100644
--- a/core/java/android/window/TransitionRequestInfo.java
+++ b/core/java/android/window/TransitionRequestInfo.java
@@ -115,8 +115,11 @@
@DataClass(genToString = true, genSetters = true, genBuilder = false, genConstructor = false)
public static final class DisplayChange implements Parcelable {
private final int mDisplayId;
+
+ // If non-null, these bounds changes should ignore any potential rotation changes.
@Nullable private Rect mStartAbsBounds = null;
@Nullable private Rect mEndAbsBounds = null;
+
private int mStartRotation = WindowConfiguration.ROTATION_UNDEFINED;
private int mEndRotation = WindowConfiguration.ROTATION_UNDEFINED;
private boolean mPhysicalDisplayChanged = false;
diff --git a/core/java/com/android/internal/pm/pkg/component/ParsedProviderImpl.java b/core/java/com/android/internal/pm/pkg/component/ParsedProviderImpl.java
index 987fd41..ec5ff4c 100644
--- a/core/java/com/android/internal/pm/pkg/component/ParsedProviderImpl.java
+++ b/core/java/com/android/internal/pm/pkg/component/ParsedProviderImpl.java
@@ -174,7 +174,7 @@
// CHECKSTYLE:OFF Generated code
//
// To regenerate run:
- // $ codegen $ANDROID_BUILD_TOP/frameworks/base/services/core/java/com/android/server/pm/pkg/component/ParsedProviderImpl.java
+ // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/com/android/internal/pm/pkg/component/ParsedProviderImpl.java
//
// To exclude the generated code from IntelliJ auto-formatting enable (one-time):
// Settings > Editor > Code Style > Formatter Control
@@ -298,9 +298,9 @@
}
@DataClass.Generated(
- time = 1642560323360L,
+ time = 1723882842941L,
codegenVersion = "1.0.23",
- sourceFile = "frameworks/base/services/core/java/com/android/server/pm/pkg/component/ParsedProviderImpl.java",
+ sourceFile = "frameworks/base/core/java/com/android/internal/pm/pkg/component/ParsedProviderImpl.java",
inputSignatures = "private @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String authority\nprivate boolean syncable\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String readPermission\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String writePermission\nprivate boolean grantUriPermissions\nprivate boolean forceUriPermissions\nprivate boolean multiProcess\nprivate int initOrder\nprivate @android.annotation.NonNull java.util.List<android.os.PatternMatcher> uriPermissionPatterns\nprivate @android.annotation.NonNull java.util.List<android.content.pm.PathPermission> pathPermissions\npublic static final @android.annotation.NonNull android.os.Parcelable.Creator<com.android.internal.pm.pkg.component.ParsedProviderImpl> CREATOR\npublic com.android.internal.pm.pkg.component.ParsedProviderImpl setReadPermission(java.lang.String)\npublic com.android.internal.pm.pkg.component.ParsedProviderImpl setWritePermission(java.lang.String)\npublic @android.annotation.NonNull com.android.internal.pm.pkg.component.ParsedProviderImpl addUriPermissionPattern(android.os.PatternMatcher)\npublic @android.annotation.NonNull com.android.internal.pm.pkg.component.ParsedProviderImpl addPathPermission(android.content.pm.PathPermission)\npublic java.lang.String toString()\npublic @java.lang.Override int describeContents()\npublic @java.lang.Override void writeToParcel(android.os.Parcel,int)\nclass ParsedProviderImpl extends com.android.internal.pm.pkg.component.ParsedMainComponentImpl implements [com.android.internal.pm.pkg.component.ParsedProvider, android.os.Parcelable]\n@com.android.internal.util.DataClass(genSetters=true, genGetters=true, genParcelable=false, genBuilder=false)")
@Deprecated
private void __metadata() {}
diff --git a/core/java/com/android/internal/protolog/LegacyProtoLogImpl.java b/core/java/com/android/internal/protolog/LegacyProtoLogImpl.java
index fcc3023..2feb3d5 100644
--- a/core/java/com/android/internal/protolog/LegacyProtoLogImpl.java
+++ b/core/java/com/android/internal/protolog/LegacyProtoLogImpl.java
@@ -419,7 +419,6 @@
return group.isLogToLogcat() || (group.isLogToProto() && isProtoEnabled());
}
- @Override
public void registerGroups(IProtoLogGroup... protoLogGroups) {
for (IProtoLogGroup group : protoLogGroups) {
mLogGroups.put(group.name(), group);
diff --git a/core/java/com/android/internal/protolog/LogcatOnlyProtoLogImpl.java b/core/java/com/android/internal/protolog/LogcatOnlyProtoLogImpl.java
index ebdad6d..e8d5195 100644
--- a/core/java/com/android/internal/protolog/LogcatOnlyProtoLogImpl.java
+++ b/core/java/com/android/internal/protolog/LogcatOnlyProtoLogImpl.java
@@ -79,9 +79,4 @@
public boolean isEnabled(IProtoLogGroup group, LogLevel level) {
return true;
}
-
- @Override
- public void registerGroups(IProtoLogGroup... protoLogGroups) {
- // Does nothing
- }
}
diff --git a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
index 79a5469..78b5cfe 100644
--- a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
+++ b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
@@ -89,6 +89,7 @@
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
+import java.util.stream.Stream;
/**
* A service for the ProtoLog logging system.
@@ -125,17 +126,18 @@
private final Lock mBackgroundServiceLock = new ReentrantLock();
private ExecutorService mBackgroundLoggingService = Executors.newSingleThreadExecutor();
- public PerfettoProtoLogImpl() {
- this(null, null, null, () -> {});
+ public PerfettoProtoLogImpl(@NonNull IProtoLogGroup[] groups) {
+ this(null, null, null, () -> {}, groups);
}
- public PerfettoProtoLogImpl(@NonNull Runnable cacheUpdater) {
- this(null, null, null, cacheUpdater);
+ public PerfettoProtoLogImpl(@NonNull Runnable cacheUpdater, @NonNull IProtoLogGroup[] groups) {
+ this(null, null, null, cacheUpdater, groups);
}
public PerfettoProtoLogImpl(
@NonNull String viewerConfigFilePath,
- @NonNull Runnable cacheUpdater) {
+ @NonNull Runnable cacheUpdater,
+ @NonNull IProtoLogGroup[] groups) {
this(viewerConfigFilePath,
null,
new ProtoLogViewerConfigReader(() -> {
@@ -146,22 +148,24 @@
"Failed to load viewer config file " + viewerConfigFilePath, e);
}
}),
- cacheUpdater);
+ cacheUpdater, groups);
}
@VisibleForTesting
public PerfettoProtoLogImpl(
@Nullable ViewerConfigInputStreamProvider viewerConfigInputStreamProvider,
@Nullable ProtoLogViewerConfigReader viewerConfigReader,
- @NonNull Runnable cacheUpdater) {
- this(null, viewerConfigInputStreamProvider, viewerConfigReader, cacheUpdater);
+ @NonNull Runnable cacheUpdater,
+ @NonNull IProtoLogGroup[] groups) {
+ this(null, viewerConfigInputStreamProvider, viewerConfigReader, cacheUpdater, groups);
}
private PerfettoProtoLogImpl(
@Nullable String viewerConfigFilePath,
@Nullable ViewerConfigInputStreamProvider viewerConfigInputStreamProvider,
@Nullable ProtoLogViewerConfigReader viewerConfigReader,
- @NonNull Runnable cacheUpdater) {
+ @NonNull Runnable cacheUpdater,
+ @NonNull IProtoLogGroup[] groups) {
if (viewerConfigFilePath != null && viewerConfigInputStreamProvider != null) {
throw new RuntimeException("Only one of viewerConfigFilePath and "
+ "viewerConfigInputStreamProvider can be set");
@@ -179,6 +183,8 @@
this.mViewerConfigReader = viewerConfigReader;
this.mCacheUpdater = cacheUpdater;
+ registerGroupsLocally(groups);
+
if (android.tracing.Flags.clientSideProtoLogging()) {
mProtoLogService =
IProtoLogService.Stub.asInterface(ServiceManager.getService(PROTOLOG_SERVICE));
@@ -192,6 +198,12 @@
args.setViewerConfigFile(viewerConfigFilePath);
}
+ final var groupArgs = Stream.of(groups)
+ .map(group -> new ProtoLogService.RegisterClientArgs.GroupConfig(
+ group.name(), group.isLogToLogcat()))
+ .toArray(ProtoLogService.RegisterClientArgs.GroupConfig[]::new);
+ args.setGroups(groupArgs);
+
mProtoLogService.registerClient(this, args);
} catch (RemoteException e) {
throw new RuntimeException("Failed to register ProtoLog client");
@@ -294,19 +306,22 @@
|| group.isLogToLogcat();
}
- @Override
- public void registerGroups(IProtoLogGroup... protoLogGroups) {
+ private void registerGroupsLocally(@NonNull IProtoLogGroup[] protoLogGroups) {
+ final var groupsLoggingToLogcat = new ArrayList<String>();
for (IProtoLogGroup protoLogGroup : protoLogGroups) {
mLogGroups.put(protoLogGroup.name(), protoLogGroup);
+
+ if (protoLogGroup.isLogToLogcat()) {
+ groupsLoggingToLogcat.add(protoLogGroup.name());
+ }
}
- final String[] groupsLoggingToLogcat = Arrays.stream(protoLogGroups)
- .filter(IProtoLogGroup::isLogToLogcat)
- .map(IProtoLogGroup::name)
- .toArray(String[]::new);
-
if (mViewerConfigReader != null) {
- mViewerConfigReader.loadViewerConfig(groupsLoggingToLogcat);
+ // Load in background to avoid delay in boot process.
+ // The caveat is that any log message that is also logged to logcat will not be
+ // successfully decoded until this completes.
+ mBackgroundLoggingService.execute(() -> mViewerConfigReader
+ .loadViewerConfig(groupsLoggingToLogcat.toArray(new String[0])));
}
}
diff --git a/core/java/com/android/internal/protolog/ProtoLog.java b/core/java/com/android/internal/protolog/ProtoLog.java
index 87678e5..f9b9894 100644
--- a/core/java/com/android/internal/protolog/ProtoLog.java
+++ b/core/java/com/android/internal/protolog/ProtoLog.java
@@ -50,6 +50,24 @@
private static IProtoLog sProtoLogInstance;
/**
+ * Initialize ProtoLog in this process.
+ * <p>
+ * This method MUST be called before any protologging is performed in this process.
+ * Ensure that all groups that will be used for protologging are registered.
+ *
+ * @param groups The ProtoLog groups that will be used in the process.
+ */
+ public static void init(IProtoLogGroup... groups) {
+ if (android.tracing.Flags.perfettoProtologTracing()) {
+ sProtoLogInstance = new PerfettoProtoLogImpl(groups);
+ } else {
+ // The first call to ProtoLog is likely to flip REQUIRE_PROTOLOGTOOL, which is when this
+ // static block will be executed before REQUIRE_PROTOLOGTOOL is actually set.
+ sProtoLogInstance = new LogcatOnlyProtoLogImpl();
+ }
+ }
+
+ /**
* DEBUG level log.
*
* @param group {@code IProtoLogGroup} controlling this log call.
@@ -150,14 +168,6 @@
return sProtoLogInstance;
}
- /**
- * Registers available protolog groups. A group must be registered before it can be used.
- * @param protoLogGroups The groups to register for use in protolog.
- */
- public static void registerGroups(IProtoLogGroup... protoLogGroups) {
- sProtoLogInstance.registerGroups(protoLogGroups);
- }
-
private static void logStringMessage(LogLevel logLevel, IProtoLogGroup group,
String stringMessage, Object... args) {
if (sProtoLogInstance == null) {
@@ -169,14 +179,4 @@
sProtoLogInstance.log(logLevel, group, stringMessage, args);
}
}
-
- static {
- if (android.tracing.Flags.perfettoProtologTracing()) {
- sProtoLogInstance = new PerfettoProtoLogImpl();
- } else {
- // The first call to ProtoLog is likely to flip REQUIRE_PROTOLOGTOOL, which is when this
- // static block will be executed before REQUIRE_PROTOLOGTOOL is actually set.
- sProtoLogInstance = new LogcatOnlyProtoLogImpl();
- }
- }
}
diff --git a/core/java/com/android/internal/protolog/ProtoLogImpl.java b/core/java/com/android/internal/protolog/ProtoLogImpl.java
index 8659a8f..da6d8cf 100644
--- a/core/java/com/android/internal/protolog/ProtoLogImpl.java
+++ b/core/java/com/android/internal/protolog/ProtoLogImpl.java
@@ -93,35 +93,30 @@
}
/**
- * Registers available protolog groups. A group must be registered before it can be used.
- * @param protoLogGroups The groups to register for use in protolog.
- */
- public static void registerGroups(IProtoLogGroup... protoLogGroups) {
- getSingleInstance().registerGroups(protoLogGroups);
- }
-
- /**
* Returns the single instance of the ProtoLogImpl singleton class.
*/
public static synchronized IProtoLog getSingleInstance() {
if (sServiceInstance == null) {
+ final var groups = sLogGroups.values().toArray(new IProtoLogGroup[0]);
+
if (android.tracing.Flags.perfettoProtologTracing()) {
File f = new File(sViewerConfigPath);
if (!ProtoLog.REQUIRE_PROTOLOGTOOL && !f.exists()) {
// TODO(b/353530422): Remove - temporary fix to unblock b/352290057
- // In so tests the viewer config file might not exist in which we don't
+ // In some tests the viewer config file might not exist in which we don't
// want to provide config path to the user
- sServiceInstance = new PerfettoProtoLogImpl(sCacheUpdater);
+ sServiceInstance = new PerfettoProtoLogImpl(sCacheUpdater, groups);
} else {
- sServiceInstance = new PerfettoProtoLogImpl(sViewerConfigPath, sCacheUpdater);
+ sServiceInstance =
+ new PerfettoProtoLogImpl(sViewerConfigPath, sCacheUpdater, groups);
}
} else {
- sServiceInstance = new LegacyProtoLogImpl(
+ var protologImpl = new LegacyProtoLogImpl(
sLegacyOutputFilePath, sLegacyViewerConfigPath, sCacheUpdater);
+ protologImpl.registerGroups(groups);
+ sServiceInstance = protologImpl;
}
- IProtoLogGroup[] groups = sLogGroups.values().toArray(new IProtoLogGroup[0]);
- sServiceInstance.registerGroups(groups);
sCacheUpdater.run();
}
return sServiceInstance;
diff --git a/core/java/com/android/internal/protolog/common/IProtoLog.java b/core/java/com/android/internal/protolog/common/IProtoLog.java
index f5695ac..d5c2ac1 100644
--- a/core/java/com/android/internal/protolog/common/IProtoLog.java
+++ b/core/java/com/android/internal/protolog/common/IProtoLog.java
@@ -68,10 +68,4 @@
* @return If we need to log this group and level to either ProtoLog or Logcat.
*/
boolean isEnabled(IProtoLogGroup group, LogLevel level);
-
- /**
- * Registers available protolog groups. A group must be registered before it can be used.
- * @param protoLogGroups The groups to register for use in protolog.
- */
- void registerGroups(IProtoLogGroup... protoLogGroups);
}
diff --git a/core/jni/android_window_ScreenCapture.cpp b/core/jni/android_window_ScreenCapture.cpp
index 1031542..b1a2cea 100644
--- a/core/jni/android_window_ScreenCapture.cpp
+++ b/core/jni/android_window_ScreenCapture.cpp
@@ -19,6 +19,7 @@
#include <android/gui/BnScreenCaptureListener.h>
#include <android_runtime/android_hardware_HardwareBuffer.h>
+#include <gui/AidlStatusUtil.h>
#include <gui/SurfaceComposerClient.h>
#include <jni.h>
#include <nativehelper/JNIHelp.h>
@@ -141,12 +142,13 @@
};
static void getCaptureArgs(JNIEnv* env, jobject captureArgsObject, CaptureArgs& captureArgs) {
- captureArgs.pixelFormat = static_cast<ui::PixelFormat>(
+ captureArgs.pixelFormat = static_cast<int32_t>(
env->GetIntField(captureArgsObject, gCaptureArgsClassInfo.pixelFormat));
- captureArgs.sourceCrop =
+ const auto sourceCrop =
JNICommon::rectFromObj(env,
env->GetObjectField(captureArgsObject,
gCaptureArgsClassInfo.sourceCrop));
+ captureArgs.sourceCrop = gui::aidl_utils::toARect(sourceCrop);
captureArgs.frameScaleX =
env->GetFloatField(captureArgsObject, gCaptureArgsClassInfo.frameScaleX);
captureArgs.frameScaleY =
@@ -172,7 +174,7 @@
jniThrowNullPointerException(env, "Exclude layer is null");
return;
}
- captureArgs.excludeHandles.emplace(excludeObject->getHandle());
+ captureArgs.excludeHandles.emplace_back(excludeObject->getHandle());
}
}
captureArgs.hintForSeamlessTransition =
@@ -182,18 +184,18 @@
static DisplayCaptureArgs displayCaptureArgsFromObject(JNIEnv* env,
jobject displayCaptureArgsObject) {
- DisplayCaptureArgs captureArgs;
- getCaptureArgs(env, displayCaptureArgsObject, captureArgs);
+ DisplayCaptureArgs displayCaptureArgs;
+ getCaptureArgs(env, displayCaptureArgsObject, displayCaptureArgs.captureArgs);
- captureArgs.displayToken =
+ displayCaptureArgs.displayToken =
ibinderForJavaObject(env,
env->GetObjectField(displayCaptureArgsObject,
gDisplayCaptureArgsClassInfo.displayToken));
- captureArgs.width =
+ displayCaptureArgs.width =
env->GetIntField(displayCaptureArgsObject, gDisplayCaptureArgsClassInfo.width);
- captureArgs.height =
+ displayCaptureArgs.height =
env->GetIntField(displayCaptureArgsObject, gDisplayCaptureArgsClassInfo.height);
- return captureArgs;
+ return displayCaptureArgs;
}
static jint nativeCaptureDisplay(JNIEnv* env, jclass clazz, jobject displayCaptureArgsObject,
@@ -212,8 +214,8 @@
static jint nativeCaptureLayers(JNIEnv* env, jclass clazz, jobject layerCaptureArgsObject,
jlong screenCaptureListenerObject, jboolean sync) {
- LayerCaptureArgs captureArgs;
- getCaptureArgs(env, layerCaptureArgsObject, captureArgs);
+ LayerCaptureArgs layerCaptureArgs;
+ getCaptureArgs(env, layerCaptureArgsObject, layerCaptureArgs.captureArgs);
SurfaceControl* layer = reinterpret_cast<SurfaceControl*>(
env->GetLongField(layerCaptureArgsObject, gLayerCaptureArgsClassInfo.layer));
@@ -221,13 +223,13 @@
return BAD_VALUE;
}
- captureArgs.layerHandle = layer->getHandle();
- captureArgs.childrenOnly =
+ layerCaptureArgs.layerHandle = layer->getHandle();
+ layerCaptureArgs.childrenOnly =
env->GetBooleanField(layerCaptureArgsObject, gLayerCaptureArgsClassInfo.childrenOnly);
sp<gui::IScreenCaptureListener> captureListener =
reinterpret_cast<gui::IScreenCaptureListener*>(screenCaptureListenerObject);
- return ScreenshotClient::captureLayers(captureArgs, captureListener, sync);
+ return ScreenshotClient::captureLayers(layerCaptureArgs, captureListener, sync);
}
static jlong nativeCreateScreenCaptureListener(JNIEnv* env, jclass clazz, jobject consumerObj) {
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 1fe6ca7..9a55b80 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -592,6 +592,10 @@
<permission name="android.permission.INTERACT_ACROSS_USERS" />
</privapp-permissions>
+ <privapp-permissions package="com.android.providers.tv">
+ <permission name="android.permission.INTERACT_ACROSS_USERS"/>
+ </privapp-permissions>
+
<privapp-permissions package="com.android.tv">
<permission name="android.permission.CHANGE_HDMI_CEC_ACTIVE_SOURCE"/>
<permission name="android.permission.DVB_DEVICE"/>
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index 53ab2d5..08a746f 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -92,8 +92,11 @@
<dimen name="docked_divider_handle_width">16dp</dimen>
<dimen name="docked_divider_handle_height">2dp</dimen>
- <dimen name="split_divider_handle_region_width">96dp</dimen>
+ <dimen name="split_divider_handle_region_width">80dp</dimen>
<dimen name="split_divider_handle_region_height">48dp</dimen>
+ <!-- The divider touch zone height is intentionally halved in portrait to avoid colliding
+ with the app handle.-->
+ <dimen name="desktop_mode_portrait_split_divider_handle_region_height">24dp</dimen>
<!-- Divider handle size for split screen -->
<dimen name="split_divider_handle_width">40dp</dimen>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index dabfeeb..3dc33c2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -2217,7 +2217,6 @@
// And since all children are removed, remove the summary.
removeCallback.accept(-1);
- // TODO: (b/145659174) remove references to mSuppressedGroupKeys once fully migrated
mBubbleData.addSummaryToSuppress(summary.getStatusBarNotification().getGroupKey(),
summary.getKey());
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java
index dcbc72a..7c51a69 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java
@@ -23,6 +23,7 @@
import android.hardware.display.DisplayManager;
import android.os.RemoteException;
import android.util.ArraySet;
+import android.util.Size;
import android.util.Slog;
import android.util.SparseArray;
import android.view.Display;
@@ -193,8 +194,8 @@
/** Called when a display rotate requested. */
- public void onDisplayRotateRequested(WindowContainerTransaction wct, int displayId,
- int fromRotation, int toRotation) {
+ public void onDisplayChangeRequested(WindowContainerTransaction wct, int displayId,
+ Rect startAbsBounds, Rect endAbsBounds, int fromRotation, int toRotation) {
synchronized (mDisplays) {
final DisplayRecord dr = mDisplays.get(displayId);
if (dr == null) {
@@ -203,6 +204,13 @@
}
if (dr.mDisplayLayout != null) {
+ if (endAbsBounds != null) {
+ // If there is a change in the display dimensions update the layout as well;
+ // note that endAbsBounds should ignore any potential rotation changes, so
+ // we still need to rotate the layout after if needed.
+ dr.mDisplayLayout.resizeTo(dr.mContext.getResources(),
+ new Size(endAbsBounds.width(), endAbsBounds.height()));
+ }
dr.mDisplayLayout.rotateTo(dr.mContext.getResources(), toRotation);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayLayout.java
index 84e32a2..b6a1686 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayLayout.java
@@ -35,6 +35,7 @@
import android.os.SystemProperties;
import android.provider.Settings;
import android.util.DisplayMetrics;
+import android.util.Size;
import android.view.Display;
import android.view.DisplayCutout;
import android.view.DisplayInfo;
@@ -244,6 +245,16 @@
recalcInsets(res);
}
+ /**
+ * Update the dimensions of this layout.
+ */
+ public void resizeTo(Resources res, Size displaySize) {
+ mWidth = displaySize.getWidth();
+ mHeight = displaySize.getHeight();
+
+ recalcInsets(res);
+ }
+
/** Get this layout's non-decor insets. */
public Rect nonDecorInsets() {
return mNonDecorInsets;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
index 442036ff..e7848e2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
@@ -58,6 +58,7 @@
import com.android.wm.shell.R;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.shared.animation.Interpolators;
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
/**
* Divider for multi window splits.
@@ -228,7 +229,9 @@
: R.dimen.split_divider_handle_region_width);
mHandleRegionHeight = getResources().getDimensionPixelSize(isLeftRightSplit
? R.dimen.split_divider_handle_region_width
- : R.dimen.split_divider_handle_region_height);
+ : DesktopModeStatus.canEnterDesktopMode(mContext)
+ ? R.dimen.desktop_mode_portrait_split_divider_handle_region_height
+ : R.dimen.split_divider_handle_region_height);
}
void onInsetsChanged(InsetsState insetsState, boolean animate) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
index eb6caba..e9c4c14 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
@@ -29,7 +29,6 @@
import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.Bundle;
-import android.view.InsetsState;
import android.view.SurfaceControl;
import android.window.DisplayAreaInfo;
import android.window.WindowContainerTransaction;
@@ -200,17 +199,8 @@
DisplayLayout layout = new DisplayLayout(mContext, mContext.getDisplay());
mPipDisplayLayoutState.setDisplayLayout(layout);
- mDisplayController.addDisplayWindowListener(this);
mDisplayController.addDisplayChangingController(this);
mDisplayInsetsController.addInsetsChangedListener(mPipDisplayLayoutState.getDisplayId(),
- new DisplayInsetsController.OnInsetsChangedListener() {
- @Override
- public void insetsChanged(InsetsState insetsState) {
- setDisplayLayout(mDisplayController
- .getDisplayLayout(mPipDisplayLayoutState.getDisplayId()));
- }
- });
- mDisplayInsetsController.addInsetsChangedListener(mPipDisplayLayoutState.getDisplayId(),
new ImeListener(mDisplayController, mPipDisplayLayoutState.getDisplayId()) {
@Override
public void onImeVisibilityChanged(boolean imeVisible, int imeHeight) {
@@ -285,34 +275,37 @@
setDisplayLayout(mDisplayController.getDisplayLayout(displayId));
}
- @Override
- public void onDisplayConfigurationChanged(int displayId, Configuration newConfig) {
- if (displayId != mPipDisplayLayoutState.getDisplayId()) {
- return;
- }
- setDisplayLayout(mDisplayController.getDisplayLayout(displayId));
- }
-
/**
* A callback for any observed transition that contains a display change in its
- * {@link android.window.TransitionRequestInfo} with a non-zero rotation delta.
+ * {@link android.window.TransitionRequestInfo},
*/
@Override
public void onDisplayChange(int displayId, int fromRotation, int toRotation,
@Nullable DisplayAreaInfo newDisplayAreaInfo, WindowContainerTransaction t) {
+ if (displayId != mPipDisplayLayoutState.getDisplayId()) {
+ return;
+ }
+ final float snapFraction = mPipBoundsAlgorithm.getSnapFraction(mPipBoundsState.getBounds());
+ final float boundsScale = mPipBoundsState.getBoundsScale();
+
+ // Update the display layout caches even if we are not in PiP.
+ setDisplayLayout(mDisplayController.getDisplayLayout(displayId));
+
if (!mPipTransitionState.isInPip()) {
return;
}
- // Calculate the snap fraction pre-rotation.
- float snapFraction = mPipBoundsAlgorithm.getSnapFraction(mPipBoundsState.getBounds());
+ mPipTouchHandler.updateMinMaxSize(mPipBoundsState.getAspectRatio());
- // Update the caches to reflect the new display layout and movement bounds.
- mPipDisplayLayoutState.rotateTo(toRotation);
+ // Update the caches to reflect the new display layout in the movement bounds;
+ // temporarily update bounds to be at the top left for the movement bounds calculation.
+ Rect toBounds = new Rect(0, 0,
+ (int) Math.ceil(mPipBoundsState.getMaxSize().x * boundsScale),
+ (int) Math.ceil(mPipBoundsState.getMaxSize().y * boundsScale));
+ mPipBoundsState.setBounds(toBounds);
mPipTouchHandler.updateMovementBounds();
- // The policy is to keep PiP width, height and snap fraction invariant.
- Rect toBounds = mPipBoundsState.getBounds();
+ // The policy is to keep PiP snap fraction invariant.
mPipBoundsAlgorithm.applySnapFraction(toBounds, snapFraction);
mPipBoundsState.setBounds(toBounds);
t.setBounds(mPipTransitionState.mPipTaskToken, toBounds);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInit.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInit.java
index 287e779..1140c82 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInit.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInit.java
@@ -48,7 +48,7 @@
public ShellInit(ShellExecutor mainExecutor) {
mMainExecutor = mainExecutor;
- ProtoLog.registerGroups(ShellProtoLogGroup.values());
+ ProtoLog.init(ShellProtoLogGroup.values());
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java
index b7b42c7..209fc39 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java
@@ -114,7 +114,6 @@
t.clear();
mMainExecutor.execute(() -> {
finishCallback.onTransitionFinished(wct);
- mRemote = null;
});
}
};
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
index 68217c0..f0d3668 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
@@ -1187,12 +1187,15 @@
}
if (request.getDisplayChange() != null) {
TransitionRequestInfo.DisplayChange change = request.getDisplayChange();
- if (change.getEndRotation() != change.getStartRotation()) {
- // Is a rotation, so dispatch to all displayChange listeners
+ if (change.getStartRotation() != change.getEndRotation()
+ || (change.getStartAbsBounds() != null
+ && !change.getStartAbsBounds().equals(change.getEndAbsBounds()))) {
+ // Is a display change, so dispatch to all displayChange listeners
if (wct == null) {
wct = new WindowContainerTransaction();
}
- mDisplayController.onDisplayRotateRequested(wct, change.getDisplayId(),
+ mDisplayController.onDisplayChangeRequested(wct, change.getDisplayId(),
+ change.getStartAbsBounds(), change.getEndAbsBounds(),
change.getStartRotation(), change.getEndRotation());
}
}
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/DragAppWindowMultiWindowAndPip.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/DragAppWindowMultiWindowAndPip.kt
new file mode 100644
index 0000000..6d52a11
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/DragAppWindowMultiWindowAndPip.kt
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.scenarios
+
+import android.platform.test.annotations.Postsubmit
+import com.android.server.wm.flicker.helpers.DesktopModeAppHelper
+import com.android.server.wm.flicker.helpers.ImeAppHelper
+import com.android.server.wm.flicker.helpers.MailAppHelper
+import com.android.server.wm.flicker.helpers.NewTasksAppHelper
+import com.android.server.wm.flicker.helpers.PipAppHelper
+import com.android.server.wm.flicker.helpers.SimpleAppHelper
+import com.android.window.flags.Flags
+import org.junit.After
+import org.junit.Assume
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.BlockJUnit4ClassRunner
+
+@RunWith(BlockJUnit4ClassRunner::class)
+@Postsubmit
+open class DragAppWindowMultiWindowAndPip : DragAppWindowScenarioTestBase()
+{
+ private val imeAppHelper = ImeAppHelper(instrumentation)
+ private val testApp = DesktopModeAppHelper(SimpleAppHelper(instrumentation))
+ private val pipApp = PipAppHelper(instrumentation)
+ private val mailApp = DesktopModeAppHelper(MailAppHelper(instrumentation))
+ private val newTasksApp = DesktopModeAppHelper(NewTasksAppHelper(instrumentation))
+ private val imeApp = DesktopModeAppHelper(ImeAppHelper(instrumentation))
+
+ @Before
+ fun setup() {
+ Assume.assumeTrue(Flags.enableDesktopWindowingMode() && tapl.isTablet)
+ // Set string extra to ensure the app is on PiP mode at launch
+ pipApp.launchViaIntentAndWaitForPip(wmHelper, stringExtras = mapOf("enter_pip" to "true"))
+ testApp.enterDesktopWithDrag(wmHelper, device)
+ mailApp.launchViaIntent(wmHelper)
+ newTasksApp.launchViaIntent(wmHelper)
+ imeApp.launchViaIntent(wmHelper)
+ }
+
+ @Test
+ override fun dragAppWindow() {
+ val (startXIme, startYIme) = getWindowDragStartCoordinate(imeAppHelper)
+
+ imeApp.dragWindow(startXIme, startYIme,
+ endX = startXIme + 150, endY = startYIme + 150,
+ wmHelper, device)
+ }
+
+ @After
+ fun teardown() {
+ testApp.exit(wmHelper)
+ pipApp.exit(wmHelper)
+ mailApp.exit(wmHelper)
+ newTasksApp.exit(wmHelper)
+ imeApp.exit(wmHelper)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ResizeAppCornerMultiWindow.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ResizeAppCornerMultiWindow.kt
index 5bc975b..b6bca7a 100644
--- a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ResizeAppCornerMultiWindow.kt
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ResizeAppCornerMultiWindow.kt
@@ -83,5 +83,8 @@
@After
fun teardown() {
testApp.exit(wmHelper)
+ mailApp.exit(wmHelper)
+ newTasksApp.exit(wmHelper)
+ imeApp.exit(wmHelper)
}
}
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ResizeAppCornerMultiWindowAndPip.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ResizeAppCornerMultiWindowAndPip.kt
new file mode 100644
index 0000000..285ea13
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ResizeAppCornerMultiWindowAndPip.kt
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.scenarios
+
+import android.platform.test.annotations.Postsubmit
+import android.app.Instrumentation
+import android.tools.NavBar
+import android.tools.Rotation
+import android.tools.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.server.wm.flicker.helpers.DesktopModeAppHelper
+import com.android.server.wm.flicker.helpers.ImeAppHelper
+import com.android.server.wm.flicker.helpers.MailAppHelper
+import com.android.server.wm.flicker.helpers.NewTasksAppHelper
+import com.android.server.wm.flicker.helpers.PipAppHelper
+import com.android.server.wm.flicker.helpers.SimpleAppHelper
+import com.android.window.flags.Flags
+import com.android.wm.shell.Utils
+import org.junit.After
+import org.junit.Assume
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.BlockJUnit4ClassRunner
+
+@RunWith(BlockJUnit4ClassRunner::class)
+@Postsubmit
+open class ResizeAppCornerMultiWindowAndPip
+@JvmOverloads
+constructor(val rotation: Rotation = Rotation.ROTATION_0,
+ val horizontalChange: Int = 50,
+ val verticalChange: Int = -50) {
+
+ private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+ private val tapl = LauncherInstrumentation()
+ private val wmHelper = WindowManagerStateHelper(instrumentation)
+ private val device = UiDevice.getInstance(instrumentation)
+ private val testApp = DesktopModeAppHelper(SimpleAppHelper(instrumentation))
+ private val pipApp = PipAppHelper(instrumentation)
+ private val mailApp = DesktopModeAppHelper(MailAppHelper(instrumentation))
+ private val newTasksApp = DesktopModeAppHelper(NewTasksAppHelper(instrumentation))
+ private val imeApp = DesktopModeAppHelper(ImeAppHelper(instrumentation))
+
+ @Rule
+ @JvmField
+ val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation)
+
+ @Before
+ fun setup() {
+ Assume.assumeTrue(Flags.enableDesktopWindowingMode() && tapl.isTablet)
+ tapl.setEnableRotation(true)
+ tapl.setExpectedRotation(rotation.value)
+ // Set string extra to ensure the app is on PiP mode at launch
+ pipApp.launchViaIntentAndWaitForPip(wmHelper, stringExtras = mapOf("enter_pip" to "true"))
+ testApp.enterDesktopWithDrag(wmHelper, device)
+ mailApp.launchViaIntent(wmHelper)
+ newTasksApp.launchViaIntent(wmHelper)
+ imeApp.launchViaIntent(wmHelper)
+ }
+
+ @Test
+ open fun resizeAppWithCornerResize() {
+ imeApp.cornerResize(wmHelper,
+ device,
+ DesktopModeAppHelper.Corners.RIGHT_TOP,
+ horizontalChange,
+ verticalChange)
+ }
+
+ @After
+ fun teardown() {
+ testApp.exit(wmHelper)
+ pipApp.exit(wmHelper)
+ mailApp.exit(wmHelper)
+ newTasksApp.exit(wmHelper)
+ imeApp.exit(wmHelper)
+ }
+}
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index c36eda9..ca468fc 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -1027,7 +1027,7 @@
* <p>This method has no effect if the device implements a fixed volume policy
* as indicated by {@link #isVolumeFixed()}.
* <p>From N onward, ringer mode adjustments that would toggle Do Not Disturb are not allowed
- * unless the app has been granted Do Not Disturb Access.
+ * unless the app has been granted Notification Policy Access.
* See {@link NotificationManager#isNotificationPolicyAccessGranted()}.
*
* @param streamType The stream type to adjust. One of {@link #STREAM_VOICE_CALL},
@@ -1379,7 +1379,7 @@
* <p>This method has no effect if the device implements a fixed volume policy
* as indicated by {@link #isVolumeFixed()}.
* * <p>From N onward, ringer mode adjustments that would toggle Do Not Disturb are not allowed
- * unless the app has been granted Do Not Disturb Access.
+ * unless the app has been granted Notification Policy Access.
* See {@link NotificationManager#isNotificationPolicyAccessGranted()}.
* @param ringerMode The ringer mode, one of {@link #RINGER_MODE_NORMAL},
* {@link #RINGER_MODE_SILENT}, or {@link #RINGER_MODE_VIBRATE}.
@@ -1403,7 +1403,7 @@
* <p>This method has no effect if the device implements a fixed volume policy
* as indicated by {@link #isVolumeFixed()}.
* <p>From N onward, volume adjustments that would toggle Do Not Disturb are not allowed unless
- * the app has been granted Do Not Disturb Access.
+ * the app has been granted Notification Policy Access.
* See {@link NotificationManager#isNotificationPolicyAccessGranted()}.
* @param streamType The stream whose volume index should be set.
* @param index The volume index to set. See
@@ -8829,7 +8829,7 @@
* <p>This method has no effect if the device implements a fixed volume policy
* as indicated by {@link #isVolumeFixed()}.
* <p>From N onward, ringer mode adjustments that would toggle Do Not Disturb are not allowed
- * unless the app has been granted Do Not Disturb Access.
+ * unless the app has been granted Notification Policy Access.
* See {@link NotificationManager#isNotificationPolicyAccessGranted()}.
* <p>This API checks if the caller has the necessary permissions based on the provided
* component name, uid, and pid values.
@@ -8870,7 +8870,7 @@
* <p>This method has no effect if the device implements a fixed volume policy
* as indicated by {@link #isVolumeFixed()}.
* <p>From N onward, volume adjustments that would toggle Do Not Disturb are not allowed unless
- * the app has been granted Do Not Disturb Access.
+ * the app has been granted Notification Policy Access.
* See {@link NotificationManager#isNotificationPolicyAccessGranted()}.
* <p>This API checks if the caller has the necessary permissions based on the provided
* component name, uid, and pid values.
diff --git a/media/java/android/media/tv/ITvInputManager.aidl b/media/java/android/media/tv/ITvInputManager.aidl
index be93abb..87cb3e7 100644
--- a/media/java/android/media/tv/ITvInputManager.aidl
+++ b/media/java/android/media/tv/ITvInputManager.aidl
@@ -70,6 +70,7 @@
void releaseSession(in IBinder sessionToken, int userId);
int getClientPid(in String sessionId);
int getClientPriority(int useCase, in String sessionId);
+ int getClientUserId(in String sessionId);
void setMainSession(in IBinder sessionToken, int userId);
void setSurface(in IBinder sessionToken, in Surface surface, int userId);
diff --git a/media/java/android/media/tv/TvInputManager.java b/media/java/android/media/tv/TvInputManager.java
index aed3e60e..25b6bfa 100644
--- a/media/java/android/media/tv/TvInputManager.java
+++ b/media/java/android/media/tv/TvInputManager.java
@@ -760,6 +760,14 @@
* @hide
*/
public static final int UNKNOWN_CLIENT_PID = -1;
+ /**
+ * An unknown state of the client userId gets from the TvInputManager. Client gets this value
+ * when query through {@link #getClientUserId(String sessionId)} fails.
+ *
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_KIDS_MODE_TVDB_SHARING)
+ public static final int UNKNOWN_CLIENT_USER_ID = -1;
/**
* Broadcast intent action when the user blocked content ratings change. For use with the
@@ -2510,6 +2518,21 @@
};
/**
+ * Get a the client user id when creating the session with the session id provided.
+ *
+ * @param sessionId a String of session id that is used to query the client user id.
+ * @return the client pid when created the session. Returns {@link #UNKNOWN_CLIENT_USER_ID}
+ * if the call fails.
+ *
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.TUNER_RESOURCE_ACCESS)
+ @FlaggedApi(Flags.FLAG_KIDS_MODE_TVDB_SHARING)
+ public int getClientUserId(@NonNull String sessionId) {
+ return getClientUserIdInternal(sessionId);
+ }
+
+ /**
* Returns a priority for the given use case type and the client's foreground or background
* status.
*
@@ -2599,6 +2622,18 @@
return clientPid;
}
+ @FlaggedApi(Flags.FLAG_KIDS_MODE_TVDB_SHARING)
+ private int getClientUserIdInternal(String sessionId) {
+ Preconditions.checkNotNull(sessionId);
+ int clientUserId = UNKNOWN_CLIENT_USER_ID;
+ try {
+ clientUserId = mService.getClientUserId(sessionId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ return clientUserId;
+ }
+
private int getClientPriorityInternal(int useCase, String sessionId) {
try {
return mService.getClientPriority(useCase, sessionId);
diff --git a/media/java/android/media/tv/flags/media_tv.aconfig b/media/java/android/media/tv/flags/media_tv.aconfig
index 3196ba1..d6e9e4e 100644
--- a/media/java/android/media/tv/flags/media_tv.aconfig
+++ b/media/java/android/media/tv/flags/media_tv.aconfig
@@ -31,4 +31,12 @@
namespace: "media_tv"
description: "Introduce ALWAYS_BOUND_TV_INPUT for TIS."
bug: "332201346"
+}
+
+flag {
+ name: "kids_mode_tvdb_sharing"
+ is_exported: true
+ namespace: "media_tv"
+ description: "Performance and Storage Optimization in Google TV Kids Mode."
+ bug: "288383796"
}
\ No newline at end of file
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 047c097..476fd8b 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -541,6 +541,16 @@
}
flag {
+ name: "clipboard_image_timeout"
+ namespace: "systemui"
+ description: "Wait for clipboard image to load before showing UI"
+ bug: "359864629"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "screenshot_action_dismiss_system_windows"
namespace: "systemui"
description: "Dismiss existing system windows when starting action from screenshot UI"
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt
index 069113b..163b355 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt
@@ -511,7 +511,7 @@
private const val SELECTED_DOT_DIAMETER_DP = (DOT_DIAMETER_DP * 1.5).toInt()
private const val SELECTED_DOT_REACTION_ANIMATION_DURATION_MS = 83
private const val SELECTED_DOT_RETRACT_ANIMATION_DURATION_MS = 750
-private const val LINE_STROKE_WIDTH_DP = DOT_DIAMETER_DP
+private const val LINE_STROKE_WIDTH_DP = 22
private const val FAILURE_ANIMATION_DOT_DIAMETER_DP = (DOT_DIAMETER_DP * 0.81f).toInt()
private const val FAILURE_ANIMATION_DOT_SHRINK_ANIMATION_DURATION_MS = 50
private const val FAILURE_ANIMATION_DOT_SHRINK_STAGGER_DELAY_MS = 33
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/shared/model/KeyguardPreviewConstants.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/shared/model/KeyguardPreviewConstants.kt
index 08ee602..a3f40d4 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/shared/model/KeyguardPreviewConstants.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/shared/model/KeyguardPreviewConstants.kt
@@ -18,10 +18,17 @@
package com.android.systemui.shared.quickaffordance.shared.model
object KeyguardPreviewConstants {
+ const val MESSAGE_ID_DEFAULT_PREVIEW = 707
const val MESSAGE_ID_HIDE_SMART_SPACE = 1111
- const val KEY_HIDE_SMART_SPACE = "hide_smart_space"
+ const val MESSAGE_ID_PREVIEW_QUICK_AFFORDANCE_SELECTED = 1988
const val MESSAGE_ID_SLOT_SELECTED = 1337
- const val KEY_SLOT_ID = "slot_id"
- const val KEY_INITIALLY_SELECTED_SLOT_ID = "initially_selected_slot_id"
+ const val MESSAGE_ID_START_CUSTOMIZING_QUICK_AFFORDANCES = 214
+
+ const val KEY_HIDE_SMART_SPACE = "hide_smart_space"
const val KEY_HIGHLIGHT_QUICK_AFFORDANCES = "highlight_quick_affordances"
+ const val KEY_INITIALLY_SELECTED_SLOT_ID = "initially_selected_slot_id"
+ const val KEY_QUICK_AFFORDANCE_ID = "quick_affordance_id"
+ const val KEY_SLOT_ID = "slot_id"
+
+ const val KEYGUARD_QUICK_AFFORDANCE_ID_NONE = "none"
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModelTest.kt
index b83ab7e..c161525 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModelTest.kt
@@ -430,6 +430,22 @@
.isEqualTo("Can’t unlock with face. Too many attempts.")
}
+ @Test
+ fun startLockdownCountdown_onActivated() =
+ testScope.runTest {
+ val bouncerMessage by collectLastValue(underTest.message)
+ val lockoutSeconds = 200 * 1000 // 200 second lockout
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Pin)
+ kosmos.fakeAuthenticationRepository.reportLockoutStarted(lockoutSeconds)
+ runCurrent()
+
+ assertThat(bouncerMessage?.text).isEqualTo("Try again in 200 seconds.")
+ advanceTimeBy(100.seconds)
+ assertThat(bouncerMessage?.text).isEqualTo("Try again in 100 seconds.")
+ advanceTimeBy(101.seconds)
+ assertThat(bouncerMessage?.text).isEqualTo("Enter PIN")
+ }
+
private fun TestScope.verifyMessagesForAuthFlags(
vararg authFlagToMessagePair: Pair<Int, Pair<String, String?>>
) {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt
index 8e109b4..c85cd66 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt
@@ -308,6 +308,15 @@
)
}
+ @Test
+ fun getConfig() =
+ testScope.runTest {
+ assertThat(underTest.getConfig(FakeCustomizationProviderClient.AFFORDANCE_1))
+ .isEqualTo(config1)
+ assertThat(underTest.getConfig(FakeCustomizationProviderClient.AFFORDANCE_2))
+ .isEqualTo(config2)
+ }
+
private fun assertSelections(
observed: Map<String, List<KeyguardQuickAffordanceConfig>>?,
expected: Map<String, List<KeyguardQuickAffordanceConfig>>,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
index 75c0d3b..ad07c1c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
@@ -426,6 +426,64 @@
}
@Test
+ fun quickAffordanceAlwaysVisible_withNonNullOverrideKeyguardQuickAffordanceId() =
+ testScope.runTest {
+ quickAccessWallet.setState(
+ KeyguardQuickAffordanceConfig.LockScreenState.Visible(
+ icon = ICON,
+ activationState = ActivationState.Active,
+ )
+ )
+ homeControls.setState(
+ KeyguardQuickAffordanceConfig.LockScreenState.Visible(
+ icon = ICON,
+ activationState = ActivationState.Active,
+ )
+ )
+
+ // The default case
+ val collectedValue =
+ collectLastValue(
+ underTest.quickAffordanceAlwaysVisible(
+ KeyguardQuickAffordancePosition.BOTTOM_START,
+ )
+ )
+ assertThat(collectedValue())
+ .isInstanceOf(KeyguardQuickAffordanceModel.Visible::class.java)
+ val visibleModel = collectedValue() as KeyguardQuickAffordanceModel.Visible
+ assertThat(visibleModel.configKey)
+ .isEqualTo(
+ "${KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START}::${homeControls.key}"
+ )
+ assertThat(visibleModel.icon).isEqualTo(ICON)
+ assertThat(visibleModel.icon.contentDescription)
+ .isEqualTo(ContentDescription.Resource(res = CONTENT_DESCRIPTION_RESOURCE_ID))
+ assertThat(visibleModel.activationState).isEqualTo(ActivationState.Active)
+
+ // With override
+ val collectedValueWithOverride =
+ collectLastValue(
+ underTest.quickAffordanceAlwaysVisible(
+ position = KeyguardQuickAffordancePosition.BOTTOM_START,
+ overrideQuickAffordanceId =
+ BuiltInKeyguardQuickAffordanceKeys.QUICK_ACCESS_WALLET,
+ )
+ )
+ assertThat(collectedValueWithOverride())
+ .isInstanceOf(KeyguardQuickAffordanceModel.Visible::class.java)
+ val visibleModelWithOverride =
+ collectedValueWithOverride() as KeyguardQuickAffordanceModel.Visible
+ assertThat(visibleModelWithOverride.configKey)
+ .isEqualTo(
+ "${KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START}::${quickAccessWallet.key}"
+ )
+ assertThat(visibleModelWithOverride.icon).isEqualTo(ICON)
+ assertThat(visibleModelWithOverride.icon.contentDescription)
+ .isEqualTo(ContentDescription.Resource(res = CONTENT_DESCRIPTION_RESOURCE_ID))
+ assertThat(visibleModelWithOverride.activationState).isEqualTo(ActivationState.Active)
+ }
+
+ @Test
fun select() =
testScope.runTest {
overrideResource(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropTest.kt
index 4d1dd1c..d9faa30 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropTest.kt
@@ -32,6 +32,7 @@
import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.onNodeWithText
import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.FlakyTest
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.common.shared.model.ContentDescription
@@ -45,6 +46,7 @@
import org.junit.Test
import org.junit.runner.RunWith
+@FlakyTest(bugId = 360351805)
@SmallTest
@RunWith(AndroidJUnit4::class)
class DragAndDropTest : SysuiTestCase() {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/data/repository/CustomTileRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/data/repository/CustomTileRepositoryTest.kt
index dfc004a..c9869bdb 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/data/repository/CustomTileRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/data/repository/CustomTileRepositoryTest.kt
@@ -280,9 +280,12 @@
}
@Test
- fun isActiveFollowsPackageManagerAdapter() =
+ fun isActiveFollowsPackageManagerAdapter_user0() =
with(kosmos) {
testScope.runTest {
+ packageManagerAdapterFacade.setExclusiveForUser(0)
+
+ underTest.updateWithDefaults(UserHandle.of(0), TEST_DEFAULTS_1, true)
packageManagerAdapterFacade.setIsActive(false)
assertThat(underTest.isTileActive()).isFalse()
@@ -295,6 +298,7 @@
fun isToggleableFollowsPackageManagerAdapter() =
with(kosmos) {
testScope.runTest {
+ underTest.updateWithDefaults(UserHandle.of(0), TEST_DEFAULTS_1, true)
packageManagerAdapterFacade.setIsToggleable(false)
assertThat(underTest.isTileToggleable()).isFalse()
@@ -303,6 +307,66 @@
}
}
+ @Test
+ fun isActiveFollowsPackageManagerAdapter_user10_withAdapterForUser10() =
+ with(kosmos) {
+ testScope.runTest {
+ packageManagerAdapterFacade.setExclusiveForUser(10)
+
+ underTest.updateWithDefaults(UserHandle.of(10), TEST_DEFAULTS_1, true)
+ packageManagerAdapterFacade.setIsActive(false)
+ assertThat(underTest.isTileActive()).isFalse()
+
+ packageManagerAdapterFacade.setIsActive(true)
+ assertThat(underTest.isTileActive()).isTrue()
+ }
+ }
+
+ @Test
+ fun isToggleableFollowsPackageManagerAdapter_user10_withAdapterForUser10() =
+ with(kosmos) {
+ testScope.runTest {
+ packageManagerAdapterFacade.setExclusiveForUser(10)
+
+ underTest.updateWithDefaults(UserHandle.of(10), TEST_DEFAULTS_1, true)
+ packageManagerAdapterFacade.setIsToggleable(false)
+ assertThat(underTest.isTileToggleable()).isFalse()
+
+ packageManagerAdapterFacade.setIsToggleable(true)
+ assertThat(underTest.isTileToggleable()).isTrue()
+ }
+ }
+
+ @Test
+ fun isActiveDoesntFollowPackageManagerAdapter_user10() =
+ with(kosmos) {
+ testScope.runTest {
+ packageManagerAdapterFacade.setExclusiveForUser(0)
+
+ underTest.updateWithDefaults(UserHandle.of(10), TEST_DEFAULTS_1, true)
+ packageManagerAdapterFacade.setIsActive(false)
+ assertThat(underTest.isTileActive()).isFalse()
+
+ packageManagerAdapterFacade.setIsActive(true)
+ assertThat(underTest.isTileActive()).isFalse()
+ }
+ }
+
+ @Test
+ fun isToggleableDoesntFollowPackageManagerAdapter_user10() =
+ with(kosmos) {
+ testScope.runTest {
+ packageManagerAdapterFacade.setExclusiveForUser(0)
+
+ underTest.updateWithDefaults(UserHandle.of(10), TEST_DEFAULTS_1, true)
+ packageManagerAdapterFacade.setIsToggleable(false)
+ assertThat(underTest.isTileToggleable()).isFalse()
+
+ packageManagerAdapterFacade.setIsToggleable(true)
+ assertThat(underTest.isTileToggleable()).isFalse()
+ }
+ }
+
private companion object {
val TEST_COMPONENT = ComponentName("test.pkg", "test.cls")
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModel.kt
index d746220..cfd4f50 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModel.kt
@@ -264,6 +264,9 @@
// Keeps the lockout message up-to-date.
launch { bouncerInteractor.onLockoutStarted.collect { startLockoutCountdown() } }
+ // Start already active lockdown if it exists
+ launch { startLockoutCountdown() }
+
// Listens to relevant bouncer events
launch {
bouncerInteractor.onIncorrectBouncerInput
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
index 04c6fa9..040af90 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
@@ -19,6 +19,7 @@
import static android.content.Intent.ACTION_CLOSE_SYSTEM_DIALOGS;
import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.CLIPBOARD_OVERLAY_SHOW_ACTIONS;
+import static com.android.systemui.Flags.clipboardImageTimeout;
import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_ACTION_SHOWN;
import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_ACTION_TAPPED;
import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_DISMISSED_OTHER;
@@ -32,7 +33,6 @@
import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_SWIPE_DISMISSED;
import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_TAP_OUTSIDE;
import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_TIMED_OUT;
-import static com.android.systemui.flags.Flags.CLIPBOARD_IMAGE_TIMEOUT;
import static com.android.systemui.flags.Flags.CLIPBOARD_SHARED_TRANSITIONS;
import android.animation.Animator;
@@ -288,7 +288,7 @@
boolean shouldAnimate = !model.dataMatches(mClipboardModel) || wasExiting;
mClipboardModel = model;
mClipboardLogger.setClipSource(mClipboardModel.getSource());
- if (mFeatureFlags.isEnabled(CLIPBOARD_IMAGE_TIMEOUT)) {
+ if (clipboardImageTimeout()) {
if (shouldAnimate) {
reset();
mClipboardLogger.setClipSource(mClipboardModel.getSource());
@@ -452,7 +452,7 @@
mClipboardLogger.logUnguarded(CLIPBOARD_OVERLAY_EXPANDED_FROM_MINIMIZED);
mIsMinimized = false;
}
- if (mFeatureFlags.isEnabled(CLIPBOARD_IMAGE_TIMEOUT)) {
+ if (clipboardImageTimeout()) {
setExpandedView(() -> animateIn());
} else {
setExpandedView();
@@ -522,7 +522,7 @@
mInputMonitor.getInputChannel(), Looper.getMainLooper()) {
@Override
public void onInputEvent(InputEvent event) {
- if ((!mFeatureFlags.isEnabled(CLIPBOARD_IMAGE_TIMEOUT) || mShowingUi)
+ if ((!clipboardImageTimeout() || mShowingUi)
&& event instanceof MotionEvent) {
MotionEvent motionEvent = (MotionEvent) event;
if (motionEvent.getActionMasked() == MotionEvent.ACTION_DOWN) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/NewPickerUiKeyguardPreview.kt b/packages/SystemUI/src/com/android/systemui/keyguard/NewPickerUiKeyguardPreview.kt
new file mode 100644
index 0000000..7e09a10
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/NewPickerUiKeyguardPreview.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2024 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
+
+import com.android.systemui.Flags
+
+/** Helper for reading or using the new picker UI flag. */
+@Suppress("NOTHING_TO_INLINE")
+object NewPickerUiKeyguardPreview {
+
+ /** Is the new picker UI enabled */
+ @JvmStatic
+ inline val isEnabled
+ get() = Flags.newPickerUi()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt
index 698328e..d49550e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt
@@ -22,7 +22,6 @@
import android.os.UserHandle
import android.util.LayoutDirection
import com.android.systemui.Dumpable
-import com.android.systemui.res.R
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
import com.android.systemui.common.coroutine.ConflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
@@ -35,6 +34,7 @@
import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceSelectionManager
import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordancePickerRepresentation
import com.android.systemui.keyguard.shared.model.KeyguardSlotPickerRepresentation
+import com.android.systemui.res.R
import com.android.systemui.settings.UserTracker
import java.io.PrintWriter
import javax.inject.Inject
@@ -61,10 +61,13 @@
private val remoteUserSelectionManager: KeyguardQuickAffordanceRemoteUserSelectionManager,
private val userTracker: UserTracker,
legacySettingSyncer: KeyguardQuickAffordanceLegacySettingSyncer,
- private val configs: Set<@JvmSuppressWildcards KeyguardQuickAffordanceConfig>,
+ configs: Set<@JvmSuppressWildcards KeyguardQuickAffordanceConfig>,
dumpManager: DumpManager,
userHandle: UserHandle,
) {
+ // Configs for all keyguard quick affordances, mapped by the quick affordance ID as key
+ private val configsByAffordanceId: Map<String, KeyguardQuickAffordanceConfig> =
+ configs.associateBy { it.key }
private val userId: Flow<Int> =
ConflatedCallbackFlow.conflatedCallbackFlow {
val callback =
@@ -126,7 +129,7 @@
*/
fun getCurrentSelections(slotId: String): List<KeyguardQuickAffordanceConfig> {
val selections = selectionManager.value.getSelections().getOrDefault(slotId, emptyList())
- return configs.filter { selections.contains(it.key) }
+ return configsByAffordanceId.values.filter { selections.contains(it.key) }
}
/**
@@ -159,7 +162,7 @@
*/
suspend fun getAffordancePickerRepresentations():
List<KeyguardQuickAffordancePickerRepresentation> {
- return configs
+ return configsByAffordanceId.values
.associateWith { config -> config.getPickerScreenState() }
.filterNot { (_, pickerState) ->
pickerState is KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice
@@ -226,6 +229,11 @@
}
}
+ /** Get the config of a quick affordance. */
+ fun getConfig(quickAffordanceId: String): KeyguardQuickAffordanceConfig? {
+ return configsByAffordanceId[quickAffordanceId]
+ }
+
private inner class Dumpster : Dumpable {
override fun dump(pw: PrintWriter, args: Array<out String>) {
val slotPickerRepresentations = getSlotPickerRepresentations()
@@ -246,7 +254,7 @@
pw.println(" $slotId$selectionText (capacity = $capacity)")
}
pw.println("Available affordances on device:")
- configs.forEach { config ->
+ configsByAffordanceId.values.forEach { config ->
pw.println(" ${config.key} (\"${config.pickerName()}\")")
}
}
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 0682d87..2af95f2 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
@@ -53,6 +53,7 @@
import com.android.systemui.settings.UserTracker
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.shared.customization.data.content.CustomizationProviderContract as Contract
+import com.android.systemui.shared.quickaffordance.shared.model.KeyguardPreviewConstants.KEYGUARD_QUICK_AFFORDANCE_ID_NONE
import com.android.systemui.statusbar.phone.SystemUIDialog
import com.android.systemui.statusbar.policy.KeyguardStateController
import dagger.Lazy
@@ -142,14 +143,18 @@
*
* This is useful for experiences like the lock screen preview mode, where the affordances must
* always be visible.
+ *
+ * @param overrideQuickAffordanceId If null, return the currently-set quick affordance;
+ * otherwise, override and return the correspondent [KeyguardQuickAffordanceModel].
*/
suspend fun quickAffordanceAlwaysVisible(
position: KeyguardQuickAffordancePosition,
+ overrideQuickAffordanceId: String? = null,
): Flow<KeyguardQuickAffordanceModel> {
return if (isFeatureDisabledByDevicePolicy()) {
flowOf(KeyguardQuickAffordanceModel.Hidden)
} else {
- quickAffordanceInternal(position)
+ quickAffordanceInternal(position, overrideQuickAffordanceId)
}
}
@@ -299,12 +304,24 @@
}
private fun quickAffordanceInternal(
- position: KeyguardQuickAffordancePosition
+ position: KeyguardQuickAffordancePosition,
+ overrideAffordanceId: String? = null,
): Flow<KeyguardQuickAffordanceModel> =
repository
.get()
.selections
- .map { it[position.toSlotId()] ?: emptyList() }
+ .map { selections ->
+ val overrideQuickAffordanceConfigs =
+ overrideAffordanceId?.let {
+ if (it == KEYGUARD_QUICK_AFFORDANCE_ID_NONE) {
+ emptyList()
+ } else {
+ val config = repository.get().getConfig(it)
+ listOfNotNull(config)
+ }
+ }
+ overrideQuickAffordanceConfigs ?: selections[position.toSlotId()] ?: emptyList()
+ }
.flatMapLatest { configs -> combinedConfigs(position, configs) }
private fun combinedConfigs(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/quickaffordance/KeyguardQuickAffordancePosition.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/quickaffordance/KeyguardQuickAffordancePosition.kt
index 2581b59..1bbe843 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/quickaffordance/KeyguardQuickAffordancePosition.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/quickaffordance/KeyguardQuickAffordancePosition.kt
@@ -16,7 +16,8 @@
package com.android.systemui.keyguard.shared.quickaffordance
-import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
+import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END
+import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START
/** Enumerates all possible positions for quick affordances that can appear on the lock-screen. */
enum class KeyguardQuickAffordancePosition {
@@ -25,8 +26,19 @@
fun toSlotId(): String {
return when (this) {
- BOTTOM_START -> KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START
- BOTTOM_END -> KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END
+ BOTTOM_START -> SLOT_ID_BOTTOM_START
+ BOTTOM_END -> SLOT_ID_BOTTOM_END
}
}
+
+ companion object {
+
+ /** If the slot ID does not match any string, return null. */
+ fun parseKeyguardQuickAffordancePosition(slotId: String): KeyguardQuickAffordancePosition? =
+ when (slotId) {
+ SLOT_ID_BOTTOM_START -> BOTTOM_START
+ SLOT_ID_BOTTOM_END -> BOTTOM_END
+ else -> null
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
index 6031ef6..51ce355 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
@@ -275,6 +275,15 @@
}
}
+ fun onStartCustomizingQuickAffordances(
+ initiallySelectedSlotId: String?,
+ ) {
+ quickAffordancesCombinedViewModel.enablePreviewMode(
+ initiallySelectedSlotId = initiallySelectedSlotId,
+ shouldHighlightSelectedAffordance = true,
+ )
+ }
+
fun onSlotSelected(slotId: String) {
if (KeyguardBottomAreaRefactor.isEnabled) {
quickAffordancesCombinedViewModel.onPreviewSlotSelected(slotId = slotId)
@@ -283,6 +292,21 @@
}
}
+ fun onPreviewQuickAffordanceSelected(slotId: String, quickAffordanceId: String) {
+ quickAffordancesCombinedViewModel.onPreviewQuickAffordanceSelected(
+ slotId,
+ quickAffordanceId,
+ )
+ }
+
+ fun onDefaultPreview() {
+ quickAffordancesCombinedViewModel.onClearPreviewQuickAffordances()
+ quickAffordancesCombinedViewModel.enablePreviewMode(
+ initiallySelectedSlotId = null,
+ shouldHighlightSelectedAffordance = false,
+ )
+ }
+
fun destroy() {
isDestroyed = true
lockscreenSmartspaceController.disconnect()
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardRemotePreviewManager.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardRemotePreviewManager.kt
index 0532ee2..a6108c4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardRemotePreviewManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardRemotePreviewManager.kt
@@ -31,7 +31,16 @@
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.shared.quickaffordance.shared.model.KeyguardPreviewConstants
+import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START
+import com.android.systemui.shared.quickaffordance.shared.model.KeyguardPreviewConstants.KEY_HIDE_SMART_SPACE
+import com.android.systemui.shared.quickaffordance.shared.model.KeyguardPreviewConstants.KEY_INITIALLY_SELECTED_SLOT_ID
+import com.android.systemui.shared.quickaffordance.shared.model.KeyguardPreviewConstants.KEY_QUICK_AFFORDANCE_ID
+import com.android.systemui.shared.quickaffordance.shared.model.KeyguardPreviewConstants.KEY_SLOT_ID
+import com.android.systemui.shared.quickaffordance.shared.model.KeyguardPreviewConstants.MESSAGE_ID_DEFAULT_PREVIEW
+import com.android.systemui.shared.quickaffordance.shared.model.KeyguardPreviewConstants.MESSAGE_ID_HIDE_SMART_SPACE
+import com.android.systemui.shared.quickaffordance.shared.model.KeyguardPreviewConstants.MESSAGE_ID_PREVIEW_QUICK_AFFORDANCE_SELECTED
+import com.android.systemui.shared.quickaffordance.shared.model.KeyguardPreviewConstants.MESSAGE_ID_SLOT_SELECTED
+import com.android.systemui.shared.quickaffordance.shared.model.KeyguardPreviewConstants.MESSAGE_ID_START_CUSTOMIZING_QUICK_AFFORDANCES
import com.android.systemui.util.kotlin.logD
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
@@ -59,7 +68,7 @@
return try {
val renderer =
if (Flags.lockscreenPreviewRendererCreateOnMainThread()) {
- runBlocking ("$TAG#previewRendererFactory.create", mainDispatcher) {
+ runBlocking("$TAG#previewRendererFactory.create", mainDispatcher) {
previewRendererFactory.create(request)
}
} else {
@@ -157,16 +166,35 @@
}
when (message.what) {
- KeyguardPreviewConstants.MESSAGE_ID_SLOT_SELECTED -> {
- message.data.getString(KeyguardPreviewConstants.KEY_SLOT_ID)?.let { slotId ->
+ MESSAGE_ID_START_CUSTOMIZING_QUICK_AFFORDANCES -> {
+ checkNotNull(renderer)
+ .onStartCustomizingQuickAffordances(
+ initiallySelectedSlotId =
+ message.data.getString(KEY_INITIALLY_SELECTED_SLOT_ID)
+ ?: SLOT_ID_BOTTOM_START
+ )
+ }
+ MESSAGE_ID_SLOT_SELECTED -> {
+ message.data.getString(KEY_SLOT_ID)?.let { slotId ->
checkNotNull(renderer).onSlotSelected(slotId = slotId)
}
}
- KeyguardPreviewConstants.MESSAGE_ID_HIDE_SMART_SPACE -> {
- checkNotNull(renderer)
- .hideSmartspace(
- message.data.getBoolean(KeyguardPreviewConstants.KEY_HIDE_SMART_SPACE)
- )
+ MESSAGE_ID_PREVIEW_QUICK_AFFORDANCE_SELECTED -> {
+ val slotId = message.data.getString(KEY_SLOT_ID)
+ val quickAffordanceId = message.data.getString(KEY_QUICK_AFFORDANCE_ID)
+ if (slotId != null && quickAffordanceId != null) {
+ checkNotNull(renderer)
+ .onPreviewQuickAffordanceSelected(
+ slotId = slotId,
+ quickAffordanceId = quickAffordanceId,
+ )
+ }
+ }
+ MESSAGE_ID_DEFAULT_PREVIEW -> {
+ checkNotNull(renderer).onDefaultPreview()
+ }
+ MESSAGE_ID_HIDE_SMART_SPACE -> {
+ checkNotNull(renderer).hideSmartspace(message.data.getBoolean(KEY_HIDE_SMART_SPACE))
}
else -> checkNotNull(onDestroy).invoke(this)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModel.kt
index 2426f97..c885c9a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModel.kt
@@ -20,6 +20,7 @@
import androidx.annotation.VisibleForTesting
import com.android.app.tracing.FlowTracing.traceEmissionCount
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyguard.NewPickerUiKeyguardPreview
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
@@ -29,6 +30,7 @@
import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
+import com.android.systemui.utils.coroutines.flow.flatMapLatestConflated
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -164,13 +166,36 @@
.map { alpha -> alpha >= AFFORDANCE_FULLY_OPAQUE_ALPHA_THRESHOLD }
.distinctUntilChanged()
+ private val previewAffordances =
+ MutableStateFlow<Map<KeyguardQuickAffordancePosition, String>>(emptyMap())
+
/** An observable for the view-model of the "start button" quick affordance. */
val startButton: Flow<KeyguardQuickAffordanceViewModel> =
- button(KeyguardQuickAffordancePosition.BOTTOM_START)
+ if (NewPickerUiKeyguardPreview.isEnabled) {
+ previewAffordances.flatMapLatestConflated {
+ button(
+ position = KeyguardQuickAffordancePosition.BOTTOM_START,
+ overrideQuickAffordanceId = it[KeyguardQuickAffordancePosition.BOTTOM_START],
+ )
+ }
+ } else {
+ button(
+ KeyguardQuickAffordancePosition.BOTTOM_START,
+ )
+ }
/** An observable for the view-model of the "end button" quick affordance. */
val endButton: Flow<KeyguardQuickAffordanceViewModel> =
- button(KeyguardQuickAffordancePosition.BOTTOM_END)
+ if (NewPickerUiKeyguardPreview.isEnabled) {
+ previewAffordances.flatMapLatestConflated {
+ button(
+ position = KeyguardQuickAffordancePosition.BOTTOM_END,
+ overrideQuickAffordanceId = it[KeyguardQuickAffordancePosition.BOTTOM_END],
+ )
+ }
+ } else {
+ button(KeyguardQuickAffordancePosition.BOTTOM_END)
+ }
/**
* Notifies that a slot with the given ID has been selected in the preview experience that is
@@ -183,6 +208,28 @@
}
/**
+ * Notifies to preview an affordance at a given slot ID. This is ignored for the real lock
+ * screen experience.
+ */
+ fun onPreviewQuickAffordanceSelected(slotId: String, affordanceId: String) {
+ val position =
+ KeyguardQuickAffordancePosition.parseKeyguardQuickAffordancePosition(slotId) ?: return
+ previewAffordances.value =
+ previewAffordances.value.toMutableMap().let {
+ it[position] = affordanceId
+ HashMap(it)
+ }
+ }
+
+ /**
+ * Notifies to clear up the preview affordances map. This is ignored for the real lock screen
+ * experience.
+ */
+ fun onClearPreviewQuickAffordances() {
+ previewAffordances.value = emptyMap()
+ }
+
+ /**
* Puts this view-model in "preview mode", which means it's being used for UI that is rendering
* the lock screen preview in wallpaper picker / settings and not the real experience on the
* lock screen.
@@ -207,14 +254,16 @@
}
private fun button(
- position: KeyguardQuickAffordancePosition
+ position: KeyguardQuickAffordancePosition,
+ overrideQuickAffordanceId: String? = null,
): Flow<KeyguardQuickAffordanceViewModel> {
return previewMode
.flatMapLatest { previewMode ->
combine(
if (previewMode.isInPreviewMode) {
quickAffordanceInteractor.quickAffordanceAlwaysVisible(
- position = position
+ position = position,
+ overrideQuickAffordanceId = overrideQuickAffordanceId,
)
} else {
quickAffordanceInteractor.quickAffordance(position = position)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/PackageManagerAdapter.java b/packages/SystemUI/src/com/android/systemui/qs/external/PackageManagerAdapter.java
index 6cf4441..28e4fd0 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/PackageManagerAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/PackageManagerAdapter.java
@@ -26,6 +26,8 @@
import android.content.pm.ServiceInfo;
import android.os.RemoteException;
+import androidx.annotation.Nullable;
+
import javax.inject.Inject;
// Adapter that wraps calls to PackageManager or IPackageManager for {@link TileLifecycleManager}.
@@ -45,6 +47,7 @@
mIPackageManager = AppGlobals.getPackageManager();
}
+ @Nullable
public ServiceInfo getServiceInfo(ComponentName className, int flags, int userId)
throws RemoteException {
return mIPackageManager.getServiceInfo(className, flags, userId);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java
index 96df728..cbcf68c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java
@@ -188,10 +188,10 @@
public boolean isActiveTile() {
try {
ServiceInfo info = mPackageManagerAdapter.getServiceInfo(mIntent.getComponent(),
- META_DATA_QUERY_FLAGS);
- return info.metaData != null
+ META_DATA_QUERY_FLAGS, mUser.getIdentifier());
+ return info != null && info.metaData != null
&& info.metaData.getBoolean(TileService.META_DATA_ACTIVE_TILE, false);
- } catch (PackageManager.NameNotFoundException e) {
+ } catch (RemoteException e) {
return false;
}
}
@@ -206,10 +206,10 @@
public boolean isToggleableTile() {
try {
ServiceInfo info = mPackageManagerAdapter.getServiceInfo(mIntent.getComponent(),
- META_DATA_QUERY_FLAGS);
- return info.metaData != null
+ META_DATA_QUERY_FLAGS, mUser.getIdentifier());
+ return info != null && info.metaData != null
&& info.metaData.getBoolean(TileService.META_DATA_TOGGLEABLE_TILE, false);
- } catch (PackageManager.NameNotFoundException e) {
+ } catch (RemoteException e) {
return false;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/data/repository/CustomTileRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/data/repository/CustomTileRepository.kt
index c932cee..0aaea8f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/data/repository/CustomTileRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/data/repository/CustomTileRepository.kt
@@ -17,8 +17,8 @@
package com.android.systemui.qs.tiles.impl.custom.data.repository
import android.content.pm.PackageManager
-import android.content.pm.ServiceInfo
import android.graphics.drawable.Icon
+import android.os.RemoteException
import android.os.UserHandle
import android.service.quicksettings.Tile
import android.service.quicksettings.TileService
@@ -163,13 +163,14 @@
override suspend fun isTileActive(): Boolean =
withContext(backgroundContext) {
try {
- val info: ServiceInfo =
+ val info =
packageManagerAdapter.getServiceInfo(
tileSpec.componentName,
- META_DATA_QUERY_FLAGS
+ META_DATA_QUERY_FLAGS,
+ getCurrentTileWithUser()?.user?.identifier ?: UserHandle.USER_CURRENT,
)
- info.metaData?.getBoolean(TileService.META_DATA_ACTIVE_TILE, false) == true
- } catch (e: PackageManager.NameNotFoundException) {
+ info?.metaData?.getBoolean(TileService.META_DATA_ACTIVE_TILE, false) == true
+ } catch (e: RemoteException) {
false
}
}
@@ -177,13 +178,14 @@
override suspend fun isTileToggleable(): Boolean =
withContext(backgroundContext) {
try {
- val info: ServiceInfo =
+ val info =
packageManagerAdapter.getServiceInfo(
tileSpec.componentName,
- META_DATA_QUERY_FLAGS
+ META_DATA_QUERY_FLAGS,
+ getCurrentTileWithUser()?.user?.identifier ?: UserHandle.USER_CURRENT
)
- info.metaData?.getBoolean(TileService.META_DATA_TOGGLEABLE_TILE, false) == true
- } catch (e: PackageManager.NameNotFoundException) {
+ info?.metaData?.getBoolean(TileService.META_DATA_TOGGLEABLE_TILE, false) == true
+ } catch (e: RemoteException) {
false
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/OWNERS b/packages/SystemUI/src/com/android/systemui/statusbar/OWNERS
index c4f539a..408fc6d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/OWNERS
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/OWNERS
@@ -13,4 +13,12 @@
per-file *Keyboard* = set noparent
per-file *Keyboard* = file:../keyguard/OWNERS
per-file *Keyguard* = set noparent
-per-file *Keyguard* = file:../keyguard/OWNERS
\ No newline at end of file
+per-file *Keyguard* = file:../keyguard/OWNERS
+per-file *Notification* = set noparent
+per-file *Notification* = file:notification/OWNERS
+per-file *Mode* = set noparent
+per-file *Mode* = file:notification/OWNERS
+per-file *RemoteInput* = set noparent
+per-file *RemoteInput* = file:notification/OWNERS
+per-file *EmptyShadeView* = set noparent
+per-file *EmptyShadeView* = file:notification/OWNERS
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/CommonNotifCollection.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/CommonNotifCollection.java
index fc7d682..0d209d8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/CommonNotifCollection.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/CommonNotifCollection.java
@@ -27,9 +27,6 @@
/**
* A notification collection that manages the list of {@link NotificationEntry}s that will be
* rendered.
- *
- * TODO: (b/145659174) Once we fully migrate to {@link NotifPipeline}, we probably won't need this,
- * but having it for now makes it easy to switch between the two.
*/
public interface CommonNotifCollection {
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 20b1fff..e802076 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -258,7 +258,7 @@
private float mOverScrolledBottomPixels;
private final ListenerSet<Runnable> mStackHeightChangedListeners = new ListenerSet<>();
private final ListenerSet<Runnable> mHeadsUpHeightChangedListeners = new ListenerSet<>();
- private NotificationLogger.OnChildLocationsChangedListener mListener;
+ private NotificationLogger.OnChildLocationsChangedListener mLegacyLocationsChangedListener;
private OnNotificationLocationsChangedListener mLocationsChangedListener;
private OnOverscrollTopChangedListener mOverscrollTopChangedListener;
private ExpandableView.OnHeightChangedListener mOnHeightChangedListener;
@@ -1281,7 +1281,7 @@
public void setChildLocationsChangedListener(
NotificationLogger.OnChildLocationsChangedListener listener) {
NotificationsLiveDataStoreRefactor.assertInLegacyMode();
- mListener = listener;
+ mLegacyLocationsChangedListener = listener;
}
private void setMaxLayoutHeight(int maxLayoutHeight) {
@@ -4433,8 +4433,8 @@
mLocationsChangedListener.onChildLocationsChanged(collectVisibleLocationsCallable);
}
} else {
- if (mListener != null) {
- mListener.onChildLocationsChanged();
+ if (mLegacyLocationsChangedListener != null) {
+ mLegacyLocationsChangedListener.onChildLocationsChanged();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegate.kt
index 4f7749b..e1dcc52 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegate.kt
@@ -21,7 +21,11 @@
import android.util.Log
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.semantics.semantics
+import androidx.compose.ui.semantics.testTagsAsResourceId
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner
import com.android.compose.PlatformButton
@@ -88,7 +92,15 @@
@Composable
private fun ModesDialogContent(dialog: SystemUIDialog) {
AlertDialogContent(
- title = { Text(stringResource(R.string.zen_modes_dialog_title)) },
+ modifier = Modifier.semantics {
+ testTagsAsResourceId = true
+ },
+ title = {
+ Text(
+ modifier = Modifier.testTag("modes_title"),
+ text = stringResource(R.string.zen_modes_dialog_title)
+ )
+ },
content = { ModeTileGrid(viewModel.get()) },
neutralButton = {
PlatformOutlinedButton(onClick = { openSettings(dialog) }) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/composable/ModeTile.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/composable/ModeTile.kt
index 3b392c8..3fffd9f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/composable/ModeTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/composable/ModeTile.kt
@@ -32,6 +32,7 @@
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.testTag
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import com.android.systemui.common.ui.compose.Icon
@@ -70,12 +71,12 @@
Text(
viewModel.text,
fontWeight = FontWeight.W500,
- modifier = Modifier.tileMarquee()
+ modifier = Modifier.tileMarquee().testTag("name")
)
Text(
viewModel.subtext,
fontWeight = FontWeight.W400,
- modifier = Modifier.tileMarquee()
+ modifier = Modifier.tileMarquee().testTag("state")
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
index 45799b2..7385b82 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
@@ -115,7 +115,6 @@
private final Executor mSysuiUiBgExecutor;
private final Bubbles.SysuiProxy mSysuiProxy;
- // TODO (b/145659174): allow for multiple callbacks to support the "shadow" new notif pipeline
private final List<NotifCallback> mCallbacks = new ArrayList<>();
private final StatusBarWindowCallback mStatusBarWindowCallback;
private final Runnable mSensitiveStateChangedListener;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt
index 24bea2c..73b9f57 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt
@@ -20,6 +20,7 @@
import android.app.admin.DevicePolicyManager
import android.content.Intent
import android.os.UserHandle
+import android.platform.test.annotations.EnableFlags
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.internal.widget.LockPatternUtils
@@ -402,6 +403,67 @@
}
@Test
+ @EnableFlags(com.android.systemui.Flags.FLAG_NEW_PICKER_UI)
+ fun startButton_inPreviewMode_onPreviewQuickAffordanceSelected() =
+ testScope.runTest {
+ underTest.onPreviewSlotSelected(KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START)
+ underTest.enablePreviewMode(KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START, true)
+
+ repository.setKeyguardShowing(false)
+ val latest = collectLastValue(underTest.startButton)
+
+ val icon: Icon = mock()
+ val testConfig =
+ TestConfig(
+ isVisible = true,
+ isClickable = true,
+ isActivated = true,
+ icon = icon,
+ canShowWhileLocked = false,
+ intent = null,
+ slotId = KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId(),
+ )
+ val defaultConfigKey =
+ setUpQuickAffordanceModel(
+ position = KeyguardQuickAffordancePosition.BOTTOM_START,
+ testConfig = testConfig,
+ )
+
+ // Set up the quick access wallet config
+ val quickAccessWalletAffordanceConfigKey =
+ quickAccessWalletAffordanceConfig
+ .apply {
+ onTriggeredResult =
+ KeyguardQuickAffordanceConfig.OnTriggeredResult.StartActivity(
+ intent = Intent("action"),
+ canShowWhileLocked = false,
+ )
+ setState(
+ KeyguardQuickAffordanceConfig.LockScreenState.Visible(
+ icon = icon,
+ activationState = ActivationState.Active,
+ )
+ )
+ }
+ .let {
+ KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId() +
+ "::${quickAccessWalletAffordanceConfig.key}"
+ }
+
+ // onPreviewQuickAffordanceSelected should trigger the override with the quick access
+ // wallet quick affordance
+ underTest.onPreviewQuickAffordanceSelected(
+ KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START,
+ BuiltInKeyguardQuickAffordanceKeys.QUICK_ACCESS_WALLET,
+ )
+ Truth.assertThat(latest()?.configKey).isEqualTo(quickAccessWalletAffordanceConfigKey)
+
+ // onClearPreviewQuickAffordances should make the default quick affordance shows again
+ underTest.onClearPreviewQuickAffordances()
+ Truth.assertThat(latest()?.configKey).isEqualTo(defaultConfigKey)
+ }
+
+ @Test
fun startButton_inPreviewMode_visibleEvenWhenKeyguardNotShowing() =
testScope.runTest {
underTest.onPreviewSlotSelected(KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START)
@@ -445,7 +507,7 @@
}
@Test
- fun endButton_inHiglightedPreviewMode_dimmedWhenOtherIsSelected() =
+ fun endButton_inHighlightedPreviewMode_dimmedWhenOtherIsSelected() =
testScope.runTest {
underTest.onPreviewSlotSelected(KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START)
underTest.enablePreviewMode(KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START, true)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java
index 68307b1..c1cf91d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java
@@ -23,6 +23,8 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
import static com.android.systemui.Flags.FLAG_QS_CUSTOM_TILE_CLICK_GUARANTEED_BUG_FIX;
+import static com.google.common.truth.Truth.assertThat;
+
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
@@ -50,6 +52,7 @@
import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
import android.content.pm.ServiceInfo;
import android.net.Uri;
import android.os.Bundle;
@@ -183,6 +186,40 @@
.thenReturn(defaultPackageInfo);
}
+ private void setPackageInstalledForUser(
+ boolean installed,
+ boolean active,
+ boolean toggleable,
+ int user
+ ) throws Exception {
+ ServiceInfo defaultServiceInfo = null;
+ if (installed) {
+ defaultServiceInfo = new ServiceInfo();
+ defaultServiceInfo.metaData = new Bundle();
+ defaultServiceInfo.metaData.putBoolean(TileService.META_DATA_ACTIVE_TILE, active);
+ defaultServiceInfo.metaData
+ .putBoolean(TileService.META_DATA_TOGGLEABLE_TILE, toggleable);
+ when(mMockPackageManagerAdapter.getServiceInfo(any(), anyInt(), eq(user)))
+ .thenReturn(defaultServiceInfo);
+ if (user == 0) {
+ when(mMockPackageManagerAdapter.getServiceInfo(any(), anyInt()))
+ .thenReturn(defaultServiceInfo);
+ }
+ PackageInfo defaultPackageInfo = new PackageInfo();
+ when(mMockPackageManagerAdapter.getPackageInfoAsUser(anyString(), anyInt(), eq(user)))
+ .thenReturn(defaultPackageInfo);
+ } else {
+ when(mMockPackageManagerAdapter.getServiceInfo(any(), anyInt(), eq(user)))
+ .thenReturn(null);
+ if (user == 0) {
+ when(mMockPackageManagerAdapter.getServiceInfo(any(), anyInt()))
+ .thenThrow(new PackageManager.NameNotFoundException());
+ }
+ when(mMockPackageManagerAdapter.getPackageInfoAsUser(anyString(), anyInt(), eq(user)))
+ .thenThrow(new PackageManager.NameNotFoundException());
+ }
+ }
+
private void verifyBind(int times) {
assertEquals(times > 0, mContext.isBound(mTileServiceComponentName));
}
@@ -557,6 +594,100 @@
verify(mockContext).unbindService(captor.getValue());
}
+ @Test
+ public void testIsActive_user0_packageInstalled() throws Exception {
+ setPackageInstalledForUser(true, true, false, 0);
+ mUser = UserHandle.of(0);
+
+ TileLifecycleManager manager = new TileLifecycleManager(mHandler, mWrappedContext,
+ mock(IQSService.class),
+ mMockPackageManagerAdapter,
+ mMockBroadcastDispatcher,
+ mTileServiceIntent,
+ mUser,
+ mActivityManager,
+ mDeviceIdleController,
+ mExecutor);
+
+ assertThat(manager.isActiveTile()).isTrue();
+ }
+
+ @Test
+ public void testIsActive_user10_packageInstalled_notForUser0() throws Exception {
+ setPackageInstalledForUser(true, true, false, 10);
+ setPackageInstalledForUser(false, false, false, 0);
+ mUser = UserHandle.of(10);
+
+ TileLifecycleManager manager = new TileLifecycleManager(mHandler, mWrappedContext,
+ mock(IQSService.class),
+ mMockPackageManagerAdapter,
+ mMockBroadcastDispatcher,
+ mTileServiceIntent,
+ mUser,
+ mActivityManager,
+ mDeviceIdleController,
+ mExecutor);
+
+ assertThat(manager.isActiveTile()).isTrue();
+ }
+
+ @Test
+ public void testIsToggleable_user0_packageInstalled() throws Exception {
+ setPackageInstalledForUser(true, false, true, 0);
+ mUser = UserHandle.of(0);
+
+ TileLifecycleManager manager = new TileLifecycleManager(mHandler, mWrappedContext,
+ mock(IQSService.class),
+ mMockPackageManagerAdapter,
+ mMockBroadcastDispatcher,
+ mTileServiceIntent,
+ mUser,
+ mActivityManager,
+ mDeviceIdleController,
+ mExecutor);
+
+ assertThat(manager.isToggleableTile()).isTrue();
+ }
+
+ @Test
+ public void testIsToggleable_user10_packageInstalled_notForUser0() throws Exception {
+ setPackageInstalledForUser(true, false, true, 10);
+ setPackageInstalledForUser(false, false, false, 0);
+ mUser = UserHandle.of(10);
+
+ TileLifecycleManager manager = new TileLifecycleManager(mHandler, mWrappedContext,
+ mock(IQSService.class),
+ mMockPackageManagerAdapter,
+ mMockBroadcastDispatcher,
+ mTileServiceIntent,
+ mUser,
+ mActivityManager,
+ mDeviceIdleController,
+ mExecutor);
+
+ assertThat(manager.isToggleableTile()).isTrue();
+ }
+
+ @Test
+ public void testIsToggleableActive_installedForDifferentUser() throws Exception {
+ setPackageInstalledForUser(true, false, false, 10);
+ setPackageInstalledForUser(false, true, true, 0);
+ mUser = UserHandle.of(10);
+
+ TileLifecycleManager manager = new TileLifecycleManager(mHandler, mWrappedContext,
+ mock(IQSService.class),
+ mMockPackageManagerAdapter,
+ mMockBroadcastDispatcher,
+ mTileServiceIntent,
+ mUser,
+ mActivityManager,
+ mDeviceIdleController,
+ mExecutor);
+
+ assertThat(manager.isToggleableTile()).isFalse();
+ assertThat(manager.isActiveTile()).isFalse();
+ }
+
private void mockChangeEnabled(long changeId, boolean enabled) {
doReturn(enabled).when(() -> CompatChanges.isChangeEnabled(eq(changeId), anyString(),
any(UserHandle.class)));
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/data/repository/FakePackageManagerAdapterFacade.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/data/repository/FakePackageManagerAdapterFacade.kt
index fa8d363..5ac7c39 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/data/repository/FakePackageManagerAdapterFacade.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/data/repository/FakePackageManagerAdapterFacade.kt
@@ -18,14 +18,19 @@
import android.content.ComponentName
import android.content.pm.PackageInfo
+import android.content.pm.PackageManager
import android.content.pm.ServiceInfo
import android.os.Bundle
+import android.os.UserHandle
import com.android.systemui.qs.external.PackageManagerAdapter
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
+import org.hamcrest.BaseMatcher
+import org.hamcrest.Description
import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.hamcrest.MockitoHamcrest.intThat
/**
* Facade for [PackageManagerAdapter] to provide a fake-like behaviour. You can create this class
@@ -36,29 +41,26 @@
* [com.android.systemui.qs.external.TileServiceManager.isToggleableTile] or
* [com.android.systemui.qs.external.TileServiceManager.isActiveTile] when the real objects are
* used.
+ *
+ * The user this is set up must be a real user (`user >= 0`) or [UserHandle.USER_ALL].
*/
class FakePackageManagerAdapterFacade(
val componentName: ComponentName,
val packageManagerAdapter: PackageManagerAdapter = mock {},
+ user: Int = UserHandle.USER_ALL,
) {
private var isToggleable: Boolean = false
private var isActive: Boolean = false
init {
- whenever(packageManagerAdapter.getServiceInfo(eq(componentName), any())).thenAnswer {
- createServiceInfo()
+ if (user == UserHandle.USER_ALL) {
+ setForAllUsers()
+ } else if (user >= 0) {
+ setExclusiveForUser(user)
+ } else {
+ throw IllegalArgumentException("User must be a real user or UserHandle.USER_ALL")
}
- whenever(
- packageManagerAdapter.getPackageInfoAsUser(
- eq(componentName.packageName),
- anyInt(),
- anyInt()
- )
- )
- .thenAnswer { PackageInfo().apply { packageName = componentName.packageName } }
- whenever(packageManagerAdapter.getServiceInfo(eq(componentName), anyInt(), anyInt()))
- .thenAnswer { createServiceInfo() }
}
private fun createServiceInfo(): ServiceInfo {
@@ -84,4 +86,67 @@
fun setIsToggleable(isToggleable: Boolean) {
this.isToggleable = isToggleable
}
+
+ fun setExclusiveForUser(newUser: Int) {
+ check(newUser >= 0)
+ val notEqualMatcher = NotEqualMatcher(newUser)
+ if (newUser == 0) {
+ whenever(packageManagerAdapter.getServiceInfo(eq(componentName), any())).thenAnswer {
+ createServiceInfo()
+ }
+ }
+ whenever(
+ packageManagerAdapter.getPackageInfoAsUser(
+ eq(componentName.packageName),
+ anyInt(),
+ eq(newUser)
+ )
+ )
+ .thenAnswer { PackageInfo().apply { packageName = componentName.packageName } }
+ whenever(
+ packageManagerAdapter.getPackageInfoAsUser(
+ eq(componentName.packageName),
+ anyInt(),
+ intThat(notEqualMatcher),
+ )
+ )
+ .thenThrow(PackageManager.NameNotFoundException())
+
+ whenever(
+ packageManagerAdapter.getServiceInfo(
+ eq(componentName),
+ anyInt(),
+ intThat(notEqualMatcher)
+ )
+ )
+ .thenAnswer { null }
+ whenever(packageManagerAdapter.getServiceInfo(eq(componentName), anyInt(), eq(newUser)))
+ .thenAnswer { createServiceInfo() }
+ }
+
+ fun setForAllUsers() {
+ whenever(packageManagerAdapter.getServiceInfo(eq(componentName), any())).thenAnswer {
+ createServiceInfo()
+ }
+ whenever(
+ packageManagerAdapter.getPackageInfoAsUser(
+ eq(componentName.packageName),
+ anyInt(),
+ anyInt()
+ )
+ )
+ .thenAnswer { PackageInfo().apply { packageName = componentName.packageName } }
+ whenever(packageManagerAdapter.getServiceInfo(eq(componentName), anyInt(), anyInt()))
+ .thenAnswer { createServiceInfo() }
+ }
+}
+
+private class NotEqualMatcher(private val notEqualValue: Int) : BaseMatcher<Int>() {
+ override fun describeTo(description: Description?) {
+ description?.appendText("!= $notEqualValue")
+ }
+
+ override fun matches(item: Any?): Boolean {
+ return (item as? Int)?.equals(notEqualValue)?.not() ?: true
+ }
}
diff --git a/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java b/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java
index 531fa45..09068d5 100644
--- a/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java
+++ b/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java
@@ -171,7 +171,7 @@
EXTENSIONS_VERSION.startsWith(LATENCY_VERSION_PREFIX) ||
EXTENSIONS_VERSION.startsWith(EFV_VERSION_PREFIX));
- private HashMap<String, Long> mMetadataVendorIdMap = new HashMap<>();
+ private static HashMap<String, Long> mMetadataVendorIdMap = new HashMap<>();
private CameraManager mCameraManager;
private static boolean checkForLatencyAPI() {
@@ -820,7 +820,7 @@
mCameraManager = getSystemService(CameraManager.class);
String [] cameraIds = mCameraManager.getCameraIdListNoLazy();
- if (cameraIds != null) {
+ if (cameraIds != null && mMetadataVendorIdMap.isEmpty()) {
for (String cameraId : cameraIds) {
CameraCharacteristics chars = mCameraManager.getCameraCharacteristics(cameraId);
Object thisClass = CameraCharacteristics.Key.class;
diff --git a/ravenwood/Android.bp b/ravenwood/Android.bp
index 6150343..58cd2e4 100644
--- a/ravenwood/Android.bp
+++ b/ravenwood/Android.bp
@@ -166,6 +166,14 @@
jarjar_rules: ":ravenwood-services-jarjar-rules",
}
+java_device_for_host {
+ name: "ravenwood-junit-impl-for-ravenizer",
+ libs: [
+ "ravenwood-junit-impl",
+ ],
+ visibility: [":__subpackages__"],
+}
+
// Separated out from ravenwood-junit-impl since it needs to compile
// against `module_current`
java_library {
diff --git a/ravenwood/tools/ravenizer-fake/Android.bp b/ravenwood/tools/ravenizer-fake/Android.bp
deleted file mode 100644
index 7e2c407..0000000
--- a/ravenwood/tools/ravenizer-fake/Android.bp
+++ /dev/null
@@ -1,14 +0,0 @@
-package {
- // See: http://go/android-license-faq
- // A large-scale-change added 'default_applicable_licenses' to import
- // all of the 'license_kinds' from "frameworks_base_license"
- // to get the below license kinds:
- // SPDX-license-identifier-Apache-2.0
- default_applicable_licenses: ["frameworks_base_license"],
-}
-
-sh_binary_host {
- name: "ravenizer",
- src: "ravenizer",
- visibility: ["//visibility:public"],
-}
diff --git a/ravenwood/tools/ravenizer-fake/ravenizer b/ravenwood/tools/ravenizer-fake/ravenizer
deleted file mode 100755
index 84b3c8e..0000000
--- a/ravenwood/tools/ravenizer-fake/ravenizer
+++ /dev/null
@@ -1,31 +0,0 @@
-#!/bin/bash
-# Copyright (C) 2024 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.
-
-# "Fake" ravenizer, which just copies the file.
-# We need it to add ravenizer support to Soong on AOSP,
-# when the actual ravenizer is not in AOSP yet.
-
-invalid_arg() {
- echo "Ravenizer(fake): invalid args" 1>&2
- exit 1
-}
-
-(( $# >= 4 )) || invalid_arg
-[[ "$1" == "--in-jar" ]] || invalid_arg
-[[ "$3" == "--out-jar" ]] || invalid_arg
-
-echo "Ravenizer(fake): copiyng $2 to $4"
-
-cp "$2" "$4"
diff --git a/ravenwood/tools/ravenizer/Android.bp b/ravenwood/tools/ravenizer/Android.bp
new file mode 100644
index 0000000..2892d07
--- /dev/null
+++ b/ravenwood/tools/ravenizer/Android.bp
@@ -0,0 +1,25 @@
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_base_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+java_binary_host {
+ name: "ravenizer",
+ main_class: "com.android.platform.test.ravenwood.ravenizer.RavenizerMain",
+ srcs: ["src/**/*.kt"],
+ static_libs: [
+ "hoststubgen-lib",
+ "ow2-asm",
+ "ow2-asm-analysis",
+ "ow2-asm-commons",
+ "ow2-asm-tree",
+ "ow2-asm-util",
+ "junit",
+ "ravenwood-junit-impl-for-ravenizer",
+ ],
+ visibility: ["//visibility:public"],
+}
diff --git a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Ravenizer.kt b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Ravenizer.kt
new file mode 100644
index 0000000..da9c7d9
--- /dev/null
+++ b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Ravenizer.kt
@@ -0,0 +1,212 @@
+/*
+ * Copyright (C) 2024 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.platform.test.ravenwood.ravenizer
+
+import com.android.hoststubgen.GeneralUserErrorException
+import com.android.hoststubgen.asm.ClassNodes
+import com.android.hoststubgen.asm.zipEntryNameToClassName
+import com.android.hoststubgen.executableName
+import com.android.hoststubgen.log
+import com.android.platform.test.ravenwood.ravenizer.adapter.TestRunnerRewritingAdapter
+import org.objectweb.asm.ClassReader
+import org.objectweb.asm.ClassVisitor
+import org.objectweb.asm.ClassWriter
+import org.objectweb.asm.util.CheckClassAdapter
+import java.io.BufferedInputStream
+import java.io.BufferedOutputStream
+import java.io.FileOutputStream
+import java.io.InputStream
+import java.io.OutputStream
+import java.util.zip.ZipEntry
+import java.util.zip.ZipFile
+import java.util.zip.ZipOutputStream
+
+/**
+ * Various stats on Ravenizer.
+ */
+data class RavenizerStats(
+ /** Total end-to-end time. */
+ var totalTime: Double = .0,
+
+ /** Time took to build [ClasNodes] */
+ var loadStructureTime: Double = .0,
+
+ /** Total real time spent for converting the jar file */
+ var totalProcessTime: Double = .0,
+
+ /** Total real time spent for converting class files (except for I/O time). */
+ var totalConversionTime: Double = .0,
+
+ /** Total real time spent for copying class files without modification. */
+ var totalCopyTime: Double = .0,
+
+ /** # of entries in the input jar file */
+ var totalEntiries: Int = 0,
+
+ /** # of *.class files in the input jar file */
+ var totalClasses: Int = 0,
+
+ /** # of *.class files that have been processed. */
+ var processedClasses: Int = 0,
+) {
+ override fun toString(): String {
+ return """
+ RavenizerStats{
+ totalTime=$totalTime,
+ loadStructureTime=$loadStructureTime,
+ totalProcessTime=$totalProcessTime,
+ totalConversionTime=$totalConversionTime,
+ totalCopyTime=$totalCopyTime,
+ totalEntiries=$totalEntiries,
+ totalClasses=$totalClasses,
+ processedClasses=$processedClasses,
+ }
+ """.trimIndent()
+ }
+}
+
+/**
+ * Main class.
+ */
+class Ravenizer(val options: RavenizerOptions) {
+ fun run() {
+ val stats = RavenizerStats()
+ stats.totalTime = log.nTime {
+ process(options.inJar.get, options.outJar.get, stats)
+ }
+ log.i(stats.toString())
+ }
+
+ private fun process(inJar: String, outJar: String, stats: RavenizerStats) {
+ var allClasses = ClassNodes.loadClassStructures(inJar) {
+ time -> stats.loadStructureTime = time
+ }
+
+ stats.totalProcessTime = log.iTime("$executableName processing $inJar") {
+ ZipFile(inJar).use { inZip ->
+ val inEntries = inZip.entries()
+
+ stats.totalEntiries = inZip.size()
+
+ ZipOutputStream(BufferedOutputStream(FileOutputStream(outJar))).use { outZip ->
+ while (inEntries.hasMoreElements()) {
+ val entry = inEntries.nextElement()
+
+ if (entry.name.endsWith(".dex")) {
+ // Seems like it's an ART jar file. We can't process it.
+ // It's a fatal error.
+ throw GeneralUserErrorException(
+ "$inJar is not a desktop jar file. It contains a *.dex file."
+ )
+ }
+
+ val className = zipEntryNameToClassName(entry.name)
+
+ if (className != null) {
+ stats.totalClasses += 1
+ }
+
+ if (className != null && shouldProcessClass(allClasses, className)) {
+ stats.processedClasses += 1
+ processSingleClass(inZip, entry, outZip, allClasses, stats)
+ } else {
+ // Too slow, let's use merge_zips to bring back the original classes.
+ copyZipEntry(inZip, entry, outZip, stats)
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Copy a single ZIP entry to the output.
+ */
+ private fun copyZipEntry(
+ inZip: ZipFile,
+ entry: ZipEntry,
+ out: ZipOutputStream,
+ stats: RavenizerStats,
+ ) {
+ stats.totalCopyTime += log.nTime {
+ inZip.getInputStream(entry).use { ins ->
+ // Copy unknown entries as is to the impl out. (but not to the stub out.)
+ val outEntry = ZipEntry(entry.name)
+ outEntry.method = 0
+ outEntry.size = entry.size
+ outEntry.crc = entry.crc
+ out.putNextEntry(outEntry)
+
+ ins.transferTo(out)
+
+ out.closeEntry()
+ }
+ }
+ }
+
+ private fun processSingleClass(
+ inZip: ZipFile,
+ entry: ZipEntry,
+ outZip: ZipOutputStream,
+ allClasses: ClassNodes,
+ stats: RavenizerStats,
+ ) {
+ val newEntry = ZipEntry(entry.name)
+ outZip.putNextEntry(newEntry)
+
+ BufferedInputStream(inZip.getInputStream(entry)).use { bis ->
+ processSingleClass(entry, bis, outZip, allClasses, stats)
+ }
+ outZip.closeEntry()
+ }
+
+ /**
+ * Whether a class needs to be processed. This must be kept in sync with [processSingleClass].
+ */
+ private fun shouldProcessClass(classes: ClassNodes, classInternalName: String): Boolean {
+ return TestRunnerRewritingAdapter.shouldProcess(classes, classInternalName)
+ }
+
+ private fun processSingleClass(
+ entry: ZipEntry,
+ input: InputStream,
+ output: OutputStream,
+ allClasses: ClassNodes,
+ stats: RavenizerStats,
+ ) {
+ val cr = ClassReader(input)
+
+ lateinit var data: ByteArray
+ stats.totalConversionTime += log.vTime("Modify ${entry.name}") {
+ val flags = ClassWriter.COMPUTE_MAXS
+ val cw = ClassWriter(flags)
+ var outVisitor: ClassVisitor = cw
+
+ val enableChecker = false
+ if (enableChecker) {
+ outVisitor = CheckClassAdapter(outVisitor)
+ }
+
+ // This must be kept in sync with shouldProcessClass.
+ outVisitor = TestRunnerRewritingAdapter(allClasses, outVisitor)
+
+ cr.accept(outVisitor, ClassReader.EXPAND_FRAMES)
+
+ data = cw.toByteArray()
+ }
+ output.write(data)
+ }
+}
diff --git a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/RavenizerMain.kt b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/RavenizerMain.kt
new file mode 100644
index 0000000..ff41818
--- /dev/null
+++ b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/RavenizerMain.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2024 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.
+ */
+@file:JvmName("RavenizerMain")
+
+package com.android.platform.test.ravenwood.ravenizer
+
+import com.android.hoststubgen.LogLevel
+import com.android.hoststubgen.executableName
+import com.android.hoststubgen.log
+import com.android.hoststubgen.runMainWithBoilerplate
+
+/**
+ * Entry point.
+ */
+fun main(args: Array<String>) {
+ executableName = "Ravenizer"
+ log.setConsoleLogLevel(LogLevel.Info)
+
+ runMainWithBoilerplate {
+ val options = RavenizerOptions.parseArgs(args)
+
+ log.i("$executableName started")
+ log.v("Options: $options")
+
+ // Run.
+ Ravenizer(options).run()
+ }
+}
diff --git a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/RavenizerOptions.kt b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/RavenizerOptions.kt
new file mode 100644
index 0000000..e85e3be
--- /dev/null
+++ b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/RavenizerOptions.kt
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2024 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.platform.test.ravenwood.ravenizer
+
+import com.android.hoststubgen.ArgIterator
+import com.android.hoststubgen.ArgumentsException
+import com.android.hoststubgen.SetOnce
+import com.android.hoststubgen.ensureFileExists
+import com.android.hoststubgen.log
+
+class RavenizerOptions(
+ /** Input jar file*/
+ var inJar: SetOnce<String> = SetOnce(""),
+
+ /** Output jar file */
+ var outJar: SetOnce<String> = SetOnce(""),
+) {
+ companion object {
+ fun parseArgs(args: Array<String>): RavenizerOptions {
+ val ret = RavenizerOptions()
+ val ai = ArgIterator.withAtFiles(args)
+
+ while (true) {
+ val arg = ai.nextArgOptional()
+ if (arg == null) {
+ break
+ }
+
+ fun nextArg(): String = ai.nextArgRequired(arg)
+
+ if (log.maybeHandleCommandLineArg(arg) { nextArg() }) {
+ continue
+ }
+ try {
+ when (arg) {
+ // TODO: Write help
+ "-h", "--help" -> TODO("Help is not implemented yet")
+
+ "--in-jar" -> ret.inJar.set(nextArg()).ensureFileExists()
+ "--out-jar" -> ret.outJar.set(nextArg())
+
+ else -> throw ArgumentsException("Unknown option: $arg")
+ }
+ } catch (e: SetOnce.SetMoreThanOnceException) {
+ throw ArgumentsException("Duplicate or conflicting argument found: $arg")
+ }
+ }
+
+ if (!ret.inJar.isSet) {
+ throw ArgumentsException("Required option missing: --in-jar")
+ }
+ if (!ret.outJar.isSet) {
+ throw ArgumentsException("Required option missing: --out-jar")
+ }
+ return ret
+ }
+ }
+
+ override fun toString(): String {
+ return """
+ RavenizerOptions{
+ inJar=$inJar,
+ outJar=$outJar,
+ }
+ """.trimIndent()
+ }
+}
diff --git a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Utils.kt b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Utils.kt
new file mode 100644
index 0000000..0018648
--- /dev/null
+++ b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Utils.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2024 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.platform.test.ravenwood.ravenizer
+
+import com.android.hoststubgen.asm.ClassNodes
+import com.android.hoststubgen.asm.findAnyAnnotation
+import org.objectweb.asm.Type
+
+val junitTestMethodType = Type.getType(org.junit.Test::class.java)
+val junitRunWithType = Type.getType(org.junit.runner.RunWith::class.java)
+
+val junitTestMethodDescriptor = junitTestMethodType.descriptor
+val junitRunWithDescriptor = junitRunWithType.descriptor
+
+val junitTestMethodDescriptors = setOf<String>(junitTestMethodDescriptor)
+val junitRunWithDescriptors = setOf<String>(junitRunWithDescriptor)
+
+/**
+ * Returns true, if a test looks like it's a test class which needs to be processed.
+ */
+fun isTestLookingClass(classes: ClassNodes, className: String): Boolean {
+ // Similar to com.android.tradefed.lite.HostUtils.testLoadClass(), except it's more lenient,
+ // and accept non-public and/or abstract classes.
+ // HostUtils also checks "Suppress" or "SuiteClasses" but this one doesn't.
+ // TODO: SuiteClasses may need to be supported.
+
+ val cn = classes.findClass(className) ?: return false
+
+ if (cn.findAnyAnnotation(junitRunWithDescriptors) != null) {
+ return true
+ }
+ cn.methods?.forEach { method ->
+ if (method.findAnyAnnotation(junitTestMethodDescriptors) != null) {
+ return true
+ }
+ }
+ if (cn.superName == null) {
+ return false
+ }
+ return isTestLookingClass(classes, cn.superName)
+}
diff --git a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/adapter/TestRunnerRewritingAdapter.kt b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/adapter/TestRunnerRewritingAdapter.kt
new file mode 100644
index 0000000..c539908
--- /dev/null
+++ b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/adapter/TestRunnerRewritingAdapter.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2024 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.platform.test.ravenwood.ravenizer.adapter
+
+import com.android.hoststubgen.asm.ClassNodes
+import com.android.hoststubgen.visitors.OPCODE_VERSION
+import com.android.platform.test.ravenwood.ravenizer.isTestLookingClass
+import org.objectweb.asm.ClassVisitor
+
+/**
+ * Class visitor to rewrite the test runner for Ravenwood
+ *
+ * TODO: Implement it.
+ */
+class TestRunnerRewritingAdapter(
+ protected val classes: ClassNodes,
+ nextVisitor: ClassVisitor,
+) : ClassVisitor(OPCODE_VERSION, nextVisitor) {
+ companion object {
+ /**
+ * Returns true if a target class is interesting to this adapter.
+ */
+ fun shouldProcess(classes: ClassNodes, className: String): Boolean {
+ return isTestLookingClass(classes, className)
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/PackageWatchdog.java b/services/core/java/com/android/server/PackageWatchdog.java
index 0815384..e84250d 100644
--- a/services/core/java/com/android/server/PackageWatchdog.java
+++ b/services/core/java/com/android/server/PackageWatchdog.java
@@ -309,13 +309,14 @@
*/
public void registerHealthObserver(PackageHealthObserver observer) {
synchronized (mLock) {
- ObserverInternal internalObserver = mAllObservers.get(observer.getName());
+ ObserverInternal internalObserver = mAllObservers.get(observer.getUniqueIdentifier());
if (internalObserver != null) {
internalObserver.registeredObserver = observer;
} else {
- internalObserver = new ObserverInternal(observer.getName(), new ArrayList<>());
+ internalObserver = new ObserverInternal(observer.getUniqueIdentifier(),
+ new ArrayList<>());
internalObserver.registeredObserver = observer;
- mAllObservers.put(observer.getName(), internalObserver);
+ mAllObservers.put(observer.getUniqueIdentifier(), internalObserver);
syncState("added new observer");
}
}
@@ -342,12 +343,12 @@
public void startObservingHealth(PackageHealthObserver observer, List<String> packageNames,
long durationMs) {
if (packageNames.isEmpty()) {
- Slog.wtf(TAG, "No packages to observe, " + observer.getName());
+ Slog.wtf(TAG, "No packages to observe, " + observer.getUniqueIdentifier());
return;
}
if (durationMs < 1) {
Slog.wtf(TAG, "Invalid duration " + durationMs + "ms for observer "
- + observer.getName() + ". Not observing packages " + packageNames);
+ + observer.getUniqueIdentifier() + ". Not observing packages " + packageNames);
durationMs = DEFAULT_OBSERVING_DURATION_MS;
}
@@ -374,14 +375,14 @@
syncState("observing new packages");
synchronized (mLock) {
- ObserverInternal oldObserver = mAllObservers.get(observer.getName());
+ ObserverInternal oldObserver = mAllObservers.get(observer.getUniqueIdentifier());
if (oldObserver == null) {
- Slog.d(TAG, observer.getName() + " started monitoring health "
+ Slog.d(TAG, observer.getUniqueIdentifier() + " started monitoring health "
+ "of packages " + packageNames);
- mAllObservers.put(observer.getName(),
- new ObserverInternal(observer.getName(), packages));
+ mAllObservers.put(observer.getUniqueIdentifier(),
+ new ObserverInternal(observer.getUniqueIdentifier(), packages));
} else {
- Slog.d(TAG, observer.getName() + " added the following "
+ Slog.d(TAG, observer.getUniqueIdentifier() + " added the following "
+ "packages to monitor " + packageNames);
oldObserver.updatePackagesLocked(packages);
}
@@ -405,9 +406,9 @@
public void unregisterHealthObserver(PackageHealthObserver observer) {
mLongTaskHandler.post(() -> {
synchronized (mLock) {
- mAllObservers.remove(observer.getName());
+ mAllObservers.remove(observer.getUniqueIdentifier());
}
- syncState("unregistering observer: " + observer.getName());
+ syncState("unregistering observer: " + observer.getUniqueIdentifier());
});
}
@@ -781,7 +782,7 @@
* Identifier for the observer, should not change across device updates otherwise the
* watchdog may drop observing packages with the old name.
*/
- String getName();
+ String getUniqueIdentifier();
/**
* An observer will not be pruned if this is set, even if the observer is not explicitly
diff --git a/services/core/java/com/android/server/RescueParty.java b/services/core/java/com/android/server/RescueParty.java
index c2cb5e9..bba97fa 100644
--- a/services/core/java/com/android/server/RescueParty.java
+++ b/services/core/java/com/android/server/RescueParty.java
@@ -917,7 +917,7 @@
}
@Override
- public String getName() {
+ public String getUniqueIdentifier() {
return NAME;
}
diff --git a/services/core/java/com/android/server/display/ExternalDisplayPolicy.java b/services/core/java/com/android/server/display/ExternalDisplayPolicy.java
index 44c8d1c..28a0b28 100644
--- a/services/core/java/com/android/server/display/ExternalDisplayPolicy.java
+++ b/services/core/java/com/android/server/display/ExternalDisplayPolicy.java
@@ -231,7 +231,7 @@
if (!isExternalDisplayAllowed()) {
Slog.w(TAG, "handleExternalDisplayConnectedLocked: External display can not be used"
+ " because it is currently not allowed.");
- mDisplayNotificationManager.onHighTemperatureExternalDisplayNotAllowed();
+ mHandler.post(mDisplayNotificationManager::onHighTemperatureExternalDisplayNotAllowed);
return;
}
@@ -329,7 +329,7 @@
if (!isExternalDisplayAllowed()) {
Slog.w(TAG, "External display is currently not allowed and is getting disabled.");
- mDisplayNotificationManager.onHighTemperatureExternalDisplayNotAllowed();
+ mHandler.post(mDisplayNotificationManager::onHighTemperatureExternalDisplayNotAllowed);
}
mLogicalDisplayMapper.setDisplayEnabledLocked(logicalDisplay, /*enabled=*/ false);
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index aa56e8d..ba3de33 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -3352,6 +3352,12 @@
mConsumedKeysForDevice.put(deviceId, consumedKeys);
}
+ // TODO(b/358569822) Remove below once we have nicer API for listening to shortcuts
+ if ((event.isMetaPressed() || KeyEvent.isMetaKey(keyCode))
+ && shouldInterceptShortcuts(focusedToken)) {
+ return keyNotConsumed;
+ }
+
if (interceptSystemKeysAndShortcuts(focusedToken, event)
&& event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) {
consumedKeys.add(keyCode);
@@ -3842,6 +3848,15 @@
return (metaState & KeyEvent.META_META_ON) != 0;
}
+ private boolean shouldInterceptShortcuts(IBinder focusedToken) {
+ KeyInterceptionInfo info =
+ mWindowManagerInternal.getKeyInterceptionInfoFromToken(focusedToken);
+ boolean hasInterceptWindowFlag = (info.layoutParamsPrivateFlags
+ & WindowManager.LayoutParams.PRIVATE_FLAG_ALLOW_ACTION_KEY_EVENTS) != 0;
+ return hasInterceptWindowFlag && mButtonOverridePermissionChecker.canAppOverrideSystemKey(
+ mContext, info.windowOwnerUid);
+ }
+
/**
* In this function, we check whether a system key should be sent to the application. We also
* detect the key gesture on this key, even if the key will be sent to the app. The gesture
diff --git a/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java b/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
index 4f28e02..e91097c 100644
--- a/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
+++ b/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
@@ -16,6 +16,8 @@
package com.android.server.rollback;
+import static android.content.pm.Flags.provideInfoOfApkInApex;
+
import android.annotation.AnyThread;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -23,6 +25,7 @@
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.VersionedPackage;
import android.content.rollback.PackageRollbackInfo;
@@ -230,7 +233,7 @@
@Override
- public String getName() {
+ public String getUniqueIdentifier() {
return NAME;
}
@@ -486,19 +489,40 @@
*/
@AnyThread
private boolean isModule(String packageName) {
- // Check if the package is an APK inside an APEX. If it is, use the parent APEX package when
- // querying PackageManager.
- String apexPackageName = mApexManager.getActiveApexPackageNameContainingPackage(
- packageName);
- if (apexPackageName != null) {
- packageName = apexPackageName;
- }
-
PackageManager pm = mContext.getPackageManager();
- try {
- return pm.getModuleInfo(packageName, 0) != null;
- } catch (PackageManager.NameNotFoundException ignore) {
- return false;
+
+ if (Flags.refactorCrashrecovery() && provideInfoOfApkInApex()) {
+ // Check if the package is listed among the system modules.
+ boolean isApex = false;
+ try {
+ isApex = (pm.getModuleInfo(packageName, 0 /* flags */) != null);
+ } catch (PackageManager.NameNotFoundException e) {
+ //pass
+ }
+
+ // Check if the package is an APK inside an APEX.
+ boolean isApkInApex = false;
+ try {
+ final PackageInfo pkg = pm.getPackageInfo(packageName, 0 /* flags */);
+ isApkInApex = (pkg.getApexPackageName() != null);
+ } catch (PackageManager.NameNotFoundException e) {
+ // pass
+ }
+ return isApex || isApkInApex;
+ } else {
+ // Check if the package is an APK inside an APEX. If it is, use the parent APEX package
+ // when querying PackageManager.
+ String apexPackageName = mApexManager.getActiveApexPackageNameContainingPackage(
+ packageName);
+ if (apexPackageName != null) {
+ packageName = apexPackageName;
+ }
+
+ try {
+ return pm.getModuleInfo(packageName, 0) != null;
+ } catch (PackageManager.NameNotFoundException ignore) {
+ return false;
+ }
}
}
diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java
index 6721893..c543b6d 100644
--- a/services/core/java/com/android/server/tv/TvInputManagerService.java
+++ b/services/core/java/com/android/server/tv/TvInputManagerService.java
@@ -19,6 +19,7 @@
import static android.media.AudioManager.DEVICE_NONE;
import static android.media.tv.TvInputManager.INPUT_STATE_CONNECTED;
import static android.media.tv.TvInputManager.INPUT_STATE_CONNECTED_STANDBY;
+import static android.media.tv.flags.Flags.kidsModeTvdbSharing;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -805,6 +806,19 @@
}
}
+ private boolean isServiceSingleUser(ComponentName component) {
+ try {
+ ServiceInfo serviceInfo = getContext().getPackageManager()
+ .getServiceInfo(component, 0);
+ // Check if the single-user flag is present
+ return (serviceInfo.flags & ServiceInfo.FLAG_SINGLE_USER) != 0;
+ } catch (PackageManager.NameNotFoundException e) {
+ // Handle the case where the service is not found
+ Slog.e(TAG, "Service not found: " + component, e);
+ return false;
+ }
+ }
+
@GuardedBy("mLock")
private void abortPendingCreateSessionRequestsLocked(ServiceState serviceState,
String inputId, int userId) {
@@ -2840,6 +2854,26 @@
}
@Override
+ public int getClientUserId(String sessionId) {
+ ensureTunerResourceAccessPermission();
+ int clientUserId = TvInputManager.UNKNOWN_CLIENT_USER_ID;
+
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ try {
+ clientUserId = getClientUserIdLocked(sessionId);
+ } catch (ClientUserIdNotFoundException e) {
+ Slog.e(TAG, "error in getClientUserId", e);
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ return clientUserId;
+ }
+
+ @Override
public int getClientPriority(int useCase, String sessionId) {
ensureTunerResourceAccessPermission();
final int callingPid = Binder.getCallingPid();
@@ -2924,6 +2958,16 @@
return mSessionIdToSessionStateMap.get(sessionId).callingPid;
}
+ @GuardedBy("mLock")
+ private int getClientUserIdLocked(String sessionId) throws ClientUserIdNotFoundException {
+ SessionState sessionState = mSessionIdToSessionStateMap.get(sessionId);
+ if (sessionState == null) {
+ throw new ClientUserIdNotFoundException(
+ "Client UserId not found with sessionId " + sessionId);
+ }
+ return sessionState.userId;
+ }
+
private void ensureTunerResourceAccessPermission() {
if (mContext.checkCallingPermission(
android.Manifest.permission.TUNER_RESOURCE_ACCESS)
@@ -3495,11 +3539,15 @@
"bindServiceAsUser(service=" + serviceState.component + ", userId=" + userId
+ ")");
}
+ int bindUserId = userId;
+ if (kidsModeTvdbSharing() && isServiceSingleUser(serviceState.component)) {
+ bindUserId = UserHandle.USER_SYSTEM;
+ }
Intent i =
new Intent(TvInputService.SERVICE_INTERFACE).setComponent(serviceState.component);
serviceState.bound = mContext.bindServiceAsUser(i, serviceState.connection,
Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE,
- new UserHandle(userId));
+ new UserHandle(bindUserId));
if (!serviceState.bound) {
Slog.e(TAG, "failed to bind " + serviceState.component + " for userId " + userId);
mContext.unbindService(serviceState.connection);
@@ -4700,4 +4748,10 @@
super(name);
}
}
+
+ private static class ClientUserIdNotFoundException extends IllegalArgumentException {
+ ClientUserIdNotFoundException(String name) {
+ super(name);
+ }
+ }
}
diff --git a/services/core/java/com/android/server/wm/DimmerAnimationHelper.java b/services/core/java/com/android/server/wm/DimmerAnimationHelper.java
index 37e4449..3dba57f 100644
--- a/services/core/java/com/android/server/wm/DimmerAnimationHelper.java
+++ b/services/core/java/com/android/server/wm/DimmerAnimationHelper.java
@@ -55,11 +55,10 @@
Change() {}
- void copyFrom(@NonNull Change other) {
+ Change(@NonNull Change other) {
mAlpha = other.mAlpha;
mBlurRadius = other.mBlurRadius;
mDimmingContainer = other.mDimmingContainer;
- mGeometryParent = other.mGeometryParent;
mRelativeLayer = other.mRelativeLayer;
}
@@ -84,8 +83,8 @@
}
}
- private final Change mCurrentProperties = new Change();
- private final Change mRequestedProperties = new Change();
+ private Change mCurrentProperties = new Change();
+ private Change mRequestedProperties = new Change();
private AnimationSpec mAlphaAnimationSpec;
private final AnimationAdapterFactory mAnimationAdapterFactory;
@@ -129,7 +128,7 @@
+ "call adjustRelativeLayer?");
return;
}
- if (mRequestedProperties.mDimmingContainer.getSurfaceControl() == null) {
+ if (mRequestedProperties.mDimmingContainer.mSurfaceControl == null) {
Log.w(TAG, "container " + mRequestedProperties.mDimmingContainer
+ "does not have a surface");
dim.remove(t);
@@ -155,35 +154,35 @@
"%s skipping animation and directly setting alpha=%f, blur=%d",
dim, mRequestedProperties.mAlpha,
mRequestedProperties.mBlurRadius);
- mCurrentProperties.copyFrom(mRequestedProperties);
- setCurrentAlphaBlur(dim.mDimSurface, t);
+ setAlphaBlur(dim.mDimSurface, mRequestedProperties.mAlpha,
+ mRequestedProperties.mBlurRadius, t);
dim.mSkipAnimation = false;
} else {
- Change startProperties = mCurrentProperties;
- mCurrentProperties.copyFrom(mRequestedProperties);
- startAnimation(t, dim, startProperties, mRequestedProperties);
+ startAnimation(t, dim);
}
+
} else if (!dim.isDimming()) {
// We are not dimming, so we tried the exit animation but the alpha is already 0,
// therefore, let's just remove this surface
dim.remove(t);
}
+ mCurrentProperties = new Change(mRequestedProperties);
}
private void startAnimation(
- @NonNull SurfaceControl.Transaction t, @NonNull Dimmer.DimState dim,
- @NonNull Change from, @NonNull Change to) {
+ @NonNull SurfaceControl.Transaction t, @NonNull Dimmer.DimState dim) {
ProtoLog.v(WM_DEBUG_DIMMER, "Starting animation on %s", dim);
- mAlphaAnimationSpec = getRequestedAnimationSpec(from, to);
+ mAlphaAnimationSpec = getRequestedAnimationSpec();
mLocalAnimationAdapter = mAnimationAdapterFactory.get(mAlphaAnimationSpec,
dim.mHostContainer.mWmService.mSurfaceAnimationRunner);
- float targetAlpha = to.mAlpha;
+ float targetAlpha = mRequestedProperties.mAlpha;
+ int targetBlur = mRequestedProperties.mBlurRadius;
mLocalAnimationAdapter.startAnimation(dim.mDimSurface, t,
ANIMATION_TYPE_DIMMER, /* finishCallback */ (type, animator) -> {
synchronized (dim.mHostContainer.mWmService.mGlobalLock) {
- setCurrentAlphaBlur(dim.mDimSurface, t);
+ setAlphaBlur(dim.mDimSurface, targetAlpha, targetBlur, t);
if (targetAlpha == 0f && !dim.isDimming()) {
dim.remove(t);
}
@@ -208,15 +207,15 @@
}
@NonNull
- private static AnimationSpec getRequestedAnimationSpec(Change from, Change to) {
- final float startAlpha = Math.max(from.mAlpha, 0f);
- final int startBlur = Math.max(from.mBlurRadius, 0);
- long duration = (long) (getDimDuration(to.mDimmingContainer)
- * Math.abs(to.mAlpha - startAlpha));
+ private AnimationSpec getRequestedAnimationSpec() {
+ final float startAlpha = Math.max(mCurrentProperties.mAlpha, 0f);
+ final int startBlur = Math.max(mCurrentProperties.mBlurRadius, 0);
+ long duration = (long) (getDimDuration(mRequestedProperties.mDimmingContainer)
+ * Math.abs(mRequestedProperties.mAlpha - startAlpha));
final AnimationSpec spec = new AnimationSpec(
- new AnimationSpec.AnimationExtremes<>(startAlpha, to.mAlpha),
- new AnimationSpec.AnimationExtremes<>(startBlur, to.mBlurRadius),
+ new AnimationSpec.AnimationExtremes<>(startAlpha, mRequestedProperties.mAlpha),
+ new AnimationSpec.AnimationExtremes<>(startBlur, mRequestedProperties.mBlurRadius),
duration
);
ProtoLog.v(WM_DEBUG_DIMMER, "Dim animation requested: %s", spec);
@@ -226,7 +225,7 @@
/**
* Change the geometry and relative parent of this dim layer
*/
- static void reparent(@NonNull SurfaceControl dimLayer,
+ void reparent(@NonNull SurfaceControl dimLayer,
@Nullable SurfaceControl newGeometryParent,
@NonNull SurfaceControl relativeParent,
int relativePosition,
@@ -241,16 +240,17 @@
}
}
- void setCurrentAlphaBlur(@NonNull SurfaceControl sc, @NonNull SurfaceControl.Transaction t) {
+ void setAlphaBlur(@NonNull SurfaceControl sc, float alpha, int blur,
+ @NonNull SurfaceControl.Transaction t) {
try {
- t.setAlpha(sc, mCurrentProperties.mAlpha);
- t.setBackgroundBlurRadius(sc, mCurrentProperties.mBlurRadius);
+ t.setAlpha(sc, alpha);
+ t.setBackgroundBlurRadius(sc, blur);
} catch (NullPointerException e) {
Log.w(TAG , "Tried to change look of dim " + sc + " after remove", e);
}
}
- private static long getDimDuration(@NonNull WindowContainer<?> container) {
+ private long getDimDuration(@NonNull WindowContainer<?> container) {
// Use the same duration as the animation on the WindowContainer
AnimationAdapter animationAdapter = container.mSurfaceAnimator.getAnimation();
final float durationScale = container.mWmService.getTransitionAnimationScaleLocked();
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 648f6bd..86285fb 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -3496,10 +3496,7 @@
*/
void collectDisplayChange(@NonNull Transition transition) {
if (!mLastHasContent) return;
- if (!transition.isCollecting()) {
- throw new IllegalArgumentException("Can only collect display change if transition"
- + " is collecting");
- }
+ if (!transition.isCollecting()) return;
if (!transition.mParticipants.contains(this)) {
transition.collect(this);
startAsyncRotationIfNeeded();
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index ef25eda..1d2b693 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -408,6 +408,10 @@
*/
@Nullable
Transition getCollectingTransition() {
+ if (mCollectingTransition != null && !mCollectingTransition.isCollecting()) {
+ Slog.wtfStack(TAG, "Collecting Transition (#" + mCollectingTransition.getSyncId()
+ + ") is not collecting. state=" + mCollectingTransition.getState());
+ }
return mCollectingTransition;
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index bdb1d43..87c0084 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -32,6 +32,7 @@
import static android.app.AppOpsManager.OP_SYSTEM_ALERT_WINDOW;
import static android.app.StatusBarManager.DISABLE_MASK;
import static android.app.WindowConfiguration.ROTATION_UNDEFINED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED;
import static android.content.pm.PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT;
import static android.content.pm.PackageManager.FEATURE_PC;
@@ -9024,14 +9025,7 @@
}
clearPointerDownOutsideFocusRunnable();
- // For embedded activity that is showing side-by-side with another activity, delay
- // handling the touch-outside event to prevent focus rapid changes back-n-forth.
- // Otherwise, handle the touch-outside event directly.
- final WindowState w = t.getWindowState();
- final ActivityRecord activity = w != null ? w.getActivityRecord() : null;
- if (mFocusedInputTarget != t && mFocusedInputTarget != null
- && activity != null && activity.isEmbedded()
- && activity.getTaskFragment().getAdjacentTaskFragment() != null) {
+ if (shouldDelayTouchOutside(t)) {
mPointerDownOutsideFocusRunnable = () -> handlePointerDownOutsideFocus(t);
mH.postDelayed(mPointerDownOutsideFocusRunnable, POINTER_DOWN_OUTSIDE_FOCUS_TIMEOUT_MS);
} else if (!fromHandler) {
@@ -9044,6 +9038,33 @@
}
}
+ private boolean shouldDelayTouchOutside(InputTarget t) {
+ final WindowState w = t.getWindowState();
+ final ActivityRecord activity = w != null ? w.getActivityRecord() : null;
+ final Task task = w != null ? w.getRootTask() : null;
+
+ final boolean isInputTargetNotFocused =
+ mFocusedInputTarget != t && mFocusedInputTarget != null;
+ if (!isInputTargetNotFocused) {
+ return false;
+ }
+
+ // For embedded activity that is showing side-by-side with another activity, delay
+ // handling the touch-outside event to prevent focus rapid changes back-n-forth.
+ final boolean shouldDelayTouchForEmbeddedActivity = activity != null
+ && activity.isEmbedded()
+ && activity.getTaskFragment().getAdjacentTaskFragment() != null;
+
+ // For cases when there are multiple freeform windows where non-top windows are blocking
+ // the gesture zones, delay handling the touch-outside event to prevent refocusing the
+ // the non-top windows during the gesture.
+ final boolean shouldDelayTouchForFreeform =
+ task != null && task.getWindowingMode() == WINDOWING_MODE_FREEFORM;
+
+ // If non of the above cases are true, handle the touch-outside event directly.
+ return shouldDelayTouchForEmbeddedActivity || shouldDelayTouchForFreeform;
+ }
+
private void handlePointerDownOutsideFocus(InputTarget t) {
synchronized (mGlobalLock) {
if (mPointerDownOutsideFocusRunnable != null
diff --git a/services/tests/displayservicetests/src/com/android/server/display/ExternalDisplayPolicyTest.java b/services/tests/displayservicetests/src/com/android/server/display/ExternalDisplayPolicyTest.java
index 82acaf8..f728168 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/ExternalDisplayPolicyTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/ExternalDisplayPolicyTest.java
@@ -258,6 +258,7 @@
when(mMockedLogicalDisplay.isEnabledLocked()).thenReturn(false);
mExternalDisplayPolicy.handleExternalDisplayConnectedLocked(mMockedLogicalDisplay);
+ mHandler.flush();
verify(mMockedInjector, never()).sendExternalDisplayEventLocked(any(), anyInt());
verify(mMockedDisplayNotificationManager, times(2))
.onHighTemperatureExternalDisplayNotAllowed();
diff --git a/services/tests/wmtests/src/com/android/server/policy/MetaKeyEventsInterceptionTests.java b/services/tests/wmtests/src/com/android/server/policy/MetaKeyEventsInterceptionTests.java
new file mode 100644
index 0000000..b979335
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/policy/MetaKeyEventsInterceptionTests.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2024 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.policy;
+
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_ALLOW_ACTION_KEY_EVENTS;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.view.KeyEvent;
+import android.view.WindowManager;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.policy.KeyInterceptionInfo;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Testing {@link PhoneWindowManager} functionality of letting app intercepting key events
+ * containing META.
+ */
+@SmallTest
+public class MetaKeyEventsInterceptionTests extends ShortcutKeyTestBase {
+
+ private static final List<KeyEvent> META_KEY_EVENTS = Arrays.asList(
+ new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_META_LEFT),
+ new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_META_RIGHT),
+ new KeyEvent(/* downTime= */ 0, /* eventTime= */
+ 0, /* action= */ 0, /* code= */ 0, /* repeat= */ 0,
+ /* metaState= */ KeyEvent.META_META_ON));
+
+ @Before
+ public void setUp() {
+ setUpPhoneWindowManager();
+ }
+
+ @Test
+ public void doesntInterceptMetaKeyEvents_whenWindowAskedForIt() {
+ mPhoneWindowManager.overrideFocusedWindowButtonOverridePermission(/* granted= */ true);
+ setWindowKeyInterceptionWithPrivateFlags(PRIVATE_FLAG_ALLOW_ACTION_KEY_EVENTS);
+
+ META_KEY_EVENTS.forEach(keyEvent -> {
+ assertKeyInterceptionResult(keyEvent, /* intercepted= */ false);
+ });
+ }
+
+ @Test
+ public void interceptsMetaKeyEvents_whenWindowDoesntHaveFlagSet() {
+ mPhoneWindowManager.overrideFocusedWindowButtonOverridePermission(/* granted= */ true);
+ setWindowKeyInterceptionWithPrivateFlags(0);
+
+ META_KEY_EVENTS.forEach(keyEvent -> {
+ assertKeyInterceptionResult(keyEvent, /* intercepted= */ true);
+ });
+ }
+
+ @Test
+ public void interceptsMetaKeyEvents_whenWindowDoesntHavePermission() {
+ mPhoneWindowManager.overrideFocusedWindowButtonOverridePermission(/* granted= */ false);
+ setWindowKeyInterceptionWithPrivateFlags(PRIVATE_FLAG_ALLOW_ACTION_KEY_EVENTS);
+
+ META_KEY_EVENTS.forEach(keyEvent -> {
+ assertKeyInterceptionResult(keyEvent, /* intercepted= */ true);
+ });
+ }
+
+ private void setWindowKeyInterceptionWithPrivateFlags(int privateFlags) {
+ KeyInterceptionInfo info = new KeyInterceptionInfo(
+ WindowManager.LayoutParams.TYPE_APPLICATION, privateFlags, "title", 0);
+ mPhoneWindowManager.overrideWindowKeyInterceptionInfo(info);
+ }
+
+ private void assertKeyInterceptionResult(KeyEvent keyEvent, boolean intercepted) {
+ long result = mPhoneWindowManager.interceptKeyBeforeDispatching(keyEvent);
+ int expected = intercepted ? -1 : 0;
+ assertThat(result).isEqualTo(expected);
+ }
+}
diff --git a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
index 43b065d..79c7ac1 100644
--- a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
+++ b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
@@ -52,6 +52,7 @@
import static org.mockito.Mockito.description;
import static org.mockito.Mockito.mockingDetails;
import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.when;
import static org.mockito.Mockito.withSettings;
import android.app.ActivityManagerInternal;
@@ -613,6 +614,10 @@
.when(mButtonOverridePermissionChecker).canAppOverrideSystemKey(any(), anyInt());
}
+ void overrideWindowKeyInterceptionInfo(KeyInterceptionInfo info) {
+ when(mWindowManagerInternal.getKeyInterceptionInfoFromToken(any())).thenReturn(info);
+ }
+
void overrideKeyEventPolicyFlags(int flags) {
mKeyEventPolicyFlags = flags;
}
diff --git a/telephony/java/android/telephony/satellite/SatelliteModemEnableRequestAttributes.java b/telephony/java/android/telephony/satellite/SatelliteModemEnableRequestAttributes.java
new file mode 100644
index 0000000..5e56f84
--- /dev/null
+++ b/telephony/java/android/telephony/satellite/SatelliteModemEnableRequestAttributes.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2024 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.satellite;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+/**
+ * SatelliteModemEnableRequestAttributes is used to pack info needed by modem to allow carrier to
+ * roam to satellite.
+ *
+ * @hide
+ */
+public final class SatelliteModemEnableRequestAttributes implements Parcelable {
+
+ /** {@code true} to enable satellite and {@code false} to disable satellite */
+ private final boolean mIsEnabled;
+ /**
+ * {@code true} to enable demo mode and {@code false} to disable. When disabling satellite,
+ * {@code mIsDemoMode} is always considered as {@code false} by Telephony.
+ */
+ private final boolean mIsDemoMode;
+ /**
+ * {@code true} means satellite is enabled for emergency mode, {@code false} otherwise. When
+ * disabling satellite, {@code isEmergencyMode} is always considered as {@code false} by
+ * Telephony.
+ */
+ private final boolean mIsEmergencyMode;
+
+ /** The subscription related info */
+ @NonNull private final SatelliteSubscriptionInfo mSatelliteSubscriptionInfo;
+
+ public SatelliteModemEnableRequestAttributes(boolean isEnabled, boolean isDemoMode,
+ boolean isEmergencyMode, @NonNull SatelliteSubscriptionInfo satelliteSubscriptionInfo) {
+ mIsEnabled = isEnabled;
+ mIsDemoMode = isDemoMode;
+ mIsEmergencyMode = isEmergencyMode;
+ mSatelliteSubscriptionInfo = satelliteSubscriptionInfo;
+ }
+
+ private SatelliteModemEnableRequestAttributes(Parcel in) {
+ mIsEnabled = in.readBoolean();
+ mIsDemoMode = in.readBoolean();
+ mIsEmergencyMode = in.readBoolean();
+ mSatelliteSubscriptionInfo = in.readParcelable(
+ SatelliteSubscriptionInfo.class.getClassLoader(), SatelliteSubscriptionInfo.class);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeBoolean(mIsEnabled);
+ dest.writeBoolean(mIsDemoMode);
+ dest.writeBoolean(mIsEmergencyMode);
+ mSatelliteSubscriptionInfo.writeToParcel(dest, flags);
+ }
+
+ public static final Creator<SatelliteModemEnableRequestAttributes> CREATOR = new Creator<>() {
+ @Override
+ public SatelliteModemEnableRequestAttributes createFromParcel(Parcel in) {
+ return new SatelliteModemEnableRequestAttributes(in);
+ }
+
+ @Override
+ public SatelliteModemEnableRequestAttributes[] newArray(int size) {
+ return new SatelliteModemEnableRequestAttributes[size];
+ }
+ };
+
+ @Override
+ public String toString() {
+ return (new StringBuilder()).append("SatelliteModemEnableRequestAttributes{")
+ .append(", mIsEnabled=").append(mIsEnabled)
+ .append(", mIsDemoMode=").append(mIsDemoMode)
+ .append(", mIsDemoMode=").append(mIsDemoMode)
+ .append("mSatelliteSubscriptionInfo=").append(mSatelliteSubscriptionInfo)
+ .append("}")
+ .toString();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ SatelliteModemEnableRequestAttributes that = (SatelliteModemEnableRequestAttributes) o;
+ return mIsEnabled == that.mIsEnabled && mIsDemoMode == that.mIsDemoMode
+ && mIsEmergencyMode == that.mIsEmergencyMode && mSatelliteSubscriptionInfo.equals(
+ that.mSatelliteSubscriptionInfo);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mIsEnabled, mIsDemoMode, mIsEmergencyMode, mSatelliteSubscriptionInfo);
+ }
+
+ public boolean isEnabled() {
+ return mIsEnabled;
+ }
+
+ public boolean isDemoMode() {
+ return mIsDemoMode;
+ }
+
+ public boolean isEmergencyMode() {
+ return mIsEmergencyMode;
+ }
+
+ @NonNull public SatelliteSubscriptionInfo getSatelliteSubscriptionInfo() {
+ return mSatelliteSubscriptionInfo;
+ }
+}
diff --git a/telephony/java/android/telephony/satellite/SatelliteSubscriptionInfo.java b/telephony/java/android/telephony/satellite/SatelliteSubscriptionInfo.java
new file mode 100644
index 0000000..2ef19f8
--- /dev/null
+++ b/telephony/java/android/telephony/satellite/SatelliteSubscriptionInfo.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2024 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.satellite;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+/**
+ * SatelliteSubscriptionInfo is used to pack subscription related info needed by modem to allow
+ * carrier to roam to satellite.
+ *
+ * @hide
+ */
+public final class SatelliteSubscriptionInfo implements Parcelable {
+ /**
+ * The ICC ID used for satellite attachment.
+ */
+ @NonNull private final String mIccId;
+
+ /**
+ * The NIDD(Non IP Data) APN to be used for carrier roaming to satellite attachment.
+ */
+ @NonNull private final String mNiddApn;
+
+ public SatelliteSubscriptionInfo(@NonNull String iccId, @NonNull String niddApn) {
+ mIccId = iccId;
+ mNiddApn = niddApn;
+ }
+
+ private SatelliteSubscriptionInfo(Parcel in) {
+ mIccId = in.readString8();
+ mNiddApn = in.readString8();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeString8(mIccId);
+ dest.writeString8(mNiddApn);
+ }
+
+ @NonNull public static final Creator<SatelliteSubscriptionInfo> CREATOR = new Creator<>() {
+ @Override
+ public SatelliteSubscriptionInfo createFromParcel(Parcel in) {
+ return new SatelliteSubscriptionInfo(in);
+ }
+
+ @Override
+ public SatelliteSubscriptionInfo[] newArray(int size) {
+ return new SatelliteSubscriptionInfo[size];
+ }
+ };
+
+ @Override
+ @NonNull public String toString() {
+ return (new StringBuilder()).append("SatelliteSubscriptionInfo{")
+ .append("IccId=").append(mIccId)
+ .append(", NiddApn=").append(mNiddApn)
+ .append("}")
+ .toString();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ SatelliteSubscriptionInfo that = (SatelliteSubscriptionInfo) o;
+ return mIccId.equals(that.getIccId()) && mNiddApn.equals(that.getNiddApn());
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(getIccId(), getNiddApn());
+ }
+
+ @NonNull
+ public String getIccId() {
+ return mIccId;
+ }
+
+ @NonNull
+ public String getNiddApn() {
+ return mNiddApn;
+ }
+}
diff --git a/telephony/java/android/telephony/satellite/stub/SatelliteModemEnableRequestAttributes.aidl b/telephony/java/android/telephony/satellite/stub/SatelliteModemEnableRequestAttributes.aidl
new file mode 100644
index 0000000..0a40e15
--- /dev/null
+++ b/telephony/java/android/telephony/satellite/stub/SatelliteModemEnableRequestAttributes.aidl
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2023 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.satellite.stub;
+
+import android.telephony.satellite.stub.SatelliteSubscriptionInfo;
+
+/**
+ * {@hide}
+ */
+ parcelable SatelliteModemEnableRequestAttributes {
+ /**
+ * {@code true} to enable satellite and {@code false} to disable.
+ */
+ boolean isEnabled;
+ /**
+ * {@code true} to enable demo mode and {@code false} to disable.
+ */
+ boolean isDemoMode;
+ /**
+ * {@code true} to enable emergency modeand {@code false} to disable.
+ */
+ boolean isEmergencyMode;
+ /**
+ * The subscription related info.
+ */
+ SatelliteSubscriptionInfo satelliteSubscriptionInfo;
+ }
\ No newline at end of file
diff --git a/telephony/java/android/telephony/satellite/stub/SatelliteSubscriptionInfo.aidl b/telephony/java/android/telephony/satellite/stub/SatelliteSubscriptionInfo.aidl
new file mode 100644
index 0000000..f664dda
--- /dev/null
+++ b/telephony/java/android/telephony/satellite/stub/SatelliteSubscriptionInfo.aidl
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2023 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.satellite.stub;
+
+/**
+ * {@hide}
+ */
+ parcelable SatelliteSubscriptionInfo {
+ /**
+ * The ICC ID used for satellite attachment.
+ */
+ String iccId;
+ /**
+ * The NIDD(Non IP Data) APN to be used for carrier roaming to satellite attachment.
+ */
+ String niddApn;
+ }
\ No newline at end of file
diff --git a/tests/Internal/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java b/tests/Internal/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java
index 4b745b2..4826f42 100644
--- a/tests/Internal/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java
+++ b/tests/Internal/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java
@@ -43,7 +43,6 @@
import android.tracing.perfetto.DataSource;
import android.util.proto.ProtoInputStream;
-import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
import com.android.internal.protolog.common.IProtoLogGroup;
@@ -73,7 +72,6 @@
* Test class for {@link ProtoLogImpl}.
*/
@SuppressWarnings("ConstantConditions")
-@SmallTest
@Presubmit
@RunWith(JUnit4.class)
public class PerfettoProtoLogImplTest {
@@ -166,8 +164,7 @@
mReader = Mockito.spy(new ProtoLogViewerConfigReader(viewerConfigInputStreamProvider));
mProtoLog = new PerfettoProtoLogImpl(
viewerConfigInputStreamProvider, mReader,
- () -> mCacheUpdater.run());
- mProtoLog.registerGroups(TestProtoLogGroup.values());
+ () -> mCacheUpdater.run(), TestProtoLogGroup.values());
}
@After
diff --git a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
index ab406ef..5b17825 100644
--- a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
+++ b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
@@ -1867,7 +1867,7 @@
return true;
}
- public String getName() {
+ public String getUniqueIdentifier() {
return mName;
}
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/Exceptions.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/Exceptions.kt
index 910bf59..f59e143 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/Exceptions.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/Exceptions.kt
@@ -15,6 +15,8 @@
*/
package com.android.hoststubgen
+import java.io.File
+
/**
* We will not print the stack trace for exceptions implementing it.
*/
@@ -49,4 +51,22 @@
/**
* We use this for general "user" errors.
*/
-class HostStubGenUserErrorException(message: String) : Exception(message), UserErrorException
+class GeneralUserErrorException(message: String) : Exception(message), UserErrorException
+
+/** Base exception class for invalid command line arguments. */
+open class ArgumentsException(message: String?) : Exception(message), UserErrorException
+
+/** Thrown when the same annotation is used with different annotation arguments. */
+class DuplicateAnnotationException(annotationName: String?) :
+ ArgumentsException("Duplicate annotation specified: '$annotationName'")
+
+/** Thrown when an input file does not exist. */
+class InputFileNotFoundException(filename: String) :
+ ArgumentsException("File '$filename' not found")
+
+fun String.ensureFileExists(): String {
+ if (!File(this).exists()) {
+ throw InputFileNotFoundException(this)
+ }
+ return this
+}
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenErrors.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenErrors.kt
index 6b01d48..a218c55 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenErrors.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenErrors.kt
@@ -19,6 +19,6 @@
open fun onErrorFound(message: String) {
// TODO: For now, we just throw as soon as any error is found, but eventually we should keep
// all errors and print them at the end.
- throw HostStubGenUserErrorException(message)
+ throw GeneralUserErrorException(message)
}
}
\ No newline at end of file
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenLogger.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenLogger.kt
index fcdf824..4bcee40 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenLogger.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenLogger.kt
@@ -89,6 +89,8 @@
addPrinter(StreamPrinter(level, PrintWriter(BufferedOutputStream(
FileOutputStream(logFilename)))))
+ log.i("Log file set: $logFilename for $level")
+
return this
}
@@ -122,6 +124,9 @@
}
fun println(level: LogLevel, message: String) {
+ if (message.isEmpty()) {
+ return // Don't print an empty message.
+ }
printers.forEach {
if (it.logLevel.ordinal >= level.ordinal) {
it.println(level, indent, message)
@@ -185,31 +190,45 @@
println(LogLevel.Debug, format, *args)
}
- inline fun <T> logTime(level: LogLevel, message: String, block: () -> T): T {
+ inline fun <T> logTime(level: LogLevel, message: String, block: () -> T): Double {
+ var ret: Double = -1.0
val start = System.currentTimeMillis()
try {
- return block()
+ block()
} finally {
val end = System.currentTimeMillis()
+ ret = (end - start) / 1000.0
if (isEnabled(level)) {
println(level,
String.format("%s: took %.1f second(s).", message, (end - start) / 1000.0))
}
}
+ return ret
}
- inline fun <T> iTime(message: String, block: () -> T): T {
+ /** Do an "i" log with how long it took. */
+ inline fun <T> iTime(message: String, block: () -> T): Double {
return logTime(LogLevel.Info, message, block)
}
- inline fun <T> vTime(message: String, block: () -> T): T {
+ /** Do a "v" log with how long it took. */
+ inline fun <T> vTime(message: String, block: () -> T): Double {
return logTime(LogLevel.Verbose, message, block)
}
- inline fun <T> dTime(message: String, block: () -> T): T {
+ /** Do a "d" log with how long it took. */
+ inline fun <T> dTime(message: String, block: () -> T): Double {
return logTime(LogLevel.Debug, message, block)
}
+ /**
+ * Similar to the other "xTime" methods, but the message is not supposed to be printed.
+ * It's only used to measure the duration with the same interface as other log methods.
+ */
+ inline fun <T> nTime(block: () -> T): Double {
+ return logTime(LogLevel.Debug, "", block)
+ }
+
inline fun forVerbose(block: () -> Unit) {
if (isEnabled(LogLevel.Verbose)) {
block()
@@ -253,6 +272,21 @@
}
}
}
+
+ /**
+ * Handle log-related command line arguments.
+ */
+ fun maybeHandleCommandLineArg(currentArg: String, nextArgProvider: () -> String): Boolean {
+ when (currentArg) {
+ "-v", "--verbose" -> setConsoleLogLevel(LogLevel.Verbose)
+ "-d", "--debug" -> setConsoleLogLevel(LogLevel.Debug)
+ "-q", "--quiet" -> setConsoleLogLevel(LogLevel.None)
+ "--verbose-log" -> addFilePrinter(LogLevel.Verbose, nextArgProvider())
+ "--debug-log" -> addFilePrinter(LogLevel.Debug, nextArgProvider())
+ else -> return false
+ }
+ return true
+ }
}
private interface LogPrinter {
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenMain.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenMain.kt
index 45e7e30..8506466 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenMain.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenMain.kt
@@ -24,20 +24,32 @@
*/
fun main(args: Array<String>) {
executableName = "HostStubGen"
+ runMainWithBoilerplate {
+ // Parse the command line arguments.
+ var clanupOnError = false
+ try {
+ val options = HostStubGenOptions.parseArgs(args)
+ clanupOnError = options.cleanUpOnError.get
+ log.v("$executableName started")
+ log.v("Options: $options")
+
+ // Run.
+ HostStubGen(options).run()
+ } catch (e: Throwable) {
+ if (clanupOnError) {
+ TODO("Remove output jars here")
+ }
+ throw e
+ }
+ }
+}
+
+inline fun runMainWithBoilerplate(realMain: () -> Unit) {
var success = false
- var clanupOnError = false
try {
- // Parse the command line arguments.
- val options = HostStubGenOptions.parseArgs(args)
- clanupOnError = options.cleanUpOnError.get
-
- log.v("$executableName started")
- log.v("Options: $options")
-
- // Run.
- HostStubGen(options).run()
+ realMain()
success = true
} catch (e: Throwable) {
@@ -45,9 +57,6 @@
if (e !is UserErrorException) {
e.printStackTrace(PrintWriter(log.getWriter(LogLevel.Error)))
}
- if (clanupOnError) {
- TODO("Remove output jars here")
- }
} finally {
log.i("$executableName finished")
log.flush()
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt
index 2f833a8..f88b107 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt
@@ -17,20 +17,17 @@
import com.android.hoststubgen.filters.FilterPolicy
import java.io.BufferedReader
-import java.io.File
import java.io.FileReader
/**
* A single value that can only set once.
*/
-class SetOnce<T>(
- private var value: T,
-) {
+open class SetOnce<T>(private var value: T) {
class SetMoreThanOnceException : Exception()
private var set = false
- fun set(v: T) {
+ fun set(v: T): T {
if (set) {
throw SetMoreThanOnceException()
}
@@ -39,6 +36,7 @@
}
set = true
value = v
+ return v
}
val get: T
@@ -59,6 +57,16 @@
}
}
+class IntSetOnce(value: Int) : SetOnce<Int>(value) {
+ fun set(v: String): Int {
+ try {
+ return this.set(v.toInt())
+ } catch (e: NumberFormatException) {
+ throw ArgumentsException("Invalid integer $v")
+ }
+ }
+}
+
/**
* Options that can be set from command line arguments.
*/
@@ -113,18 +121,11 @@
var apiListFile: SetOnce<String?> = SetOnce(null),
- var numShards: SetOnce<Int> = SetOnce(1),
- var shard: SetOnce<Int> = SetOnce(0),
+ var numShards: IntSetOnce = IntSetOnce(1),
+ var shard: IntSetOnce = IntSetOnce(0),
) {
companion object {
- private fun String.ensureFileExists(): String {
- if (!File(this).exists()) {
- throw InputFileNotFoundException(this)
- }
- return this
- }
-
private fun parsePackageRedirect(fromColonTo: String): Pair<String, String> {
val colon = fromColonTo.indexOf(':')
if ((colon < 1) || (colon + 1 >= fromColonTo.length)) {
@@ -137,7 +138,7 @@
fun parseArgs(args: Array<String>): HostStubGenOptions {
val ret = HostStubGenOptions()
- val ai = ArgIterator(expandAtFiles(args))
+ val ai = ArgIterator.withAtFiles(args)
var allAnnotations = mutableSetOf<String>()
@@ -148,11 +149,6 @@
return name
}
- fun setLogFile(level: LogLevel, filename: String) {
- log.addFilePrinter(level, filename)
- log.i("$level log file: $filename")
- }
-
while (true) {
val arg = ai.nextArgOptional()
if (arg == null) {
@@ -161,33 +157,23 @@
// Define some shorthands...
fun nextArg(): String = ai.nextArgRequired(arg)
- fun SetOnce<String>.setNextStringArg(): String = nextArg().also { this.set(it) }
- fun SetOnce<String?>.setNextStringArg(): String = nextArg().also { this.set(it) }
fun MutableSet<String>.addUniqueAnnotationArg(): String =
nextArg().also { this += ensureUniqueAnnotation(it) }
- fun SetOnce<Int>.setNextIntArg(): String = nextArg().also {
- try {
- this.set(it.toInt())
- } catch (e: NumberFormatException) {
- throw ArgumentsException("Invalid integer for $arg: $it")
- }
- }
+ if (log.maybeHandleCommandLineArg(arg) { nextArg() }) {
+ continue
+ }
try {
when (arg) {
// TODO: Write help
"-h", "--help" -> TODO("Help is not implemented yet")
- "-v", "--verbose" -> log.setConsoleLogLevel(LogLevel.Verbose)
- "-d", "--debug" -> log.setConsoleLogLevel(LogLevel.Debug)
- "-q", "--quiet" -> log.setConsoleLogLevel(LogLevel.None)
-
- "--in-jar" -> ret.inJar.setNextStringArg().ensureFileExists()
- "--out-stub-jar" -> ret.outStubJar.setNextStringArg()
- "--out-impl-jar" -> ret.outImplJar.setNextStringArg()
+ "--in-jar" -> ret.inJar.set(nextArg()).ensureFileExists()
+ "--out-stub-jar" -> ret.outStubJar.set(nextArg())
+ "--out-impl-jar" -> ret.outImplJar.set(nextArg())
"--policy-override-file" ->
- ret.policyOverrideFile.setNextStringArg().ensureFileExists()
+ ret.policyOverrideFile.set(nextArg())!!.ensureFileExists()
"--clean-up-on-error" -> ret.cleanUpOnError.set(true)
"--no-clean-up-on-error" -> ret.cleanUpOnError.set(false)
@@ -231,19 +217,19 @@
ret.packageRedirects += parsePackageRedirect(nextArg())
"--annotation-allowed-classes-file" ->
- ret.annotationAllowedClassesFile.setNextStringArg()
+ ret.annotationAllowedClassesFile.set(nextArg())
"--default-class-load-hook" ->
- ret.defaultClassLoadHook.setNextStringArg()
+ ret.defaultClassLoadHook.set(nextArg())
"--default-method-call-hook" ->
- ret.defaultMethodCallHook.setNextStringArg()
+ ret.defaultMethodCallHook.set(nextArg())
"--intersect-stub-jar" ->
ret.intersectStubJars += nextArg().ensureFileExists()
"--gen-keep-all-file" ->
- ret.inputJarAsKeepAllFile.setNextStringArg()
+ ret.inputJarAsKeepAllFile.set(nextArg())
// Following options are for debugging.
"--enable-class-checker" -> ret.enableClassChecker.set(true)
@@ -261,16 +247,21 @@
"--no-non-stub-method-check" ->
ret.enableNonStubMethodCallDetection.set(false)
- "--gen-input-dump-file" -> ret.inputJarDumpFile.setNextStringArg()
+ "--gen-input-dump-file" -> ret.inputJarDumpFile.set(nextArg())
- "--verbose-log" -> setLogFile(LogLevel.Verbose, nextArg())
- "--debug-log" -> setLogFile(LogLevel.Debug, nextArg())
+ "--stats-file" -> ret.statsFile.set(nextArg())
+ "--supported-api-list-file" -> ret.apiListFile.set(nextArg())
- "--stats-file" -> ret.statsFile.setNextStringArg()
- "--supported-api-list-file" -> ret.apiListFile.setNextStringArg()
-
- "--num-shards" -> ret.numShards.setNextIntArg()
- "--shard-index" -> ret.shard.setNextIntArg()
+ "--num-shards" -> ret.numShards.set(nextArg()).also {
+ if (it < 1) {
+ throw ArgumentsException("$arg must be positive integer")
+ }
+ }
+ "--shard-index" -> ret.shard.set(nextArg()).also {
+ if (it < 0) {
+ throw ArgumentsException("$arg must be positive integer or zero")
+ }
+ }
else -> throw ArgumentsException("Unknown option: $arg")
}
@@ -286,6 +277,15 @@
log.w("Neither --out-stub-jar nor --out-impl-jar is set." +
" $executableName will not generate jar files.")
}
+ if (ret.numShards.isSet != ret.shard.isSet) {
+ throw ArgumentsException("--num-shards and --shard-index must be used together")
+ }
+
+ if (ret.numShards.isSet) {
+ if (ret.shard.get >= ret.numShards.get) {
+ throw ArgumentsException("--shard-index must be smaller than --num-shards")
+ }
+ }
if (ret.enableNonStubMethodCallDetection.get) {
log.w("--enable-non-stub-method-check is not fully implemented yet." +
@@ -294,87 +294,6 @@
return ret
}
-
- /**
- * Scan the arguments, and if any of them starts with an `@`, then load from the file
- * and use its content as arguments.
- *
- * In this file, each line is treated as a single argument.
- *
- * The file can contain '#' as comments.
- */
- private fun expandAtFiles(args: Array<String>): List<String> {
- val ret = mutableListOf<String>()
-
- args.forEach { arg ->
- if (!arg.startsWith('@')) {
- ret += arg
- return@forEach
- }
- // Read from the file, and add each line to the result.
- val filename = arg.substring(1).ensureFileExists()
-
- log.v("Expanding options file $filename")
-
- BufferedReader(FileReader(filename)).use { reader ->
- while (true) {
- var line = reader.readLine()
- if (line == null) {
- break // EOF
- }
-
- line = normalizeTextLine(line)
- if (line.isNotEmpty()) {
- ret += line
- }
- }
- }
- }
- return ret
- }
- }
-
- open class ArgumentsException(message: String?) : Exception(message), UserErrorException
-
- /** Thrown when the same annotation is used with different annotation arguments. */
- class DuplicateAnnotationException(annotationName: String?) :
- ArgumentsException("Duplicate annotation specified: '$annotationName'")
-
- /** Thrown when an input file does not exist. */
- class InputFileNotFoundException(filename: String) :
- ArgumentsException("File '$filename' not found")
-
- private class ArgIterator(
- private val args: List<String>,
- private var currentIndex: Int = -1
- ) {
- val current: String
- get() = args.get(currentIndex)
-
- /**
- * Get the next argument, or [null] if there's no more arguments.
- */
- fun nextArgOptional(): String? {
- if ((currentIndex + 1) >= args.size) {
- return null
- }
- return args.get(++currentIndex)
- }
-
- /**
- * Get the next argument, or throw if
- */
- fun nextArgRequired(argName: String): String {
- nextArgOptional().let {
- if (it == null) {
- throw ArgumentsException("Missing parameter for option $argName")
- }
- if (it.isEmpty()) {
- throw ArgumentsException("Parameter can't be empty for option $argName")
- }
- return it
- }
- }
}
override fun toString(): String {
@@ -415,3 +334,80 @@
""".trimIndent()
}
}
+
+class ArgIterator(
+ private val args: List<String>,
+ private var currentIndex: Int = -1
+) {
+ val current: String
+ get() = args.get(currentIndex)
+
+ /**
+ * Get the next argument, or [null] if there's no more arguments.
+ */
+ fun nextArgOptional(): String? {
+ if ((currentIndex + 1) >= args.size) {
+ return null
+ }
+ return args.get(++currentIndex)
+ }
+
+ /**
+ * Get the next argument, or throw if
+ */
+ fun nextArgRequired(argName: String): String {
+ nextArgOptional().let {
+ if (it == null) {
+ throw ArgumentsException("Missing parameter for option $argName")
+ }
+ if (it.isEmpty()) {
+ throw ArgumentsException("Parameter can't be empty for option $argName")
+ }
+ return it
+ }
+ }
+
+ companion object {
+ fun withAtFiles(args: Array<String>): ArgIterator {
+ return ArgIterator(expandAtFiles(args))
+ }
+ }
+}
+
+/**
+ * Scan the arguments, and if any of them starts with an `@`, then load from the file
+ * and use its content as arguments.
+ *
+ * In this file, each line is treated as a single argument.
+ *
+ * The file can contain '#' as comments.
+ */
+private fun expandAtFiles(args: Array<String>): List<String> {
+ val ret = mutableListOf<String>()
+
+ args.forEach { arg ->
+ if (!arg.startsWith('@')) {
+ ret += arg
+ return@forEach
+ }
+ // Read from the file, and add each line to the result.
+ val filename = arg.substring(1).ensureFileExists()
+
+ log.v("Expanding options file $filename")
+
+ BufferedReader(FileReader(filename)).use { reader ->
+ while (true) {
+ var line = reader.readLine()
+ if (line == null) {
+ break // EOF
+ }
+
+ line = normalizeTextLine(line)
+ if (line.isNotEmpty()) {
+ ret += line
+ }
+ }
+ }
+ }
+ return ret
+}
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/AsmUtils.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/AsmUtils.kt
index f219dac..6cf2143 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/AsmUtils.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/AsmUtils.kt
@@ -58,7 +58,24 @@
return null
}
-fun findAnnotationValueAsString(an: AnnotationNode, propertyName: String): String? {
+fun ClassNode.findAnyAnnotation(set: Set<String>): AnnotationNode? {
+ return findAnyAnnotation(set, this.visibleAnnotations, this.invisibleAnnotations)
+}
+
+fun MethodNode.findAnyAnnotation(set: Set<String>): AnnotationNode? {
+ return findAnyAnnotation(set, this.visibleAnnotations, this.invisibleAnnotations)
+}
+
+fun FieldNode.findAnyAnnotation(set: Set<String>): AnnotationNode? {
+ return findAnyAnnotation(set, this.visibleAnnotations, this.invisibleAnnotations)
+}
+
+fun <T> findAnnotationValueAsObject(
+ an: AnnotationNode,
+ propertyName: String,
+ expectedTypeHumanReadableName: String,
+ converter: (Any?) -> T?,
+): T? {
for (i in 0..(an.values?.size ?: 0) - 2 step 2) {
val name = an.values[i]
@@ -66,16 +83,30 @@
continue
}
val value = an.values[i + 1]
- if (value is String) {
- return value
+ if (value == null) {
+ return null
}
- throw ClassParseException(
- "The type of '$name' in annotation \"${an.desc}\" must be String" +
- ", but is ${value?.javaClass?.canonicalName}")
+
+ try {
+ return converter(value)
+ } catch (e: ClassCastException) {
+ throw ClassParseException(
+ "The type of '$propertyName' in annotation @${an.desc} must be " +
+ "$expectedTypeHumanReadableName, but is ${value?.javaClass?.canonicalName}")
+ }
}
return null
}
+fun findAnnotationValueAsString(an: AnnotationNode, propertyName: String): String? {
+ return findAnnotationValueAsObject(an, propertyName, "String", {it as String})
+}
+
+fun findAnnotationValueAsType(an: AnnotationNode, propertyName: String): Type? {
+ return findAnnotationValueAsObject(an, propertyName, "Class", {it as Type})
+}
+
+
val periodOrSlash = charArrayOf('.', '/')
fun getPackageNameFromFullClassName(fullClassName: String): String {
@@ -125,6 +156,24 @@
return Pair(name.substring(0, pos), name.substring(pos + 1))
}
+fun String.startsWithAny(vararg prefixes: String): Boolean {
+ prefixes.forEach {
+ if (this.startsWith(it)) {
+ return true
+ }
+ }
+ return false
+}
+
+fun String.endsWithAny(vararg suffixes: String): Boolean {
+ suffixes.forEach {
+ if (this.endsWith(it)) {
+ return true
+ }
+ }
+ return false
+}
+
fun String.toJvmClassName(): String {
return this.replace('.', '/')
}
@@ -137,6 +186,14 @@
return this.replace('/', '.')
}
+fun zipEntryNameToClassName(entryFilename: String): String? {
+ val suffix = ".class"
+ if (!entryFilename.endsWith(suffix)) {
+ return null
+ }
+ return entryFilename.substring(0, entryFilename.length - suffix.length)
+}
+
private val numericalInnerClassName = """.*\$\d+$""".toRegex()
fun isAnonymousInnerClass(cn: ClassNode): Boolean {
@@ -278,6 +335,14 @@
return (this.access and Opcodes.ACC_STATIC) != 0
}
+fun MethodNode.isPublic(): Boolean {
+ return (this.access and Opcodes.ACC_PUBLIC) != 0
+}
+
+fun MethodNode.isSpecial(): Boolean {
+ return CTOR_NAME == this.name || CLASS_INITIALIZER_NAME == this.name
+}
+
fun FieldNode.isEnum(): Boolean {
return (this.access and Opcodes.ACC_ENUM) != 0
}
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/ClassNodes.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/ClassNodes.kt
index 2607df6..e2647eb 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/ClassNodes.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/ClassNodes.kt
@@ -27,6 +27,11 @@
import java.io.BufferedInputStream
import java.io.PrintWriter
import java.util.Arrays
+import java.util.concurrent.Executors
+import java.util.concurrent.TimeUnit
+import java.util.concurrent.atomic.AtomicReference
+import java.util.function.Consumer
+import java.util.zip.ZipEntry
import java.util.zip.ZipFile
/**
@@ -183,10 +188,43 @@
/**
* Load all the classes, without code.
*/
- fun loadClassStructures(inJar: String): ClassNodes {
- log.iTime("Reading class structure from $inJar") {
- val allClasses = ClassNodes()
+ fun loadClassStructures(
+ inJar: String,
+ timeCollector: Consumer<Double>? = null,
+ ): ClassNodes {
+ val allClasses = ClassNodes()
+ // Load classes in parallel.
+ val executor = Executors.newFixedThreadPool(4)
+
+ // First exception defected.
+ val exception = AtomicReference<Throwable>()
+
+ // Called on a BG thread. Read a single jar entry and add it to [allClasses].
+ fun parseClass(inZip: ZipFile, entry: ZipEntry) {
+ try {
+ inZip.getInputStream(entry).use { ins ->
+ val cr = ClassReader(BufferedInputStream(ins))
+ val cn = ClassNode()
+ cr.accept(
+ cn, ClassReader.SKIP_CODE
+ or ClassReader.SKIP_DEBUG
+ or ClassReader.SKIP_FRAMES
+ )
+ synchronized(allClasses) {
+ if (!allClasses.addClass(cn)) {
+ log.w("Duplicate class found: ${cn.name}")
+ }
+ }
+ }
+ } catch (e: Throwable) {
+ log.e("Failed to load class: $e")
+ exception.compareAndSet(null, e)
+ }
+ }
+
+ // Actually open the jar and read it on worker threads.
+ val time = log.iTime("Reading class structure from $inJar") {
log.withIndent {
ZipFile(inJar).use { inZip ->
val inEntries = inZip.entries()
@@ -194,40 +232,42 @@
while (inEntries.hasMoreElements()) {
val entry = inEntries.nextElement()
- BufferedInputStream(inZip.getInputStream(entry)).use { bis ->
- if (entry.name.endsWith(".class")) {
- val cr = ClassReader(bis)
- val cn = ClassNode()
- cr.accept(
- cn, ClassReader.SKIP_CODE
- or ClassReader.SKIP_DEBUG
- or ClassReader.SKIP_FRAMES
- )
- if (!allClasses.addClass(cn)) {
- log.w("Duplicate class found: ${cn.name}")
- }
- } else if (entry.name.endsWith(".dex")) {
- // Seems like it's an ART jar file. We can't process it.
- // It's a fatal error.
- throw InvalidJarFileException(
- "$inJar is not a desktop jar file."
- + " It contains a *.dex file."
- )
- } else {
- // Unknown file type. Skip.
- while (bis.available() > 0) {
- bis.skip((1024 * 1024).toLong())
- }
+ if (entry.name.endsWith(".class")) {
+ executor.submit {
+ parseClass(inZip, entry)
}
+ } else if (entry.name.endsWith(".dex")) {
+ // Seems like it's an ART jar file. We can't process it.
+ // It's a fatal error.
+ throw InvalidJarFileException(
+ "$inJar is not a desktop jar file."
+ + " It contains a *.dex file."
+ )
+ } else {
+ // Unknown file type. Skip.
}
}
+ // Wait for all the work to complete. (must do it before closing the zip)
+ log.i("Waiting for all loaders to finish...")
+ executor.shutdown()
+ executor.awaitTermination(5, TimeUnit.MINUTES)
+ log.i("All loaders to finished.")
}
}
+
+ // If any exception is detected, throw it.
+ exception.get()?.let {
+ throw it
+ }
+
if (allClasses.size == 0) {
log.w("$inJar contains no *.class files.")
+ } else {
+ log.i("Loaded ${allClasses.size} classes from $inJar.")
}
- return allClasses
}
+ timeCollector?.accept(time)
+ return allClasses
}
}
}
\ No newline at end of file
diff --git a/tools/protologtool/src/com/android/protolog/tool/ProtoLogCallProcessorImpl.kt b/tools/protologtool/src/com/android/protolog/tool/ProtoLogCallProcessorImpl.kt
index 3c99e68..c3595b7 100644
--- a/tools/protologtool/src/com/android/protolog/tool/ProtoLogCallProcessorImpl.kt
+++ b/tools/protologtool/src/com/android/protolog/tool/ProtoLogCallProcessorImpl.kt
@@ -120,7 +120,7 @@
logCallVisitor?.processCall(call, messageString, getLevelForMethodName(
call.name.toString(), call, context), groupMap.getValue(groupName))
- } else if (call.name.id == "initialize") {
+ } else if (call.name.id == "init") {
// No processing
} else {
// Process non-log message calls