summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/android/os/StrictMode.java28
-rw-r--r--core/java/android/view/ViewGroup.java7
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/common/RawFoldingFeatureProducer.java2
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java43
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/util/AcceptOnceConsumer.java2
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/util/BaseDataProducer.java21
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/util/DataProducer.java44
-rw-r--r--libs/WindowManager/Shell/res/values/config.xml4
-rw-r--r--libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java11
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/apptoweb/AppToWebGenericLinksParser.kt96
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java16
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java17
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java73
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt60
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolder.kt27
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apptoweb/AppToWebGenericLinksParserTests.kt131
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt33
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java134
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt2
-rw-r--r--packages/PackageInstaller/TEST_MAPPING3
-rw-r--r--packages/SettingsLib/res/values/strings.xml6
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ContentListState.kt6
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt11
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt4
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt92
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractorTest.kt109
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/log/CommunalMetricsLoggerTest.kt93
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt49
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt63
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartableTest.kt76
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt29
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt19
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractor.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractor.kt15
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/shared/log/CommunalMetricsLogger.kt89
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/shared/log/CommunalUiEvent.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalWidgetContentModel.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt26
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt24
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt23
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt29
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java13
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt57
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractorKosmos.kt4
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorKosmos.kt4
-rw-r--r--services/core/java/com/android/server/audio/AudioService.java1
-rw-r--r--services/core/java/com/android/server/hdmi/OneTouchPlayAction.java11
-rw-r--r--services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java30
-rw-r--r--services/core/java/com/android/server/inputmethod/InputMethodManagerService.java164
-rw-r--r--services/core/java/com/android/server/location/contexthub/ContextHubTestModeManager.java74
-rw-r--r--services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java45
-rw-r--r--services/core/java/com/android/server/pm/TEST_MAPPING3
-rw-r--r--services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java9
-rw-r--r--services/permission/java/com/android/server/permission/access/util/AtomicFileExtensions.kt8
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/OneTouchPlayActionTest.java118
-rw-r--r--tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/PackageFilter.kt61
-rw-r--r--tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt33
-rw-r--r--tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/utils/Trie.kt58
-rw-r--r--tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt92
-rw-r--r--tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/02-hoststubgen-test-tiny-framework-host-stub-dump.txt32
-rw-r--r--tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/03-hoststubgen-test-tiny-framework-host-impl-dump.txt32
-rw-r--r--tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/12-hoststubgen-test-tiny-framework-host-ext-stub-dump.txt32
-rw-r--r--tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/13-hoststubgen-test-tiny-framework-host-ext-impl-dump.txt50
-rw-r--r--tools/hoststubgen/hoststubgen/test-tiny-framework/policy-override-tiny-framework.txt8
-rw-r--r--tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/packagetest/A.java19
-rw-r--r--tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/packagetest/B.java19
-rw-r--r--tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/packagetest/sub/A.java19
-rw-r--r--tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/packagetest/sub/B.java19
-rw-r--r--tools/hoststubgen/hoststubgen/test/com/android/hoststubgen/utils/TrieTest.kt47
75 files changed, 2042 insertions, 596 deletions
diff --git a/core/java/android/os/StrictMode.java b/core/java/android/os/StrictMode.java
index 292e6bdba539..50b73a9d3f66 100644
--- a/core/java/android/os/StrictMode.java
+++ b/core/java/android/os/StrictMode.java
@@ -126,20 +126,18 @@ import java.util.function.Consumer;
* method:
*
* <pre>
- * public void onCreate() {
- * StrictMode.setThreadPolicy(new {@link ThreadPolicy.Builder StrictMode.ThreadPolicy.Builder}()
- * .detectDiskReads()
- * .detectDiskWrites()
- * .detectNetwork() // or .detectAll() for all detectable problems
- * .penaltyLog()
- * .build());
- * StrictMode.setVmPolicy(new {@link VmPolicy.Builder StrictMode.VmPolicy.Builder}()
- * .detectLeakedSqlLiteObjects()
- * .detectLeakedClosableObjects()
- * .penaltyLog()
- * .penaltyDeath()
- * .build());
- * super.onCreate();
+ * override fun onCreate(savedInstanceState: Bundle?) {
+ * super.onCreate(savedInstanceState)
+ * StrictMode.setThreadPolicy(
+ * StrictMode.ThreadPolicy.Builder()
+ * .detectAll()
+ * .build()
+ * )
+ * StrictMode.setVmPolicy(
+ * StrictMode.VmPolicy.Builder()
+ * .detectAll()
+ * .build()
+ * )
* }
* </pre>
*
@@ -354,7 +352,7 @@ public final class StrictMode {
public static final int NETWORK_POLICY_LOG = 1;
/** {@hide} */
public static final int NETWORK_POLICY_REJECT = 2;
-
+
/**
* Detect explicit calls to {@link Runtime#gc()}.
*/
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index b2c39b13525f..ceaca2257af4 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -2798,9 +2798,10 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
handled = true;
} else {
- final boolean cancelChild = resetCancelNextUpFlag(target.child)
- || intercepted;
- if (dispatchTransformedTouchEvent(ev, cancelChild,
+ final boolean cancelChild =
+ (target.child != null && resetCancelNextUpFlag(target.child))
+ || intercepted;
+ if (target.child != null && dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits)) {
handled = true;
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/common/RawFoldingFeatureProducer.java b/libs/WindowManager/Jetpack/src/androidx/window/common/RawFoldingFeatureProducer.java
index 8906e6d3d02e..88264f383153 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/common/RawFoldingFeatureProducer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/common/RawFoldingFeatureProducer.java
@@ -34,7 +34,7 @@ import java.util.Optional;
import java.util.function.Consumer;
/**
- * Implementation of {@link androidx.window.util.DataProducer} that produces a
+ * Implementation of {@link androidx.window.util.BaseDataProducer} that produces a
* {@link String} that can be parsed to a {@link CommonFoldingFeature}.
* {@link RawFoldingFeatureProducer} searches for the value in two places. The first check is in
* settings where the {@link String} property is saved with the key
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
index 859bc2cc40f3..84984a9f8c7b 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
@@ -31,10 +31,12 @@ import android.app.Application;
import android.app.WindowConfiguration;
import android.content.ComponentCallbacks;
import android.content.Context;
+import android.content.ContextWrapper;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.IBinder;
+import android.os.StrictMode;
import android.util.ArrayMap;
import android.util.Log;
@@ -136,14 +138,23 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent {
|| containsConsumer(consumer)) {
return;
}
+ final IllegalArgumentException exception = new IllegalArgumentException(
+ "Context must be a UI Context with display association, which should be"
+ + " an Activity, WindowContext or InputMethodService");
if (!context.isUiContext()) {
- throw new IllegalArgumentException("Context must be a UI Context, which should be"
- + " an Activity, WindowContext or InputMethodService");
+ throw exception;
}
if (context.getAssociatedDisplayId() == INVALID_DISPLAY) {
- Log.w(TAG, "The registered Context is a UI Context but not associated with any"
- + " display. This Context may not receive any WindowLayoutInfo update");
+ // This is to identify if #isUiContext of a non-UI Context is overridden.
+ // #isUiContext is more likely to be overridden than #getAssociatedDisplayId
+ // since #isUiContext is a public API.
+ StrictMode.onIncorrectContextUsed("The registered Context is a UI Context "
+ + "but not associated with any display. "
+ + "This Context may not receive any WindowLayoutInfo update. "
+ + dumpAllBaseContextToString(context), exception);
}
+ Log.d(TAG, "Register WindowLayoutInfoListener on "
+ + dumpAllBaseContextToString(context));
mFoldingFeatureProducer.getData((features) -> {
WindowLayoutInfo newWindowLayout = getWindowLayoutInfo(context, features);
consumer.accept(newWindowLayout);
@@ -162,6 +173,16 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent {
}
}
+ @NonNull
+ private String dumpAllBaseContextToString(@NonNull Context context) {
+ final StringBuilder builder = new StringBuilder("Context=" + context);
+ while ((context instanceof ContextWrapper wrapper) && wrapper.getBaseContext() != null) {
+ context = wrapper.getBaseContext();
+ builder.append(", of which baseContext=").append(context);
+ }
+ return builder.toString();
+ }
+
@Override
public void removeWindowLayoutInfoListener(
@NonNull java.util.function.Consumer<WindowLayoutInfo> consumer) {
@@ -417,9 +438,19 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent {
*/
private boolean shouldReportDisplayFeatures(@NonNull @UiContext Context context) {
int displayId = context.getAssociatedDisplayId();
+ if (!context.isUiContext() || displayId == INVALID_DISPLAY) {
+ // This could happen if a caller sets MutableContextWrapper's base Context to a non-UI
+ // Context.
+ StrictMode.onIncorrectContextUsed("Context is not a UI Context anymore."
+ + " Was the base context changed? It's suggested to unregister"
+ + " the windowLayoutInfo callback before changing the base Context."
+ + " UI Contexts are Activity, InputMethodService or context created"
+ + " with createWindowContext. " + dumpAllBaseContextToString(context),
+ new UnsupportedOperationException("Context is not a UI Context anymore."
+ + " Was the base context changed?"));
+ }
if (displayId != DEFAULT_DISPLAY) {
- // Display features are not supported on secondary displays or the context is not
- // associated with any display.
+ // Display features are not supported on secondary displays.
return false;
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/util/AcceptOnceConsumer.java b/libs/WindowManager/Jetpack/src/androidx/window/util/AcceptOnceConsumer.java
index fe60037483c4..63828ab2e62b 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/util/AcceptOnceConsumer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/util/AcceptOnceConsumer.java
@@ -23,7 +23,7 @@ import java.util.function.Consumer;
/**
* A base class that works with {@link BaseDataProducer} to add/remove a consumer that should
* only be used once when {@link BaseDataProducer#notifyDataChanged} is called.
- * @param <T> The type of data this producer returns through {@link DataProducer#getData}.
+ * @param <T> The type of data this producer returns through {@link BaseDataProducer#getData}.
*/
public class AcceptOnceConsumer<T> implements Consumer<T> {
private final Consumer<T> mCallback;
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/util/BaseDataProducer.java b/libs/WindowManager/Jetpack/src/androidx/window/util/BaseDataProducer.java
index de52f0969fa8..cd26efd4fdb6 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/util/BaseDataProducer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/util/BaseDataProducer.java
@@ -26,13 +26,12 @@ import java.util.Set;
import java.util.function.Consumer;
/**
- * Base class that provides the implementation for the callback mechanism of the
- * {@link DataProducer} API. This class is thread safe for adding, removing, and notifying
- * consumers.
+ * Base class that manages listeners when listening to a piece of data that changes. This class is
+ * thread safe for adding, removing, and notifying consumers.
*
- * @param <T> The type of data this producer returns through {@link DataProducer#getData}.
+ * @param <T> The type of data this producer returns through {@link BaseDataProducer#getData}.
*/
-public abstract class BaseDataProducer<T> implements DataProducer<T>,
+public abstract class BaseDataProducer<T> implements
AcceptOnceConsumer.AcceptOnceProducerCallback<T> {
private final Object mLock = new Object();
@@ -42,12 +41,17 @@ public abstract class BaseDataProducer<T> implements DataProducer<T>,
private final Set<Consumer<T>> mCallbacksToRemove = new HashSet<>();
/**
+ * Emits the first available data at that point in time.
+ * @param dataConsumer a {@link Consumer} that will receive one value.
+ */
+ public abstract void getData(@NonNull Consumer<T> dataConsumer);
+
+ /**
* Adds a callback to the set of callbacks listening for data. Data is delivered through
* {@link BaseDataProducer#notifyDataChanged(Object)}. This method is thread safe. Callers
* should ensure that callbacks are thread safe.
* @param callback that will receive data from the producer.
*/
- @Override
public final void addDataChangedCallback(@NonNull Consumer<T> callback) {
synchronized (mLock) {
mCallbacks.add(callback);
@@ -63,7 +67,6 @@ public abstract class BaseDataProducer<T> implements DataProducer<T>,
* @param callback that was registered in
* {@link BaseDataProducer#addDataChangedCallback(Consumer)}.
*/
- @Override
public final void removeDataChangedCallback(@NonNull Consumer<T> callback) {
synchronized (mLock) {
mCallbacks.remove(callback);
@@ -92,8 +95,8 @@ public abstract class BaseDataProducer<T> implements DataProducer<T>,
/**
* Called to notify all registered consumers that the data provided
- * by {@link DataProducer#getData} has changed. Calls to this are thread save but callbacks need
- * to ensure thread safety.
+ * by {@link BaseDataProducer#getData} has changed. Calls to this are thread save but callbacks
+ * need to ensure thread safety.
*/
protected void notifyDataChanged(T value) {
synchronized (mLock) {
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/util/DataProducer.java b/libs/WindowManager/Jetpack/src/androidx/window/util/DataProducer.java
deleted file mode 100644
index ec301dc34aaa..000000000000
--- a/libs/WindowManager/Jetpack/src/androidx/window/util/DataProducer.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright (C) 2021 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 androidx.window.util;
-
-import android.annotation.NonNull;
-
-import java.util.function.Consumer;
-
-/**
- * Produces data through {@link DataProducer#getData} and provides a mechanism for receiving
- * a callback when the data managed by the produces has changed.
- *
- * @param <T> The type of data this producer returns through {@link DataProducer#getData}.
- */
-public interface DataProducer<T> {
- /**
- * Emits the first available data at that point in time.
- * @param dataConsumer a {@link Consumer} that will receive one value.
- */
- void getData(@NonNull Consumer<T> dataConsumer);
-
- /**
- * Adds a callback to be notified when the data returned
- * from {@link DataProducer#getData} has changed.
- */
- void addDataChangedCallback(@NonNull Consumer<T> callback);
-
- /** Removes a callback previously added with {@link #addDataChangedCallback(Consumer)}. */
- void removeDataChangedCallback(@NonNull Consumer<T> callback);
-}
diff --git a/libs/WindowManager/Shell/res/values/config.xml b/libs/WindowManager/Shell/res/values/config.xml
index c2ba064ac7b6..39f6d8c46a24 100644
--- a/libs/WindowManager/Shell/res/values/config.xml
+++ b/libs/WindowManager/Shell/res/values/config.xml
@@ -179,4 +179,8 @@
<!-- Whether pointer pilfer is required to start back animation. -->
<bool name="config_backAnimationRequiresPointerPilfer">true</bool>
+
+ <!-- This is to be overridden to define a list of packages mapped to web links which will be
+ parsed and utilized for desktop windowing's app-to-web feature. -->
+ <string name="generic_links_list" translatable="false"/>
</resources>
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java
index fc4710f8f67b..a1ba24ca366f 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java
@@ -70,6 +70,10 @@ public class DesktopModeStatus {
private static final boolean ENFORCE_DEVICE_RESTRICTIONS = SystemProperties.getBoolean(
"persist.wm.debug.desktop_mode_enforce_device_restrictions", true);
+ private static final boolean USE_APP_TO_WEB_BUILD_TIME_GENERIC_LINKS =
+ SystemProperties.getBoolean(
+ "persist.wm.debug.use_app_to_web_build_time_generic_links", true);
+
/** Whether the desktop density override is enabled. */
public static final boolean DESKTOP_DENSITY_OVERRIDE_ENABLED =
SystemProperties.getBoolean("persist.wm.debug.desktop_mode_density_enabled", false);
@@ -176,6 +180,13 @@ public class DesktopModeStatus {
}
/**
+ * Returns {@code true} if the app-to-web feature is using the build-time generic links list.
+ */
+ public static boolean useAppToWebBuildTimeGenericLinks() {
+ return USE_APP_TO_WEB_BUILD_TIME_GENERIC_LINKS;
+ }
+
+ /**
* Return {@code true} if the override desktop density is enabled.
*/
private static boolean isDesktopDensityOverrideEnabled() {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/apptoweb/AppToWebGenericLinksParser.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/apptoweb/AppToWebGenericLinksParser.kt
new file mode 100644
index 000000000000..56447de6bae1
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/apptoweb/AppToWebGenericLinksParser.kt
@@ -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.wm.shell.apptoweb
+
+import android.content.Context
+import android.provider.DeviceConfig
+import android.webkit.URLUtil
+import com.android.internal.annotations.VisibleForTesting
+import com.android.wm.shell.R
+import com.android.wm.shell.common.ShellExecutor
+import com.android.wm.shell.shared.annotations.ShellMainThread
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus.useAppToWebBuildTimeGenericLinks
+
+/**
+ * Retrieves the build-time or server-side generic links list and parses and stores the
+ * package-to-url pairs.
+ */
+class AppToWebGenericLinksParser(
+ private val context: Context,
+ @ShellMainThread private val mainExecutor: ShellExecutor
+) {
+ private val genericLinksMap: MutableMap<String, String> = mutableMapOf()
+
+ init {
+ // If using the server-side generic links list, register a listener
+ if (!useAppToWebBuildTimeGenericLinks()) {
+ DeviceConfigListener()
+ }
+
+ updateGenericLinksMap()
+ }
+
+ /** Returns the generic link associated with the [packageName] or null if there is none. */
+ fun getGenericLink(packageName: String): String? = genericLinksMap[packageName]
+
+ private fun updateGenericLinksMap() {
+ val genericLinksList =
+ if (useAppToWebBuildTimeGenericLinks()) {
+ context.resources.getString(R.string.generic_links_list)
+ } else {
+ DeviceConfig.getString(NAMESPACE, FLAG_GENERIC_LINKS, /* defaultValue= */ "")
+ } ?: return
+
+ parseGenericLinkList(genericLinksList)
+ }
+
+ private fun parseGenericLinkList(genericLinksList: String) {
+ val newEntries =
+ genericLinksList
+ .split(" ")
+ .filter { it.contains(':') }
+ .map {
+ val (packageName, url) = it.split(':', limit = 2)
+ return@map packageName to url
+ }
+ .filter { URLUtil.isNetworkUrl(it.second) }
+
+ genericLinksMap.clear()
+ genericLinksMap.putAll(newEntries)
+ }
+
+ /**
+ * Listens for changes to the server-side generic links list and updates the package to url map
+ * if [DesktopModeStatus#useBuildTimeGenericLinkList()] is set to false.
+ */
+ inner class DeviceConfigListener : DeviceConfig.OnPropertiesChangedListener {
+ init {
+ DeviceConfig.addOnPropertiesChangedListener(NAMESPACE, mainExecutor, this)
+ }
+
+ override fun onPropertiesChanged(properties: DeviceConfig.Properties) {
+ if (properties.keyset.contains(FLAG_GENERIC_LINKS)) {
+ updateGenericLinksMap()
+ }
+ }
+ }
+
+ companion object {
+ private const val NAMESPACE = DeviceConfig.NAMESPACE_APP_COMPAT_OVERRIDES
+ @VisibleForTesting const val FLAG_GENERIC_LINKS = "generic_links_flag"
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index 700742acfb43..32526ff06abb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -35,6 +35,7 @@ import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.WindowManagerShellWrapper;
import com.android.wm.shell.activityembedding.ActivityEmbeddingController;
+import com.android.wm.shell.apptoweb.AppToWebGenericLinksParser;
import com.android.wm.shell.bubbles.BubbleController;
import com.android.wm.shell.bubbles.BubbleData;
import com.android.wm.shell.bubbles.BubbleDataRepository;
@@ -223,7 +224,8 @@ public abstract class WMShellModule {
Transitions transitions,
Optional<DesktopTasksController> desktopTasksController,
RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
- InteractionJankMonitor interactionJankMonitor) {
+ InteractionJankMonitor interactionJankMonitor,
+ AppToWebGenericLinksParser genericLinksParser) {
if (DesktopModeStatus.canEnterDesktopMode(context)) {
return new DesktopModeWindowDecorViewModel(
context,
@@ -242,7 +244,8 @@ public abstract class WMShellModule {
transitions,
desktopTasksController,
rootTaskDisplayAreaOrganizer,
- interactionJankMonitor);
+ interactionJankMonitor,
+ genericLinksParser);
}
return new CaptionWindowDecorViewModel(
context,
@@ -259,6 +262,15 @@ public abstract class WMShellModule {
transitions);
}
+ @WMSingleton
+ @Provides
+ static AppToWebGenericLinksParser provideGenericLinksParser(
+ Context context,
+ @ShellMainThread ShellExecutor mainExecutor
+ ) {
+ return new AppToWebGenericLinksParser(context, mainExecutor);
+ }
+
//
// Freeform
//
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index 0e8fd7c7d0a1..a77a76c4188b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -87,6 +87,7 @@ import com.android.window.flags.Flags;
import com.android.wm.shell.R;
import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.apptoweb.AppToWebGenericLinksParser;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayInsetsController;
import com.android.wm.shell.common.DisplayLayout;
@@ -162,6 +163,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
private final DesktopModeKeyguardChangeListener mDesktopModeKeyguardChangeListener =
new DesktopModeKeyguardChangeListener();
private final RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer;
+ private final AppToWebGenericLinksParser mGenericLinksParser;
private final DisplayInsetsController mDisplayInsetsController;
private final Region mExclusionRegion = Region.obtain();
private boolean mInImmersiveMode;
@@ -198,7 +200,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
Transitions transitions,
Optional<DesktopTasksController> desktopTasksController,
RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
- InteractionJankMonitor interactionJankMonitor
+ InteractionJankMonitor interactionJankMonitor,
+ AppToWebGenericLinksParser genericLinksParser
) {
this(
context,
@@ -216,6 +219,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
syncQueue,
transitions,
desktopTasksController,
+ genericLinksParser,
new DesktopModeWindowDecoration.Factory(),
new InputMonitorFactory(),
SurfaceControl.Transaction::new,
@@ -241,6 +245,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
SyncTransactionQueue syncQueue,
Transitions transitions,
Optional<DesktopTasksController> desktopTasksController,
+ AppToWebGenericLinksParser genericLinksParser,
DesktopModeWindowDecoration.Factory desktopModeWindowDecorFactory,
InputMonitorFactory inputMonitorFactory,
Supplier<SurfaceControl.Transaction> transactionFactory,
@@ -266,6 +271,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
mInputMonitorFactory = inputMonitorFactory;
mTransactionFactory = transactionFactory;
mRootTaskDisplayAreaOrganizer = rootTaskDisplayAreaOrganizer;
+ mGenericLinksParser = genericLinksParser;
mInputManager = mContext.getSystemService(InputManager.class);
mWindowDecorByTaskId = windowDecorByTaskId;
mSysUIPackageName = mContext.getResources().getString(
@@ -669,12 +675,6 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
View v, MotionEvent e) {
final int id = v.getId();
if (id == R.id.caption_handle) {
- if (e.getActionMasked() == MotionEvent.ACTION_DOWN) {
- // Caption handle is located within the status bar region, meaning the
- // DisplayPolicy will attempt to transfer this input to status bar if it's
- // a swipe down. Pilfer here to keep the gesture in handle alone.
- mInputManager.pilferPointers(v.getViewRootImpl().getInputToken());
- }
handleCaptionThroughStatusBar(e, decoration);
final boolean wasDragging = mIsDragging;
updateDragStatus(e.getActionMasked());
@@ -1157,7 +1157,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
mBgExecutor,
mMainChoreographer,
mSyncQueue,
- mRootTaskDisplayAreaOrganizer);
+ mRootTaskDisplayAreaOrganizer,
+ mGenericLinksParser);
mWindowDecorByTaskId.put(taskInfo.taskId, windowDecoration);
final DragPositioningCallback dragPositioningCallback;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
index 529def7ca3d7..a1cc6507c280 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
@@ -30,6 +30,7 @@ import static com.android.wm.shell.windowdecor.DragResizeWindowGeometry.getLarge
import static com.android.wm.shell.windowdecor.DragResizeWindowGeometry.getResizeEdgeHandleSize;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.app.ActivityManager;
import android.app.WindowConfiguration.WindowingMode;
@@ -49,8 +50,8 @@ import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Handler;
import android.os.Trace;
-import android.util.Log;
import android.util.Size;
+import android.util.Slog;
import android.view.Choreographer;
import android.view.MotionEvent;
import android.view.SurfaceControl;
@@ -68,6 +69,7 @@ import com.android.window.flags.Flags;
import com.android.wm.shell.R;
import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.apptoweb.AppToWebGenericLinksParser;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.ShellExecutor;
@@ -133,12 +135,15 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
private CharSequence mAppName;
private CapturedLink mCapturedLink;
+ private Uri mGenericLink;
private OpenInBrowserClickListener mOpenInBrowserClickListener;
private ExclusionRegionListener mExclusionRegionListener;
private final RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer;
private final MaximizeMenuFactory mMaximizeMenuFactory;
+ private final HandleMenuFactory mHandleMenuFactory;
+ private final AppToWebGenericLinksParser mGenericLinksParser;
// Hover state for the maximize menu and button. The menu will remain open as long as either of
// these is true. See {@link #onMaximizeHoverStateChanged()}.
@@ -161,13 +166,14 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
@ShellBackgroundThread ShellExecutor bgExecutor,
Choreographer choreographer,
SyncTransactionQueue syncQueue,
- RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer) {
+ RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
+ AppToWebGenericLinksParser genericLinksParser) {
this (context, displayController, splitScreenController, taskOrganizer, taskInfo,
taskSurface, handler, bgExecutor, choreographer, syncQueue,
- rootTaskDisplayAreaOrganizer, SurfaceControl.Builder::new,
+ rootTaskDisplayAreaOrganizer, genericLinksParser, SurfaceControl.Builder::new,
SurfaceControl.Transaction::new, WindowContainerTransaction::new,
SurfaceControl::new, new SurfaceControlViewHostFactory() {},
- DefaultMaximizeMenuFactory.INSTANCE);
+ DefaultMaximizeMenuFactory.INSTANCE, DefaultHandleMenuFactory.INSTANCE);
}
DesktopModeWindowDecoration(
@@ -182,12 +188,14 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
Choreographer choreographer,
SyncTransactionQueue syncQueue,
RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
+ AppToWebGenericLinksParser genericLinksParser,
Supplier<SurfaceControl.Builder> surfaceControlBuilderSupplier,
Supplier<SurfaceControl.Transaction> surfaceControlTransactionSupplier,
Supplier<WindowContainerTransaction> windowContainerTransactionSupplier,
Supplier<SurfaceControl> surfaceControlSupplier,
SurfaceControlViewHostFactory surfaceControlViewHostFactory,
- MaximizeMenuFactory maximizeMenuFactory) {
+ MaximizeMenuFactory maximizeMenuFactory,
+ HandleMenuFactory handleMenuFactory) {
super(context, displayController, taskOrganizer, taskInfo, taskSurface,
surfaceControlBuilderSupplier, surfaceControlTransactionSupplier,
windowContainerTransactionSupplier, surfaceControlSupplier,
@@ -198,7 +206,9 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
mChoreographer = choreographer;
mSyncQueue = syncQueue;
mRootTaskDisplayAreaOrganizer = rootTaskDisplayAreaOrganizer;
+ mGenericLinksParser = genericLinksParser;
mMaximizeMenuFactory = maximizeMenuFactory;
+ mHandleMenuFactory = handleMenuFactory;
}
/**
@@ -425,11 +435,23 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
}
void onOpenInBrowserClick() {
- if (mOpenInBrowserClickListener == null || mCapturedLink == null) return;
- mOpenInBrowserClickListener.onClick(this, mCapturedLink.mUri);
+ if (mOpenInBrowserClickListener == null || mHandleMenu == null) {
+ return;
+ }
+ mOpenInBrowserClickListener.onClick(this, mHandleMenu.getOpenInBrowserLink());
onCapturedLinkExpired();
}
+ @Nullable
+ private Uri getBrowserLink() {
+ // If the captured link is available and has not expired, return the captured link.
+ // Otherwise, return the generic link which is set to null if a generic link is unavailable.
+ if (mCapturedLink != null && !mCapturedLink.mExpired) {
+ return mCapturedLink.mUri;
+ }
+ return mGenericLink;
+ }
+
private void updateDragResizeListener(SurfaceControl oldDecorationSurface) {
if (!isDragResizable(mTaskInfo)) {
if (!mTaskInfo.positionInParent.equals(mPositionInParent)) {
@@ -520,11 +542,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
return new AppHandleViewHolder(
mResult.mRootView,
mOnCaptionTouchListener,
- mOnCaptionButtonClickListener,
- (v, event) -> {
- updateHoverAndPressStatus(event);
- return true;
- }
+ mOnCaptionButtonClickListener
);
} else if (mRelayoutParams.mLayoutResId
== R.layout.desktop_mode_app_header) {
@@ -589,9 +607,11 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
controlsElement.mWidthResId = R.dimen.desktop_mode_customizable_caption_margin_end;
controlsElement.mAlignment = RelayoutParams.OccludingCaptionElement.Alignment.END;
relayoutParams.mOccludingCaptionElements.add(controlsElement);
- } else if (isAppHandle) {
+ } else if (isAppHandle && !Flags.enableAdditionalWindowsAboveStatusBar()) {
// The focused decor (fullscreen/split) does not need to handle input because input in
// the App Handle is handled by the InputMonitor in DesktopModeWindowDecorViewModel.
+ // Note: This does not apply with the above flag enabled as the status bar input layer
+ // will forward events to the handle directly.
relayoutParams.mInputFeatures
|= WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL;
}
@@ -700,7 +720,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
}
final ComponentName baseActivity = mTaskInfo.baseActivity;
if (baseActivity == null) {
- Log.e(TAG, "Base activity component not found in task");
+ Slog.e(TAG, "Base activity component not found in task");
return;
}
final PackageManager pm = mContext.getApplicationContext().getPackageManager();
@@ -719,7 +739,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
final ApplicationInfo applicationInfo = activityInfo.applicationInfo;
mAppName = pm.getApplicationLabel(applicationInfo);
} catch (PackageManager.NameNotFoundException e) {
- Log.e(TAG, "Base activity's component name cannot be found on the system");
+ Slog.e(TAG, "Base activity's component name cannot be found on the system", e);
} finally {
Trace.endSection();
}
@@ -914,7 +934,8 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
*/
void createHandleMenu(SplitScreenController splitScreenController) {
loadAppInfoIfNeeded();
- mHandleMenu = new HandleMenu(
+ updateGenericLink();
+ mHandleMenu = mHandleMenuFactory.create(
this,
mRelayoutParams.mLayoutResId,
mOnCaptionButtonClickListener,
@@ -924,7 +945,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
mDisplayController,
splitScreenController,
DesktopModeStatus.canEnterDesktopMode(mContext),
- browserLinkAvailable(),
+ getBrowserLink(),
mResult.mCaptionWidth,
mResult.mCaptionHeight,
mResult.mCaptionX
@@ -933,9 +954,15 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
mHandleMenu.show();
}
- @VisibleForTesting
- boolean browserLinkAvailable() {
- return mCapturedLink != null && !mCapturedLink.mExpired;
+ private void updateGenericLink() {
+ final ComponentName baseActivity = mTaskInfo.baseActivity;
+ if (baseActivity == null) {
+ return;
+ }
+
+ final String genericLink =
+ mGenericLinksParser.getGenericLink(baseActivity.getPackageName());
+ mGenericLink = genericLink == null ? null : Uri.parse(genericLink);
}
/**
@@ -1219,7 +1246,8 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
@ShellBackgroundThread ShellExecutor bgExecutor,
Choreographer choreographer,
SyncTransactionQueue syncQueue,
- RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer) {
+ RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
+ AppToWebGenericLinksParser genericLinksParser) {
return new DesktopModeWindowDecoration(
context,
displayController,
@@ -1231,7 +1259,8 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
bgExecutor,
choreographer,
syncQueue,
- rootTaskDisplayAreaOrganizer);
+ rootTaskDisplayAreaOrganizer,
+ genericLinksParser);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt
index e174e83d11c9..32522c6411cb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt
@@ -27,6 +27,7 @@ import android.graphics.BlendModeColorFilter
import android.graphics.Point
import android.graphics.PointF
import android.graphics.Rect
+import android.net.Uri
import android.view.MotionEvent
import android.view.SurfaceControl
import android.view.View
@@ -68,7 +69,7 @@ class HandleMenu(
private val displayController: DisplayController,
private val splitScreenController: SplitScreenController,
private val shouldShowWindowingPill: Boolean,
- private val shouldShowBrowserPill: Boolean,
+ val openInBrowserLink: Uri?,
private val captionWidth: Int,
private val captionHeight: Int,
captionX: Int
@@ -106,6 +107,9 @@ class HandleMenu(
// those as well.
private val globalMenuPosition: Point = Point()
+ private val shouldShowBrowserPill: Boolean
+ get() = openInBrowserLink != null
+
init {
updateHandleMenuPillPositions(captionX)
}
@@ -497,3 +501,57 @@ class HandleMenu(
private const val SHOULD_SHOW_MORE_ACTIONS_PILL = false
}
}
+
+/** A factory interface to create a [HandleMenu]. */
+interface HandleMenuFactory {
+ fun create(
+ parentDecor: DesktopModeWindowDecoration,
+ layoutResId: Int,
+ onClickListener: View.OnClickListener?,
+ onTouchListener: View.OnTouchListener?,
+ appIconBitmap: Bitmap?,
+ appName: CharSequence?,
+ displayController: DisplayController,
+ splitScreenController: SplitScreenController,
+ shouldShowWindowingPill: Boolean,
+ openInBrowserLink: Uri?,
+ captionWidth: Int,
+ captionHeight: Int,
+ captionX: Int
+ ): HandleMenu
+}
+
+/** A [HandleMenuFactory] implementation that creates a [HandleMenu]. */
+object DefaultHandleMenuFactory : HandleMenuFactory {
+ override fun create(
+ parentDecor: DesktopModeWindowDecoration,
+ layoutResId: Int,
+ onClickListener: View.OnClickListener?,
+ onTouchListener: View.OnTouchListener?,
+ appIconBitmap: Bitmap?,
+ appName: CharSequence?,
+ displayController: DisplayController,
+ splitScreenController: SplitScreenController,
+ shouldShowWindowingPill: Boolean,
+ openInBrowserLink: Uri?,
+ captionWidth: Int,
+ captionHeight: Int,
+ captionX: Int
+ ): HandleMenu {
+ return HandleMenu(
+ parentDecor,
+ layoutResId,
+ onClickListener,
+ onTouchListener,
+ appIconBitmap,
+ appName,
+ displayController,
+ splitScreenController,
+ shouldShowWindowingPill,
+ openInBrowserLink,
+ captionWidth,
+ captionHeight,
+ captionX
+ )
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolder.kt
index 76dfe37c7cc0..57d8cac2d336 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolder.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolder.kt
@@ -21,10 +21,11 @@ import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
import android.content.res.ColorStateList
import android.graphics.Color
import android.graphics.Point
+import android.hardware.input.InputManager
+import android.view.MotionEvent.ACTION_DOWN
import android.view.SurfaceControl
import android.view.View
import android.view.View.OnClickListener
-import android.view.View.OnHoverListener
import android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS
import android.view.WindowManager
import android.widget.ImageButton
@@ -39,9 +40,8 @@ import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalSystem
*/
internal class AppHandleViewHolder(
rootView: View,
- private val onCaptionTouchListener: View.OnTouchListener,
- private val onCaptionButtonClickListener: OnClickListener,
- private val onCaptionHoverListener: OnHoverListener,
+ onCaptionTouchListener: View.OnTouchListener,
+ onCaptionButtonClickListener: OnClickListener
) : WindowDecorationViewHolder(rootView) {
companion object {
@@ -51,6 +51,7 @@ internal class AppHandleViewHolder(
private val windowManager = context.getSystemService(WindowManager::class.java)
private val captionView: View = rootView.requireViewById(R.id.desktop_mode_caption)
private val captionHandle: ImageButton = rootView.requireViewById(R.id.caption_handle)
+ private val inputManager = context.getSystemService(InputManager::class.java)
// An invisible View that takes up the same coordinates as captionHandle but is layered
// above the status bar. The purpose of this View is to receive input intended for
@@ -61,7 +62,6 @@ internal class AppHandleViewHolder(
captionView.setOnTouchListener(onCaptionTouchListener)
captionHandle.setOnTouchListener(onCaptionTouchListener)
captionHandle.setOnClickListener(onCaptionButtonClickListener)
- captionHandle.setOnHoverListener(onCaptionHoverListener)
}
override fun bindData(
@@ -106,10 +106,19 @@ internal class AppHandleViewHolder(
// gesture listener that receives events before window. This is to prevent notification
// shade gesture when we swipe down to enter desktop.
lp.inputFeatures = WindowManager.LayoutParams.INPUT_FEATURE_SPY
- view.id = R.id.caption_handle
- view.setOnClickListener(onCaptionButtonClickListener)
- view.setOnTouchListener(onCaptionTouchListener)
- view.setOnHoverListener(onCaptionHoverListener)
+ view.setOnHoverListener { _, event ->
+ captionHandle.onHoverEvent(event)
+ }
+ // Caption handle is located within the status bar region, meaning the
+ // DisplayPolicy will attempt to transfer this input to status bar if it's
+ // a swipe down. Pilfer here to keep the gesture in handle alone.
+ view.setOnTouchListener { v, event ->
+ if (event.actionMasked == ACTION_DOWN) {
+ inputManager.pilferPointers(v.viewRootImpl.inputToken)
+ }
+ captionHandle.dispatchTouchEvent(event)
+ true
+ }
windowManager.updateViewLayout(view, lp)
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apptoweb/AppToWebGenericLinksParserTests.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apptoweb/AppToWebGenericLinksParserTests.kt
new file mode 100644
index 000000000000..053027fc1cf4
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apptoweb/AppToWebGenericLinksParserTests.kt
@@ -0,0 +1,131 @@
+/*
+ * 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.apptoweb
+
+import android.provider.DeviceConfig
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.testing.TestableResources
+import androidx.test.filters.SmallTest
+import com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn
+import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession
+import com.android.dx.mockito.inline.extended.StaticMockitoSession
+import com.android.wm.shell.R
+import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.common.ShellExecutor
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
+import com.android.wm.shell.apptoweb.AppToWebGenericLinksParser.Companion.FLAG_GENERIC_LINKS
+import junit.framework.TestCase.assertEquals
+import junit.framework.TestCase.assertNull
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+import org.mockito.quality.Strictness
+
+/**
+ * Tests for [AppToWebGenericLinksParser].
+ *
+ * Build/Install/Run: atest WMShellUnitTests:AppToWebGenericLinksParserTests
+ */
+@SmallTest
+@TestableLooper.RunWithLooper
+@RunWith(AndroidTestingRunner::class)
+class AppToWebGenericLinksParserTests : ShellTestCase() {
+ @Mock private lateinit var mockExecutor: ShellExecutor
+
+ private lateinit var genericLinksParser: AppToWebGenericLinksParser
+ private lateinit var mockitoSession: StaticMockitoSession
+ private lateinit var resources: TestableResources
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+
+ mockitoSession =
+ mockitoSession()
+ .strictness(Strictness.LENIENT)
+ .spyStatic(DesktopModeStatus::class.java)
+ .startMocking()
+ resources = mContext.getOrCreateTestableResources()
+ resources.addOverride(R.string.generic_links_list, BUILD_TIME_LIST)
+ DeviceConfig.setProperty(
+ NAMESPACE,
+ FLAG_GENERIC_LINKS,
+ SERVER_SIDE_LIST,
+ false /* makeDefault */
+ )
+ }
+
+ @After
+ fun tearDown() {
+ mockitoSession.finishMocking()
+ }
+
+ @Test
+ fun init_usingBuildTimeList() {
+ doReturn(true).`when` { DesktopModeStatus.useAppToWebBuildTimeGenericLinks() }
+ genericLinksParser = AppToWebGenericLinksParser(mContext, mockExecutor)
+ // Assert build-time list correctly parsed
+ assertEquals(URL_B, genericLinksParser.getGenericLink(PACKAGE_NAME_1))
+ }
+
+ @Test
+ fun init_usingServerSideList() {
+ doReturn(false).`when` { DesktopModeStatus.useAppToWebBuildTimeGenericLinks() }
+ genericLinksParser = AppToWebGenericLinksParser(mContext, mockExecutor)
+ // Assert server side list correctly parsed
+ assertEquals(URL_S, genericLinksParser.getGenericLink(PACKAGE_NAME_1))
+ }
+
+ @Test
+ fun init_ignoresMalformedPair() {
+ doReturn(true).`when` { DesktopModeStatus.useAppToWebBuildTimeGenericLinks() }
+ val packageName2 = "com.google.android.slides"
+ val url2 = "https://docs.google.com"
+ resources.addOverride(R.string.generic_links_list,
+ "$PACKAGE_NAME_1:$URL_B error $packageName2:$url2")
+ genericLinksParser = AppToWebGenericLinksParser(mContext, mockExecutor)
+ // Assert generics links list correctly parsed
+ assertEquals(URL_B, genericLinksParser.getGenericLink(PACKAGE_NAME_1))
+ assertEquals(url2, genericLinksParser.getGenericLink(packageName2))
+ }
+
+
+ @Test
+ fun onlySavesValidPackageToUrlMaps() {
+ doReturn(true).`when` { DesktopModeStatus.useAppToWebBuildTimeGenericLinks() }
+ resources.addOverride(R.string.generic_links_list, "$PACKAGE_NAME_1:www.yout")
+ genericLinksParser = AppToWebGenericLinksParser(mContext, mockExecutor)
+ // Verify map with invalid url not saved
+ assertNull(genericLinksParser.getGenericLink(PACKAGE_NAME_1))
+ }
+
+ companion object {
+ private const val PACKAGE_NAME_1 = "com.google.android.youtube"
+
+ private const val URL_B = "http://www.youtube.com"
+ private const val URL_S = "http://www.google.com"
+
+ private const val SERVER_SIDE_LIST = "$PACKAGE_NAME_1:$URL_S"
+ private const val BUILD_TIME_LIST = "$PACKAGE_NAME_1:$URL_B"
+
+ private const val NAMESPACE = DeviceConfig.NAMESPACE_APP_COMPAT_OVERRIDES
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
index aeae0bebb697..01c4f3ad6870 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
@@ -64,6 +64,7 @@ import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.ShellTestCase
import com.android.wm.shell.TestRunningTaskInfoBuilder
import com.android.wm.shell.TestShellExecutor
+import com.android.wm.shell.apptoweb.AppToWebGenericLinksParser
import com.android.wm.shell.common.DisplayController
import com.android.wm.shell.common.DisplayInsetsController
import com.android.wm.shell.common.DisplayLayout
@@ -140,6 +141,7 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() {
@Mock private lateinit var mockShellCommandHandler: ShellCommandHandler
@Mock private lateinit var mockWindowManager: IWindowManager
@Mock private lateinit var mockInteractionJankMonitor: InteractionJankMonitor
+ @Mock private lateinit var mockGenericLinksParser: AppToWebGenericLinksParser
private val bgExecutor = TestShellExecutor()
private val transactionFactory = Supplier<SurfaceControl.Transaction> {
@@ -171,11 +173,13 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() {
mockSyncQueue,
mockTransitions,
Optional.of(mockDesktopTasksController),
+ mockGenericLinksParser,
mockDesktopModeWindowDecorFactory,
mockInputMonitorFactory,
transactionFactory,
mockRootTaskDisplayAreaOrganizer,
- windowDecorByTaskIdSpy, mockInteractionJankMonitor
+ windowDecorByTaskIdSpy,
+ mockInteractionJankMonitor
)
desktopModeWindowDecorViewModel.setSplitScreenController(mockSplitScreenController)
whenever(mockDisplayController.getDisplayLayout(any())).thenReturn(mockDisplayLayout)
@@ -218,7 +222,8 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() {
bgExecutor,
mockMainChoreographer,
mockSyncQueue,
- mockRootTaskDisplayAreaOrganizer
+ mockRootTaskDisplayAreaOrganizer,
+ mockGenericLinksParser
)
verify(decoration).close()
}
@@ -244,7 +249,8 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() {
bgExecutor,
mockMainChoreographer,
mockSyncQueue,
- mockRootTaskDisplayAreaOrganizer
+ mockRootTaskDisplayAreaOrganizer,
+ mockGenericLinksParser
)
task.setWindowingMode(WINDOWING_MODE_FREEFORM)
@@ -261,7 +267,8 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() {
bgExecutor,
mockMainChoreographer,
mockSyncQueue,
- mockRootTaskDisplayAreaOrganizer
+ mockRootTaskDisplayAreaOrganizer,
+ mockGenericLinksParser
)
}
@@ -359,7 +366,7 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() {
verify(mockDesktopModeWindowDecorFactory, never())
.create(any(), any(), any(), any(), eq(task), any(), any(), any(), any(), any(),
- any())
+ any(), any())
}
@Test
@@ -381,7 +388,7 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() {
onTaskOpening(task)
verify(mockDesktopModeWindowDecorFactory)
.create(any(), any(), any(), any(), eq(task), any(), any(), any(), any(),
- any(), any())
+ any(), any(), any())
} finally {
mockitoSession.finishMocking()
}
@@ -399,7 +406,7 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() {
verify(mockDesktopModeWindowDecorFactory, never())
.create(any(), any(), any(), any(), eq(task), any(), any(), any(), any(), any(),
- any())
+ any(), any())
}
@Test
@@ -416,7 +423,7 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() {
onTaskOpening(task)
verify(mockDesktopModeWindowDecorFactory, never())
- .create(any(), any(), any(), any(), eq(task), any(), any(), any(), any(),
+ .create(any(), any(), any(), any(), eq(task), any(), any(), any(), any(), any(),
any(), any())
}
@@ -515,7 +522,7 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() {
onTaskOpening(task)
verify(mockDesktopModeWindowDecorFactory, never())
.create(any(), any(), any(), any(), eq(task), any(), any(), any(), any(), any(),
- any())
+ any(), any())
} finally {
mockitoSession.finishMocking()
}
@@ -540,7 +547,7 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() {
onTaskOpening(task)
verify(mockDesktopModeWindowDecorFactory)
.create(any(), any(), any(), any(), eq(task), any(), any(), any(), any(), any(),
- any())
+ any(), any())
} finally {
mockitoSession.finishMocking()
}
@@ -564,7 +571,7 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() {
onTaskOpening(task)
verify(mockDesktopModeWindowDecorFactory)
.create(any(), any(), any(), any(), eq(task), any(), any(), any(), any(), any(),
- any())
+ any(), any())
} finally {
mockitoSession.finishMocking()
}
@@ -702,8 +709,8 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() {
private fun setUpMockDecorationForTask(task: RunningTaskInfo): DesktopModeWindowDecoration {
val decoration = mock(DesktopModeWindowDecoration::class.java)
whenever(
- mockDesktopModeWindowDecorFactory.create(
- any(), any(), any(), any(), eq(task), any(), any(), any(), any(), any(), any())
+ mockDesktopModeWindowDecorFactory.create(any(), any(), any(), any(), eq(task), any(),
+ any(), any(), any(), any(), any(), any())
).thenReturn(decoration)
decoration.mTaskInfo = task
whenever(decoration.isFocused).thenReturn(task.isFocused)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
index 412fef30d4fb..4b069f933d9f 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
@@ -31,6 +31,7 @@ import static com.google.common.truth.Truth.assertThat;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.any;
@@ -59,6 +60,7 @@ import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.SetFlagsRule;
import android.testing.AndroidTestingRunner;
import android.testing.TestableContext;
+import android.testing.TestableLooper;
import android.view.AttachedSurfaceControl;
import android.view.Choreographer;
import android.view.Display;
@@ -83,6 +85,7 @@ import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.TestRunningTaskInfoBuilder;
import com.android.wm.shell.TestShellExecutor;
+import com.android.wm.shell.apptoweb.AppToWebGenericLinksParser;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
@@ -114,6 +117,7 @@ import java.util.function.Supplier;
* atest WMShellUnitTests:DesktopModeWindowDecorationTests
*/
@SmallTest
+@TestableLooper.RunWithLooper
@RunWith(AndroidTestingRunner.class)
public class DesktopModeWindowDecorationTests extends ShellTestCase {
private static final String USE_WINDOW_SHADOWS_SYSPROP_KEY =
@@ -123,7 +127,8 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
private static final String USE_ROUNDED_CORNERS_SYSPROP_KEY =
"persist.wm.debug.desktop_use_rounded_corners";
- private static final Uri TEST_URI = Uri.parse("www.google.com");
+ private static final Uri TEST_URI1 = Uri.parse("https://www.google.com/");
+ private static final Uri TEST_URI2 = Uri.parse("https://docs.google.com/");
@Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT);
@@ -161,6 +166,12 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
private Handler mMockHandler;
@Mock
private DesktopModeWindowDecoration.OpenInBrowserClickListener mMockOpenInBrowserClickListener;
+ @Mock
+ private AppToWebGenericLinksParser mMockGenericLinksParser;
+ @Mock
+ private HandleMenu mMockHandleMenu;
+ @Mock
+ private HandleMenuFactory mMockHandleMenuFactory;
@Captor
private ArgumentCaptor<Function1<Boolean, Unit>> mOnMaxMenuHoverChangeListener;
@Captor
@@ -204,6 +215,8 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
final Display defaultDisplay = mock(Display.class);
doReturn(defaultDisplay).when(mMockDisplayController).getDisplay(Display.DEFAULT_DISPLAY);
doReturn(mInsetsState).when(mMockDisplayController).getInsetsState(anyInt());
+ doReturn(mMockHandleMenu).when(mMockHandleMenuFactory).create(any(), anyInt(), any(), any(),
+ any(), any(), any(), any(), anyBoolean(), any(), anyInt(), anyInt(), anyInt());
}
@After
@@ -572,65 +585,123 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
verify(mMockHandler).removeCallbacks(any());
}
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_TO_WEB)
+ public void capturedLink_handleMenuBrowserLinkSetToCapturedLinkIfValid() {
+ final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(true /* visible */);
+ final DesktopModeWindowDecoration decor = createWindowDecoration(
+ taskInfo, TEST_URI1 /* captured link */, TEST_URI2 /* generic link */);
+
+ // Verify handle menu's browser link set as captured link
+ decor.createHandleMenu(mMockSplitScreenController);
+ verifyHandleMenuCreated(TEST_URI1);
+ }
+
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_TO_WEB)
public void capturedLink_postsOnCapturedLinkExpiredRunnable() {
final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(true /* visible */);
+ final DesktopModeWindowDecoration decor = createWindowDecoration(
+ taskInfo, TEST_URI1 /* captured link */, null /* generic link */);
final ArgumentCaptor<Runnable> runnableArgument = ArgumentCaptor.forClass(Runnable.class);
- final DesktopModeWindowDecoration decor = createWindowDecoration(taskInfo);
- decor.relayout(taskInfo);
- // Assert captured link is set
- assertTrue(decor.browserLinkAvailable());
- // Asset runnable posted to set captured link to expired
+ // Run runnable to set captured link to expired
verify(mMockHandler).postDelayed(runnableArgument.capture(), anyLong());
runnableArgument.getValue().run();
- assertFalse(decor.browserLinkAvailable());
+
+ // Verify captured link is no longer valid by verifying link is not set as handle menu
+ // browser link.
+ decor.createHandleMenu(mMockSplitScreenController);
+ verifyHandleMenuCreated(null /* uri */);
}
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_TO_WEB)
public void capturedLink_capturedLinkNotResetToSameLink() {
final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(true /* visible */);
- final DesktopModeWindowDecoration decor = createWindowDecoration(taskInfo);
+ final DesktopModeWindowDecoration decor = createWindowDecoration(
+ taskInfo, TEST_URI1 /* captured link */, null /* generic link */);
final ArgumentCaptor<Runnable> runnableArgument = ArgumentCaptor.forClass(Runnable.class);
- // Set captured link and run on captured link expired runnable
- decor.relayout(taskInfo);
+ // Run runnable to set captured link to expired
verify(mMockHandler).postDelayed(runnableArgument.capture(), anyLong());
runnableArgument.getValue().run();
+ // Relayout decor with same captured link
decor.relayout(taskInfo);
- // Assert captured link not set to same value twice
- assertFalse(decor.browserLinkAvailable());
+
+ // Verify handle menu's browser link not set to captured link since link is expired
+ decor.createHandleMenu(mMockSplitScreenController);
+ verifyHandleMenuCreated(null /* uri */);
}
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_TO_WEB)
- public void capturedLink_capturedLinkExpiresAfterClick() {
+ public void capturedLink_capturedLinkStillUsedIfExpiredAfterHandleMenuCreation() {
final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(true /* visible */);
- final DesktopModeWindowDecoration decor = createWindowDecoration(taskInfo);
+ final DesktopModeWindowDecoration decor = createWindowDecoration(
+ taskInfo, TEST_URI1 /* captured link */, null /* generic link */);
+ final ArgumentCaptor<Runnable> runnableArgument = ArgumentCaptor.forClass(Runnable.class);
- decor.relayout(taskInfo);
- // Assert captured link is set
- assertTrue(decor.browserLinkAvailable());
+ // Create handle menu before link expires
+ decor.createHandleMenu(mMockSplitScreenController);
+
+ // Run runnable to set captured link to expired
+ verify(mMockHandler).postDelayed(runnableArgument.capture(), anyLong());
+ runnableArgument.getValue().run();
+
+ // Verify handle menu's browser link is set to captured link since menu was opened before
+ // captured link expired
+ decor.createHandleMenu(mMockSplitScreenController);
+ verifyHandleMenuCreated(TEST_URI1);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_TO_WEB)
+ public void capturedLink_capturedLinkExpiresAfterClick() {
+ final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(true /* visible */);
+ final DesktopModeWindowDecoration decor = createWindowDecoration(
+ taskInfo, TEST_URI1 /* captured link */, null /* generic link */);
+ // Simulate menu opening and clicking open in browser button
+ decor.createHandleMenu(mMockSplitScreenController);
decor.onOpenInBrowserClick();
- //Assert Captured link expires after button is clicked
- assertFalse(decor.browserLinkAvailable());
+
+ // Verify handle menu's browser link not set to captured link since link not valid after
+ // open in browser clicked
+ decor.createHandleMenu(mMockSplitScreenController);
+ verifyHandleMenuCreated(null /* uri */);
}
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_TO_WEB)
public void capturedLink_openInBrowserListenerCalledOnClick() {
final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(true /* visible */);
- final DesktopModeWindowDecoration decor = createWindowDecoration(taskInfo);
-
- decor.relayout(taskInfo);
+ final DesktopModeWindowDecoration decor = createWindowDecoration(
+ taskInfo, TEST_URI1 /* captured link */, null /* generic link */);
+ decor.createHandleMenu(mMockSplitScreenController);
decor.onOpenInBrowserClick();
verify(mMockOpenInBrowserClickListener).onClick(any(), any());
}
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_TO_WEB)
+ public void genericLink_genericLinkUsedWhenCapturedLinkUnavailable() {
+ final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(true /* visible */);
+ final DesktopModeWindowDecoration decor = createWindowDecoration(
+ taskInfo, null /* captured link */, TEST_URI2 /* generic link */);
+
+ // Verify handle menu's browser link set as generic link no captured link is available
+ decor.createHandleMenu(mMockSplitScreenController);
+ verifyHandleMenuCreated(TEST_URI2);
+ }
+
+ private void verifyHandleMenuCreated(@Nullable Uri uri) {
+ verify(mMockHandleMenuFactory).create(any(), anyInt(), any(), any(), any(), any(), any(),
+ any(), anyBoolean(), eq(uri), anyInt(), anyInt(), anyInt());
+ }
+
private void createMaximizeMenu(DesktopModeWindowDecoration decoration, MaximizeMenu menu) {
final OnTaskActionClickListener l = (taskId, tag) -> {};
decoration.setOnMaximizeOrRestoreClickListener(l);
@@ -657,6 +728,18 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
R.dimen.rounded_corner_radius_bottom, fillValue);
}
+ private DesktopModeWindowDecoration createWindowDecoration(
+ ActivityManager.RunningTaskInfo taskInfo, @Nullable Uri capturedLink,
+ @Nullable Uri genericLink) {
+ taskInfo.capturedLink = capturedLink;
+ taskInfo.capturedLinkTimestamp = System.currentTimeMillis();
+ final String genericLinkString = genericLink == null ? null : genericLink.toString();
+ doReturn(genericLinkString).when(mMockGenericLinksParser).getGenericLink(any());
+ final DesktopModeWindowDecoration decor = createWindowDecoration(taskInfo);
+ // Relayout to set captured link
+ decor.relayout(taskInfo);
+ return decor;
+ }
private DesktopModeWindowDecoration createWindowDecoration(
ActivityManager.RunningTaskInfo taskInfo) {
@@ -669,14 +752,15 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
final DesktopModeWindowDecoration windowDecor = new DesktopModeWindowDecoration(mContext,
mMockDisplayController, mMockSplitScreenController, mMockShellTaskOrganizer,
taskInfo, mMockSurfaceControl, mMockHandler, mBgExecutor, mMockChoreographer,
- mMockSyncQueue, mMockRootTaskDisplayAreaOrganizer,
+ mMockSyncQueue, mMockRootTaskDisplayAreaOrganizer, mMockGenericLinksParser,
SurfaceControl.Builder::new, mMockTransactionSupplier,
WindowContainerTransaction::new, SurfaceControl::new,
- mMockSurfaceControlViewHostFactory, maximizeMenuFactory);
+ mMockSurfaceControlViewHostFactory, maximizeMenuFactory, mMockHandleMenuFactory);
windowDecor.setCaptionListeners(mMockTouchEventListener, mMockTouchEventListener,
mMockTouchEventListener, mMockTouchEventListener);
windowDecor.setExclusionRegionListener(mMockExclusionRegionListener);
windowDecor.setOpenInBrowserClickListener(mMockOpenInBrowserClickListener);
+ windowDecor.mDecorWindowContext = mContext;
return windowDecor;
}
@@ -692,8 +776,6 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
"DesktopModeWindowDecorationTests");
taskInfo.baseActivity = new ComponentName("com.android.wm.shell.windowdecor",
"DesktopModeWindowDecorationTests");
- taskInfo.capturedLink = TEST_URI;
- taskInfo.capturedLinkTimestamp = System.currentTimeMillis();
return taskInfo;
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt
index e548f8f1eb2a..ed43aa3f1301 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt
@@ -229,7 +229,7 @@ class HandleMenuTest : ShellTestCase() {
val handleMenu = HandleMenu(mockDesktopWindowDecoration, layoutId,
onClickListener, onTouchListener, appIcon, appName, displayController,
splitScreenController, shouldShowWindowingPill = true,
- shouldShowBrowserPill = true, captionWidth = HANDLE_WIDTH, captionHeight = 50,
+ null /* openInBrowserLink */, captionWidth = HANDLE_WIDTH, captionHeight = 50,
captionX = captionX
)
handleMenu.show()
diff --git a/packages/PackageInstaller/TEST_MAPPING b/packages/PackageInstaller/TEST_MAPPING
index 76d7ab13adad..b3fb1e7b3034 100644
--- a/packages/PackageInstaller/TEST_MAPPING
+++ b/packages/PackageInstaller/TEST_MAPPING
@@ -9,6 +9,9 @@
]
},
{
+ "name": "CtsUpdateOwnershipEnforcementTestCases"
+ },
+ {
"name": "CtsNoPermissionTestCases"
},
{
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index cfd74d4b9f42..ce997bf15b91 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -1010,10 +1010,8 @@
<!-- UI debug setting: force allow on external summary [CHAR LIMIT=150] -->
<string name="force_resizable_activities_summary">Make all activities resizable for multi-window, regardless of manifest values.</string>
- <!-- UI debug setting: enable legacy freeform window support [CHAR LIMIT=50] -->
- <string name="enable_freeform_support">Enable freeform windows (legacy)</string>
- <!-- UI debug setting: enable legacy freeform window support summary [CHAR LIMIT=150] -->
- <string name="enable_freeform_support_summary">Enable support for experimental legacy freeform windows.</string>
+ <!-- Title for a toggle that enables support for windows to be in freeform (apps run in resizable windows). [CHAR LIMIT=50] -->
+ <string name="enable_freeform_support">Enable freeform window support</string>
<!-- Local (desktop) backup password menu title [CHAR LIMIT=25] -->
<string name="local_backup_password_title">Desktop backup password</string>
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ContentListState.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ContentListState.kt
index c5dab3347e5f..38a347465318 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ContentListState.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ContentListState.kt
@@ -58,7 +58,7 @@ internal constructor(
communalContent: List<CommunalContentModel>,
private val onAddWidget:
(componentName: ComponentName, user: UserHandle, priority: Int) -> Unit,
- private val onDeleteWidget: (id: Int) -> Unit,
+ private val onDeleteWidget: (id: Int, componentName: ComponentName, priority: Int) -> Unit,
private val onReorderWidgets: (widgetIdToPriorityMap: Map<Int, Int>) -> Unit,
) {
var list = communalContent.toMutableStateList()
@@ -74,7 +74,7 @@ internal constructor(
if (list[indexToRemove].isWidgetContent()) {
val widget = list[indexToRemove] as CommunalContentModel.WidgetContent
list.apply { removeAt(indexToRemove) }
- onDeleteWidget(widget.appWidgetId)
+ onDeleteWidget(widget.appWidgetId, widget.componentName, widget.priority)
}
}
@@ -110,7 +110,7 @@ internal constructor(
// reorder and then add the new widget
onReorderWidgets(widgetIdToPriorityMap)
if (newItemComponentName != null && newItemUser != null && newItemIndex != null) {
- onAddWidget(newItemComponentName, newItemUser, /*priority=*/ list.size - newItemIndex)
+ onAddWidget(newItemComponentName, newItemUser, /* priority= */ list.size - newItemIndex)
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt
index 4ad020f94c89..bbd2f6b0174a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt
@@ -16,11 +16,13 @@
package com.android.systemui.communal
+import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import android.provider.Settings
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.Flags.FLAG_COMMUNAL_HUB
+import com.android.systemui.Flags.FLAG_COMMUNAL_SCENE_KTF_REFACTOR
import com.android.systemui.SysuiTestCase
import com.android.systemui.communal.domain.interactor.communalInteractor
import com.android.systemui.communal.domain.interactor.communalSceneInteractor
@@ -103,6 +105,7 @@ class CommunalSceneStartableTest : SysuiTestCase() {
}
@Test
+ @DisableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
fun keyguardGoesAway_whenLaunchingWidget_doNotForceBlankScene() =
with(kosmos) {
testScope.runTest {
@@ -123,6 +126,7 @@ class CommunalSceneStartableTest : SysuiTestCase() {
}
@Test
+ @DisableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
fun keyguardGoesAway_whenNotLaunchingWidget_forceBlankScene() =
with(kosmos) {
testScope.runTest {
@@ -143,6 +147,7 @@ class CommunalSceneStartableTest : SysuiTestCase() {
}
@Test
+ @DisableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
fun keyguardGoesAway_whenInEditMode_doesNotChangeScene() =
with(kosmos) {
testScope.runTest {
@@ -180,6 +185,7 @@ class CommunalSceneStartableTest : SysuiTestCase() {
}
@Test
+ @DisableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
fun occluded_forceBlankScene() =
with(kosmos) {
testScope.runTest {
@@ -199,6 +205,7 @@ class CommunalSceneStartableTest : SysuiTestCase() {
}
@Test
+ @DisableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
fun occluded_doesNotForceBlankSceneIfLaunchingActivityOverLockscreen() =
with(kosmos) {
testScope.runTest {
@@ -218,6 +225,7 @@ class CommunalSceneStartableTest : SysuiTestCase() {
}
@Test
+ @DisableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
fun deviceDocked_doesNotForceCommunalIfTransitioningFromCommunal() =
with(kosmos) {
testScope.runTest {
@@ -235,6 +243,7 @@ class CommunalSceneStartableTest : SysuiTestCase() {
}
@Test
+ @DisableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
fun deviceAsleep_forceBlankSceneAfterTimeout() =
with(kosmos) {
testScope.runTest {
@@ -256,6 +265,7 @@ class CommunalSceneStartableTest : SysuiTestCase() {
}
@Test
+ @DisableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
fun deviceAsleep_wakesUpBeforeTimeout_noChangeInScene() =
with(kosmos) {
testScope.runTest {
@@ -483,6 +493,7 @@ class CommunalSceneStartableTest : SysuiTestCase() {
}
@Test
+ @DisableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
fun transitionFromDozingToGlanceableHub_forcesCommunal() =
with(kosmos) {
testScope.runTest {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt
index c707ebf0a2c4..ca81838bcdaa 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt
@@ -696,7 +696,7 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
CommunalWidgetContentModel.Pending(
appWidgetId = 2,
priority = 2,
- packageName = "pk_2",
+ componentName = ComponentName("pk_2", "cls_2"),
icon = fakeIcon,
user = mainUser,
),
@@ -731,7 +731,7 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
CommunalWidgetContentModel.Pending(
appWidgetId = 1,
priority = 1,
- packageName = "pk_1",
+ componentName = ComponentName("pk_1", "cls_1"),
icon = fakeIcon,
user = mainUser,
),
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
index 9539c0492056..0242c2d6b89d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
@@ -19,10 +19,8 @@ package com.android.systemui.communal.domain.interactor
import android.app.admin.DevicePolicyManager
import android.app.admin.devicePolicyManager
-import android.appwidget.AppWidgetProviderInfo
import android.content.Intent
import android.content.pm.UserInfo
-import android.graphics.Bitmap
import android.os.UserHandle
import android.os.UserManager
import android.os.userManager
@@ -52,7 +50,6 @@ import com.android.systemui.communal.domain.model.CommunalContentModel
import com.android.systemui.communal.domain.model.CommunalTransitionProgressModel
import com.android.systemui.communal.shared.model.CommunalContentSize
import com.android.systemui.communal.shared.model.CommunalScenes
-import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
import com.android.systemui.communal.shared.model.EditModeState
import com.android.systemui.communal.widgets.EditWidgetsActivityStarter
import com.android.systemui.coroutines.collectLastValue
@@ -251,18 +248,16 @@ class CommunalInteractorTest : SysuiTestCase() {
runCurrent()
// Widgets available.
- val widget1 = createWidgetForUser(1, USER_INFO_WORK.id)
- val widget2 = createWidgetForUser(2, MAIN_USER_INFO.id)
- val widget3 = createWidgetForUser(3, MAIN_USER_INFO.id)
- val widgets = listOf(widget1, widget2, widget3)
- widgetRepository.setCommunalWidgets(widgets)
+ widgetRepository.addWidget(appWidgetId = 1, userId = USER_INFO_WORK.id)
+ widgetRepository.addWidget(appWidgetId = 2, userId = MAIN_USER_INFO.id)
+ widgetRepository.addWidget(appWidgetId = 3, userId = MAIN_USER_INFO.id)
val widgetContent by collectLastValue(underTest.widgetContent)
- assertThat(widgetContent!!).isNotEmpty()
- widgetContent!!.forEachIndexed { index, model ->
- assertThat(model.appWidgetId).isEqualTo(widgets[index].appWidgetId)
- }
+ assertThat(checkNotNull(widgetContent)).isNotEmpty()
+ assertThat(widgetContent!![0].appWidgetId).isEqualTo(1)
+ assertThat(widgetContent!![1].appWidgetId).isEqualTo(2)
+ assertThat(widgetContent!![2].appWidgetId).isEqualTo(3)
}
@Test
@@ -839,11 +834,9 @@ class CommunalInteractorTest : SysuiTestCase() {
val widgetContent by collectLastValue(underTest.widgetContent)
// Given three widgets, and one of them is associated with pre-existing work profile.
- val widget1 = createWidgetForUser(1, USER_INFO_WORK.id)
- val widget2 = createWidgetForUser(2, MAIN_USER_INFO.id)
- val widget3 = createWidgetForUser(3, MAIN_USER_INFO.id)
- val widgets = listOf(widget1, widget2, widget3)
- widgetRepository.setCommunalWidgets(widgets)
+ widgetRepository.addWidget(appWidgetId = 1, userId = USER_INFO_WORK.id)
+ widgetRepository.addWidget(appWidgetId = 2, userId = MAIN_USER_INFO.id)
+ widgetRepository.addWidget(appWidgetId = 3, userId = MAIN_USER_INFO.id)
// One widget is filtered out and the remaining two link to main user id.
assertThat(checkNotNull(widgetContent).size).isEqualTo(2)
@@ -882,11 +875,9 @@ class CommunalInteractorTest : SysuiTestCase() {
whenever(userManager.isManagedProfile(eq(USER_INFO_WORK.id))).thenReturn(true)
val widgetContent by collectLastValue(underTest.widgetContent)
- val widget1 = createWidgetForUser(1, USER_INFO_WORK.id)
- val widget2 = createWidgetForUser(2, MAIN_USER_INFO.id)
- val widget3 = createWidgetForUser(3, MAIN_USER_INFO.id)
- val widgets = listOf(widget1, widget2, widget3)
- widgetRepository.setCommunalWidgets(widgets)
+ widgetRepository.addWidget(appWidgetId = 1, userId = USER_INFO_WORK.id)
+ widgetRepository.addWidget(appWidgetId = 2, userId = MAIN_USER_INFO.id)
+ widgetRepository.addWidget(appWidgetId = 3, userId = MAIN_USER_INFO.id)
// The work profile widget is in quiet mode, while other widgets are not.
assertThat(widgetContent).hasSize(3)
@@ -927,11 +918,9 @@ class CommunalInteractorTest : SysuiTestCase() {
val widgetContent by collectLastValue(underTest.widgetContent)
// One available work widget, one pending work widget, and one regular available widget.
- val widget1 = createWidgetForUser(1, USER_INFO_WORK.id)
- val widget2 = createPendingWidgetForUser(2, userId = USER_INFO_WORK.id)
- val widget3 = createWidgetForUser(3, MAIN_USER_INFO.id)
- val widgets = listOf(widget1, widget2, widget3)
- widgetRepository.setCommunalWidgets(widgets)
+ widgetRepository.addWidget(appWidgetId = 1, userId = USER_INFO_WORK.id)
+ widgetRepository.addPendingWidget(appWidgetId = 2, userId = USER_INFO_WORK.id)
+ widgetRepository.addWidget(appWidgetId = 3, userId = MAIN_USER_INFO.id)
setKeyguardFeaturesDisabled(
USER_INFO_WORK,
@@ -962,11 +951,9 @@ class CommunalInteractorTest : SysuiTestCase() {
val widgetContent by collectLastValue(underTest.widgetContent)
// Given three widgets, and one of them is associated with work profile.
- val widget1 = createWidgetForUser(1, USER_INFO_WORK.id)
- val widget2 = createPendingWidgetForUser(2, userId = USER_INFO_WORK.id)
- val widget3 = createWidgetForUser(3, MAIN_USER_INFO.id)
- val widgets = listOf(widget1, widget2, widget3)
- widgetRepository.setCommunalWidgets(widgets)
+ widgetRepository.addWidget(appWidgetId = 1, userId = USER_INFO_WORK.id)
+ widgetRepository.addPendingWidget(appWidgetId = 2, userId = USER_INFO_WORK.id)
+ widgetRepository.addWidget(appWidgetId = 3, userId = MAIN_USER_INFO.id)
setKeyguardFeaturesDisabled(
USER_INFO_WORK,
@@ -1088,47 +1075,6 @@ class CommunalInteractorTest : SysuiTestCase() {
)
}
- private fun createWidgetForUser(
- appWidgetId: Int,
- userId: Int
- ): CommunalWidgetContentModel.Available =
- mock<CommunalWidgetContentModel.Available> {
- whenever(this.appWidgetId).thenReturn(appWidgetId)
- val providerInfo =
- mock<AppWidgetProviderInfo>().apply {
- widgetCategory = AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD
- }
- whenever(providerInfo.profile).thenReturn(UserHandle(userId))
- whenever(this.providerInfo).thenReturn(providerInfo)
- }
-
- private fun createPendingWidgetForUser(
- appWidgetId: Int,
- priority: Int = 0,
- packageName: String = "",
- icon: Bitmap? = null,
- userId: Int = 0,
- ): CommunalWidgetContentModel.Pending {
- return CommunalWidgetContentModel.Pending(
- appWidgetId = appWidgetId,
- priority = priority,
- packageName = packageName,
- icon = icon,
- user = UserHandle(userId),
- )
- }
-
- private fun createWidgetWithCategory(
- appWidgetId: Int,
- category: Int
- ): CommunalWidgetContentModel =
- mock<CommunalWidgetContentModel.Available> {
- whenever(this.appWidgetId).thenReturn(appWidgetId)
- val providerInfo = mock<AppWidgetProviderInfo>().apply { widgetCategory = category }
- whenever(providerInfo.profile).thenReturn(UserHandle(MAIN_USER_INFO.id))
- whenever(this.providerInfo).thenReturn(providerInfo)
- }
-
private companion object {
val MAIN_USER_INFO = UserInfo(0, "primary", UserInfo.FLAG_MAIN)
val USER_INFO_WORK =
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractorTest.kt
index f7f70c154ce6..ad7385344fac 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractorTest.kt
@@ -34,8 +34,11 @@ import com.android.systemui.flags.fakeFeatureFlagsClassic
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
import com.android.systemui.keyguard.data.repository.realKeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.DozeStateModel
+import com.android.systemui.keyguard.shared.model.DozeTransitionModel
import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING
import com.android.systemui.keyguard.shared.model.KeyguardState.GLANCEABLE_HUB
+import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED
import com.android.systemui.keyguard.shared.model.TransitionInfo
@@ -46,6 +49,8 @@ import com.android.systemui.keyguard.shared.model.TransitionState.RUNNING
import com.android.systemui.keyguard.shared.model.TransitionState.STARTED
import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.kosmos.testScope
+import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
+import com.android.systemui.power.domain.interactor.powerInteractor
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -53,6 +58,7 @@ import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.advanceTimeBy
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -211,8 +217,15 @@ class CommunalSceneTransitionInteractorTest : SysuiTestCase() {
@Test
fun transition_from_hub_end_in_dream() =
testScope.runTest {
+ // Device is dreaming and not dozing.
+ kosmos.powerInteractor.setAwakeForTest()
+ kosmos.fakeKeyguardRepository.setDozeTransitionModel(
+ DozeTransitionModel(from = DozeStateModel.DOZE, to = DozeStateModel.FINISH)
+ )
+ kosmos.fakeKeyguardRepository.setKeyguardOccluded(true)
kosmos.fakeKeyguardRepository.setDreaming(true)
- runCurrent()
+ kosmos.fakeKeyguardRepository.setDreamingWithOverlay(true)
+ advanceTimeBy(100L)
sceneTransitions.value = hubToBlank
@@ -254,6 +267,100 @@ class CommunalSceneTransitionInteractorTest : SysuiTestCase() {
)
}
+ /** Transition from hub to occluded. */
+ @Test
+ fun transition_from_hub_end_in_occluded() =
+ testScope.runTest {
+ kosmos.fakeKeyguardRepository.setKeyguardOccluded(true)
+ runCurrent()
+
+ sceneTransitions.value = hubToBlank
+
+ val currentStep by collectLastValue(keyguardTransitionRepository.transitions)
+
+ assertThat(currentStep)
+ .isEqualTo(
+ TransitionStep(
+ from = GLANCEABLE_HUB,
+ to = OCCLUDED,
+ transitionState = STARTED,
+ value = 0f,
+ ownerName = ownerName,
+ )
+ )
+
+ progress.emit(0.4f)
+ assertThat(currentStep)
+ .isEqualTo(
+ TransitionStep(
+ from = GLANCEABLE_HUB,
+ to = OCCLUDED,
+ transitionState = RUNNING,
+ value = 0.4f,
+ ownerName = ownerName,
+ )
+ )
+
+ sceneTransitions.value = Idle(CommunalScenes.Blank)
+ assertThat(currentStep)
+ .isEqualTo(
+ TransitionStep(
+ from = GLANCEABLE_HUB,
+ to = OCCLUDED,
+ transitionState = FINISHED,
+ value = 1f,
+ ownerName = ownerName,
+ )
+ )
+ }
+
+ /** Transition from hub to gone. */
+ @Test
+ fun transition_from_hub_end_in_gone() =
+ testScope.runTest {
+ kosmos.fakeKeyguardRepository.setKeyguardGoingAway(true)
+ runCurrent()
+
+ sceneTransitions.value = hubToBlank
+
+ val currentStep by collectLastValue(keyguardTransitionRepository.transitions)
+
+ assertThat(currentStep)
+ .isEqualTo(
+ TransitionStep(
+ from = GLANCEABLE_HUB,
+ to = GONE,
+ transitionState = STARTED,
+ value = 0f,
+ ownerName = ownerName,
+ )
+ )
+
+ progress.emit(0.4f)
+ assertThat(currentStep)
+ .isEqualTo(
+ TransitionStep(
+ from = GLANCEABLE_HUB,
+ to = GONE,
+ transitionState = RUNNING,
+ value = 0.4f,
+ ownerName = ownerName,
+ )
+ )
+
+ sceneTransitions.value = Idle(CommunalScenes.Blank)
+ assertThat(currentStep)
+ .isEqualTo(
+ TransitionStep(
+ from = GLANCEABLE_HUB,
+ to = GONE,
+ transitionState = FINISHED,
+ value = 1f,
+ ownerName = ownerName,
+ )
+ )
+ }
+
/** Transition from blank to hub, then settle back in blank. */
@Test
fun transition_from_blank_end_in_blank() =
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/log/CommunalMetricsLoggerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/log/CommunalMetricsLoggerTest.kt
new file mode 100644
index 000000000000..35df641035f7
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/log/CommunalMetricsLoggerTest.kt
@@ -0,0 +1,93 @@
+/*
+ * 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.communal.log
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.communal.shared.log.CommunalMetricsLogger
+import com.android.systemui.shared.system.SysUiStatsLog
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.kotlin.any
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.never
+import org.mockito.kotlin.verify
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class CommunalMetricsLoggerTest : SysuiTestCase() {
+ private val statsLogProxy = mock<CommunalMetricsLogger.StatsLogProxy>()
+
+ private val loggablePrefixes = listOf("com.blue.", "com.red.")
+ private lateinit var underTest: CommunalMetricsLogger
+
+ @Before
+ fun setUp() {
+ underTest = CommunalMetricsLogger(loggablePrefixes, statsLogProxy)
+ }
+
+ @Test
+ fun logAddWidget_componentNotLoggable_doNotLog() {
+ underTest.logAddWidget(
+ componentName = "com.green.package/my_test_widget",
+ rank = 1,
+ )
+ verify(statsLogProxy, never())
+ .writeCommunalHubWidgetEventReported(anyInt(), any(), anyInt())
+ }
+
+ @Test
+ fun logAddWidget_componentLoggable_logAddEvent() {
+ underTest.logAddWidget(
+ componentName = "com.blue.package/my_test_widget",
+ rank = 1,
+ )
+ verify(statsLogProxy)
+ .writeCommunalHubWidgetEventReported(
+ SysUiStatsLog.COMMUNAL_HUB_WIDGET_EVENT_REPORTED__ACTION__ADD,
+ "com.blue.package/my_test_widget",
+ 1,
+ )
+ }
+
+ @Test
+ fun logRemoveWidget_componentNotLoggable_doNotLog() {
+ underTest.logRemoveWidget(
+ componentName = "com.yellow.package/my_test_widget",
+ rank = 2,
+ )
+ verify(statsLogProxy, never())
+ .writeCommunalHubWidgetEventReported(anyInt(), any(), anyInt())
+ }
+
+ @Test
+ fun logRemoveWidget_componentLoggable_logRemoveEvent() {
+ underTest.logRemoveWidget(
+ componentName = "com.red.package/my_test_widget",
+ rank = 2,
+ )
+ verify(statsLogProxy)
+ .writeCommunalHubWidgetEventReported(
+ SysUiStatsLog.COMMUNAL_HUB_WIDGET_EVENT_REPORTED__ACTION__REMOVE,
+ "com.red.package/my_test_widget",
+ 2,
+ )
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
index f8906adc33d4..61487b0a8c73 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
@@ -16,14 +16,13 @@
package com.android.systemui.communal.view.viewmodel
-import android.appwidget.AppWidgetProviderInfo
import android.content.ActivityNotFoundException
+import android.content.ComponentName
import android.content.Intent
import android.content.pm.ActivityInfo
import android.content.pm.PackageManager
import android.content.pm.ResolveInfo
import android.content.pm.UserInfo
-import android.os.UserHandle
import android.provider.Settings
import android.widget.RemoteViews
import androidx.activity.result.ActivityResultLauncher
@@ -47,8 +46,8 @@ import com.android.systemui.communal.domain.interactor.communalPrefsInteractor
import com.android.systemui.communal.domain.interactor.communalSceneInteractor
import com.android.systemui.communal.domain.interactor.communalSettingsInteractor
import com.android.systemui.communal.domain.model.CommunalContentModel
+import com.android.systemui.communal.shared.log.CommunalMetricsLogger
import com.android.systemui.communal.shared.log.CommunalUiEvent
-import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
import com.android.systemui.communal.shared.model.EditModeState
import com.android.systemui.communal.ui.viewmodel.CommunalEditModeViewModel
import com.android.systemui.coroutines.collectLastValue
@@ -86,9 +85,9 @@ import org.mockito.kotlin.spy
class CommunalEditModeViewModelTest : SysuiTestCase() {
@Mock private lateinit var mediaHost: MediaHost
@Mock private lateinit var uiEventLogger: UiEventLogger
- @Mock private lateinit var providerInfo: AppWidgetProviderInfo
@Mock private lateinit var packageManager: PackageManager
@Mock private lateinit var activityResultLauncher: ActivityResultLauncher<Intent>
+ @Mock private lateinit var metricsLogger: CommunalMetricsLogger
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
@@ -120,7 +119,6 @@ class CommunalEditModeViewModelTest : SysuiTestCase() {
selectedUserIndex = 0,
)
kosmos.fakeFeatureFlagsClassic.set(Flags.COMMUNAL_SERVICE_ENABLED, true)
- whenever(providerInfo.profile).thenReturn(UserHandle(MAIN_USER_INFO.id))
underTest =
CommunalEditModeViewModel(
@@ -133,6 +131,7 @@ class CommunalEditModeViewModelTest : SysuiTestCase() {
logcatLogBuffer("CommunalEditModeViewModelTest"),
kosmos.testDispatcher,
kosmos.communalPrefsInteractor,
+ metricsLogger,
)
}
@@ -142,20 +141,8 @@ class CommunalEditModeViewModelTest : SysuiTestCase() {
tutorialRepository.setTutorialSettingState(Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED)
// Widgets available.
- val widgets =
- listOf(
- CommunalWidgetContentModel.Available(
- appWidgetId = 0,
- priority = 30,
- providerInfo = providerInfo,
- ),
- CommunalWidgetContentModel.Available(
- appWidgetId = 1,
- priority = 20,
- providerInfo = providerInfo,
- ),
- )
- widgetRepository.setCommunalWidgets(widgets)
+ widgetRepository.addWidget(appWidgetId = 0, priority = 30)
+ widgetRepository.addWidget(appWidgetId = 1, priority = 20)
// Smartspace available.
smartspaceRepository.setTimers(
@@ -216,20 +203,8 @@ class CommunalEditModeViewModelTest : SysuiTestCase() {
tutorialRepository.setTutorialSettingState(Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED)
// Widgets available.
- val widgets =
- listOf(
- CommunalWidgetContentModel.Available(
- appWidgetId = 0,
- priority = 30,
- providerInfo = providerInfo,
- ),
- CommunalWidgetContentModel.Available(
- appWidgetId = 1,
- priority = 20,
- providerInfo = providerInfo,
- ),
- )
- widgetRepository.setCommunalWidgets(widgets)
+ widgetRepository.addWidget(appWidgetId = 0, priority = 30)
+ widgetRepository.addWidget(appWidgetId = 1, priority = 20)
val communalContent by collectLastValue(underTest.communalContent)
@@ -240,14 +215,18 @@ class CommunalEditModeViewModelTest : SysuiTestCase() {
assertThat(communalContent?.get(1))
.isInstanceOf(CommunalContentModel.WidgetContent::class.java)
- underTest.onDeleteWidget(widgets.get(0).appWidgetId)
+ underTest.onDeleteWidget(
+ id = 0,
+ componentName = ComponentName("test_package", "test_class"),
+ priority = 30,
+ )
// Only one widget and CTA tile remain.
assertThat(communalContent?.size).isEqualTo(1)
val item = communalContent?.get(0)
val appWidgetId =
if (item is CommunalContentModel.WidgetContent) item.appWidgetId else null
- assertThat(appWidgetId).isEqualTo(widgets.get(1).appWidgetId)
+ assertThat(appWidgetId).isEqualTo(1)
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
index c480aa8e4a3d..d862a21d8e99 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
@@ -16,9 +16,7 @@
package com.android.systemui.communal.view.viewmodel
-import android.appwidget.AppWidgetProviderInfo
import android.content.pm.UserInfo
-import android.os.UserHandle
import android.platform.test.flag.junit.FlagsParameterization
import android.provider.Settings
import android.widget.RemoteViews
@@ -44,7 +42,6 @@ import com.android.systemui.communal.domain.interactor.communalSettingsInteracto
import com.android.systemui.communal.domain.interactor.communalTutorialInteractor
import com.android.systemui.communal.domain.model.CommunalContentModel
import com.android.systemui.communal.shared.model.CommunalScenes
-import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
import com.android.systemui.communal.ui.viewmodel.CommunalViewModel.Companion.POPUP_AUTO_HIDE_TIMEOUT_MS
import com.android.systemui.communal.ui.viewmodel.PopupType
@@ -110,7 +107,6 @@ import platform.test.runner.parameterized.Parameters
@RunWith(ParameterizedAndroidJunit4::class)
class CommunalViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
@Mock private lateinit var mediaHost: MediaHost
- @Mock private lateinit var providerInfo: AppWidgetProviderInfo
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
@@ -153,7 +149,6 @@ class CommunalViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
userInfos = listOf(MAIN_USER_INFO),
selectedUserIndex = 0,
)
- whenever(providerInfo.profile).thenReturn(UserHandle(MAIN_USER_INFO.id))
whenever(mediaHost.visible).thenReturn(true)
kosmos.powerInteractor.setAwakeForTest()
@@ -212,20 +207,8 @@ class CommunalViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
tutorialRepository.setTutorialSettingState(Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED)
// Widgets available.
- val widgets =
- listOf(
- CommunalWidgetContentModel.Available(
- appWidgetId = 0,
- priority = 30,
- providerInfo = providerInfo,
- ),
- CommunalWidgetContentModel.Available(
- appWidgetId = 1,
- priority = 20,
- providerInfo = providerInfo,
- ),
- )
- widgetRepository.setCommunalWidgets(widgets)
+ widgetRepository.addWidget(appWidgetId = 0, priority = 30)
+ widgetRepository.addWidget(appWidgetId = 1, priority = 20)
// Smartspace available.
smartspaceRepository.setTimers(
@@ -314,15 +297,7 @@ class CommunalViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
testScope.runTest {
tutorialRepository.setTutorialSettingState(Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED)
- widgetRepository.setCommunalWidgets(
- listOf(
- CommunalWidgetContentModel.Available(
- appWidgetId = 1,
- priority = 1,
- providerInfo = providerInfo,
- )
- ),
- )
+ widgetRepository.addWidget(appWidgetId = 1, priority = 1)
mediaRepository.mediaInactive()
smartspaceRepository.setTimers(emptyList())
@@ -676,20 +651,8 @@ class CommunalViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
)
// Widgets available
- val widgets =
- listOf(
- CommunalWidgetContentModel.Available(
- appWidgetId = 0,
- priority = 30,
- providerInfo = providerInfo,
- ),
- CommunalWidgetContentModel.Available(
- appWidgetId = 1,
- priority = 20,
- providerInfo = providerInfo,
- ),
- )
- widgetRepository.setCommunalWidgets(widgets)
+ widgetRepository.addWidget(appWidgetId = 0, priority = 30)
+ widgetRepository.addWidget(appWidgetId = 1, priority = 20)
// Then hub shows widgets and the CTA tile
assertThat(communalContent).hasSize(3)
@@ -743,20 +706,8 @@ class CommunalViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
)
// And widgets available
- val widgets =
- listOf(
- CommunalWidgetContentModel.Available(
- appWidgetId = 0,
- priority = 30,
- providerInfo = providerInfo,
- ),
- CommunalWidgetContentModel.Available(
- appWidgetId = 1,
- priority = 20,
- providerInfo = providerInfo,
- ),
- )
- widgetRepository.setCommunalWidgets(widgets)
+ widgetRepository.addWidget(appWidgetId = 0, priority = 30)
+ widgetRepository.addWidget(appWidgetId = 1, priority = 20)
// Then emits widgets and the CTA tile
assertThat(communalContent).hasSize(3)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartableTest.kt
index 3d2eabf2a07c..c9f3f1453492 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartableTest.kt
@@ -16,10 +16,7 @@
package com.android.systemui.communal.widgets
-import android.appwidget.AppWidgetProviderInfo
import android.content.pm.UserInfo
-import android.graphics.Bitmap
-import android.os.UserHandle
import android.provider.Settings
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
@@ -27,7 +24,6 @@ import com.android.systemui.Flags.FLAG_COMMUNAL_HUB
import com.android.systemui.SysuiTestCase
import com.android.systemui.communal.data.repository.fakeCommunalWidgetRepository
import com.android.systemui.communal.domain.interactor.communalInteractor
-import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.flags.Flags
import com.android.systemui.flags.fakeFeatureFlagsClassic
@@ -38,7 +34,6 @@ import com.android.systemui.kosmos.testScope
import com.android.systemui.settings.fakeUserTracker
import com.android.systemui.testKosmos
import com.android.systemui.user.data.repository.fakeUserRepository
-import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.settings.fakeSettings
import com.google.common.truth.Truth.assertThat
@@ -172,26 +167,23 @@ class CommunalAppWidgetHostStartableTest : SysuiTestCase() {
with(kosmos) {
testScope.runTest {
// Set up communal widgets
- val widget1 =
- mock<CommunalWidgetContentModel.Available> {
- whenever(this.appWidgetId).thenReturn(1)
- }
- val widget2 =
- mock<CommunalWidgetContentModel.Available> {
- whenever(this.appWidgetId).thenReturn(2)
- }
- val widget3 =
- mock<CommunalWidgetContentModel.Available> {
- whenever(this.appWidgetId).thenReturn(3)
- }
- fakeCommunalWidgetRepository.setCommunalWidgets(listOf(widget1, widget2, widget3))
+ fakeCommunalWidgetRepository.addWidget(appWidgetId = 1)
+ fakeCommunalWidgetRepository.addWidget(appWidgetId = 2)
+ fakeCommunalWidgetRepository.addWidget(appWidgetId = 3)
underTest.start()
// Assert communal widgets has 3
val communalWidgets by
collectLastValue(fakeCommunalWidgetRepository.communalWidgets)
- assertThat(communalWidgets).containsExactly(widget1, widget2, widget3)
+ assertThat(communalWidgets).hasSize(3)
+
+ val widget1 = communalWidgets!![0]
+ val widget2 = communalWidgets!![1]
+ val widget3 = communalWidgets!![2]
+ assertThat(widget1.appWidgetId).isEqualTo(1)
+ assertThat(widget2.appWidgetId).isEqualTo(2)
+ assertThat(widget3.appWidgetId).isEqualTo(3)
// Report app widget 1 to remove and assert widget removed
appWidgetIdToRemove.emit(1)
@@ -216,18 +208,26 @@ class CommunalAppWidgetHostStartableTest : SysuiTestCase() {
selectedUserIndex = 0,
)
// One work widget, one pending work widget, and one personal widget.
- val widget1 = createWidgetForUser(1, USER_INFO_WORK.id)
- val widget2 = createPendingWidgetForUser(2, USER_INFO_WORK.id)
- val widget3 = createWidgetForUser(3, MAIN_USER_INFO.id)
- val widgets = listOf(widget1, widget2, widget3)
- fakeCommunalWidgetRepository.setCommunalWidgets(widgets)
+ fakeCommunalWidgetRepository.addWidget(appWidgetId = 1, userId = USER_INFO_WORK.id)
+ fakeCommunalWidgetRepository.addPendingWidget(
+ appWidgetId = 2,
+ userId = USER_INFO_WORK.id
+ )
+ fakeCommunalWidgetRepository.addWidget(appWidgetId = 3, userId = MAIN_USER_INFO.id)
underTest.start()
runCurrent()
val communalWidgets by
collectLastValue(fakeCommunalWidgetRepository.communalWidgets)
- assertThat(communalWidgets).containsExactly(widget1, widget2, widget3)
+ assertThat(communalWidgets).hasSize(3)
+
+ val widget1 = communalWidgets!![0]
+ val widget2 = communalWidgets!![1]
+ val widget3 = communalWidgets!![2]
+ assertThat(widget1.appWidgetId).isEqualTo(1)
+ assertThat(widget2.appWidgetId).isEqualTo(2)
+ assertThat(widget3.appWidgetId).isEqualTo(3)
// Unlock the device and remove work profile.
fakeKeyguardRepository.setKeyguardShowing(false)
@@ -259,32 +259,6 @@ class CommunalAppWidgetHostStartableTest : SysuiTestCase() {
)
}
- private fun createWidgetForUser(
- appWidgetId: Int,
- userId: Int
- ): CommunalWidgetContentModel.Available =
- mock<CommunalWidgetContentModel.Available> {
- whenever(this.appWidgetId).thenReturn(appWidgetId)
- val providerInfo = mock<AppWidgetProviderInfo>()
- whenever(providerInfo.profile).thenReturn(UserHandle(userId))
- whenever(this.providerInfo).thenReturn(providerInfo)
- }
-
- private fun createPendingWidgetForUser(
- appWidgetId: Int,
- userId: Int,
- priority: Int = 0,
- packageName: String = "",
- icon: Bitmap? = null,
- ): CommunalWidgetContentModel.Pending =
- CommunalWidgetContentModel.Pending(
- appWidgetId = appWidgetId,
- priority = priority,
- packageName = packageName,
- icon = icon,
- user = UserHandle(userId),
- )
-
private companion object {
val MAIN_USER_INFO = UserInfo(0, "primary", UserInfo.FLAG_MAIN)
val USER_INFO_WORK = UserInfo(10, "work", UserInfo.FLAG_PROFILE)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
index 42cd5ec69466..3fd1c20c0560 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
@@ -2230,11 +2230,13 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest
fun glanceableHubToDreaming_communalKtfRefactor() =
testScope.runTest {
// GIVEN that we are dreaming and not dozing
+ powerInteractor.setAwakeForTest()
keyguardRepository.setDreaming(true)
+ keyguardRepository.setDreamingWithOverlay(true)
keyguardRepository.setDozeTransitionModel(
DozeTransitionModel(from = DozeStateModel.DOZE, to = DozeStateModel.FINISH)
)
- runCurrent()
+ advanceTimeBy(100L)
// GIVEN a prior transition has run to GLANCEABLE_HUB
communalSceneInteractor.changeScene(CommunalScenes.Communal)
diff --git a/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt b/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt
index e9b23850627a..6b7712d9364e 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt
@@ -20,6 +20,7 @@ import android.provider.Settings
import com.android.compose.animation.scene.SceneKey
import com.android.compose.animation.scene.TransitionKey
import com.android.systemui.CoreStartable
+import com.android.systemui.Flags.communalSceneKtfRefactor
import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor
@@ -96,20 +97,22 @@ constructor(
return
}
- // Handle automatically switching based on keyguard state.
- keyguardTransitionInteractor.startedKeyguardTransitionStep
- .mapLatest(::determineSceneAfterTransition)
- .filterNotNull()
- .onEach { (nextScene, nextTransition) ->
- if (!communalSceneInteractor.isLaunchingWidget.value) {
- // When launching a widget, we don't want to animate the scene change or the
- // Communal Hub will reveal the wallpaper even though it shouldn't. Instead we
- // snap to the new scene as part of the launch animation, once the activity
- // launch is done, so we don't change scene here.
- communalSceneInteractor.changeScene(nextScene, nextTransition)
+ if (!communalSceneKtfRefactor()) {
+ // Handle automatically switching based on keyguard state.
+ keyguardTransitionInteractor.startedKeyguardTransitionStep
+ .mapLatest(::determineSceneAfterTransition)
+ .filterNotNull()
+ .onEach { (nextScene, nextTransition) ->
+ if (!communalSceneInteractor.isLaunchingWidget.value) {
+ // When launching a widget, we don't want to animate the scene change or the
+ // Communal Hub will reveal the wallpaper even though it shouldn't. Instead
+ // we snap to the new scene as part of the launch animation, once the
+ // activity launch is done, so we don't change scene here.
+ communalSceneInteractor.changeScene(nextScene, nextTransition)
+ }
}
- }
- .launchIn(applicationScope)
+ .launchIn(applicationScope)
+ }
// TODO(b/322787129): re-enable once custom animations are in place
// Handle automatically switching to communal when docked.
diff --git a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt
index a44533555a04..ba2b7bf96a30 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt
@@ -28,6 +28,8 @@ import com.android.systemui.communal.data.repository.CommunalSmartspaceRepositor
import com.android.systemui.communal.data.repository.CommunalTutorialRepositoryModule
import com.android.systemui.communal.data.repository.CommunalWidgetRepositoryModule
import com.android.systemui.communal.domain.interactor.CommunalSceneTransitionInteractor
+import com.android.systemui.communal.shared.log.CommunalMetricsLogger
+import com.android.systemui.communal.shared.log.CommunalStatsLogProxyImpl
import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.communal.util.CommunalColors
import com.android.systemui.communal.util.CommunalColorsImpl
@@ -44,6 +46,7 @@ import dagger.Module
import dagger.Provides
import dagger.multibindings.ClassKey
import dagger.multibindings.IntoMap
+import javax.inject.Named
import kotlinx.coroutines.CoroutineScope
@Module(
@@ -74,6 +77,11 @@ interface CommunalModule {
@Binds fun bindCommunalColors(impl: CommunalColorsImpl): CommunalColors
@Binds
+ fun bindCommunalStatsLogProxy(
+ impl: CommunalStatsLogProxyImpl
+ ): CommunalMetricsLogger.StatsLogProxy
+
+ @Binds
@IntoMap
@ClassKey(CommunalSceneTransitionInteractor::class)
abstract fun bindCommunalSceneTransitionInteractor(
@@ -81,6 +89,8 @@ interface CommunalModule {
): CoreStartable
companion object {
+ const val LOGGABLE_PREFIXES = "loggable_prefixes"
+
@Provides
@Communal
@SysUISingleton
@@ -107,5 +117,14 @@ interface CommunalModule {
): CommunalBackupUtils {
return CommunalBackupUtils(context)
}
+
+ /** The prefixes of widgets packages names that are considered loggable. */
+ @Provides
+ @Named(LOGGABLE_PREFIXES)
+ fun provideLoggablePrefixes(@Application context: Context): List<String> {
+ return context.resources
+ .getStringArray(com.android.internal.R.array.config_loggable_dream_prefixes)
+ .toList()
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt
index e65e5e5688f5..ad0bfc76ebbf 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt
@@ -398,16 +398,13 @@ constructor(
)
}
- val session =
- installSessions.firstOrNull {
- it.packageName ==
- ComponentName.unflattenFromString(entry.componentName)?.packageName
- }
- return if (session != null) {
+ val componentName = ComponentName.unflattenFromString(entry.componentName)
+ val session = installSessions.firstOrNull { it.packageName == componentName?.packageName }
+ return if (componentName != null && session != null) {
CommunalWidgetContentModel.Pending(
appWidgetId = entry.appWidgetId,
priority = entry.priority,
- packageName = session.packageName,
+ componentName = componentName,
icon = session.icon,
user = session.user,
)
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
index dbddc23d6146..6aaaf3d4ce9a 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
@@ -433,6 +433,7 @@ constructor(
is CommunalWidgetContentModel.Available -> {
WidgetContent.Widget(
appWidgetId = widget.appWidgetId,
+ priority = widget.priority,
providerInfo = widget.providerInfo,
appWidgetHost = appWidgetHost,
inQuietMode = isQuietModeEnabled(widget.providerInfo.profile)
@@ -441,7 +442,8 @@ constructor(
is CommunalWidgetContentModel.Pending -> {
WidgetContent.PendingWidget(
appWidgetId = widget.appWidgetId,
- packageName = widget.packageName,
+ priority = widget.priority,
+ componentName = widget.componentName,
icon = widget.icon,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractor.kt
index 45cfe3673f3d..e45a69599a68 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractor.kt
@@ -28,6 +28,7 @@ import com.android.systemui.communal.shared.model.EditModeState
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.util.kotlin.BooleanFlowOperators.allOf
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -184,6 +185,10 @@ constructor(
initialValue = false,
)
+ /** This flow will be true when idle on the hub and not transitioning to edit mode. */
+ val isIdleOnCommunalNotEditMode: Flow<Boolean> =
+ allOf(isIdleOnCommunal, editModeState.map { it == null })
+
/**
* Flow that emits a boolean if any portion of the communal UI is visible at all.
*
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractor.kt
index 8351566fcae6..6a20610da3a6 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractor.kt
@@ -85,16 +85,16 @@ constructor(
*/
private val nextKeyguardStateInternal =
combine(
- keyguardInteractor.isDreaming,
+ keyguardInteractor.isAbleToDream,
keyguardInteractor.isKeyguardOccluded,
keyguardInteractor.isKeyguardGoingAway,
) { dreaming, occluded, keyguardGoingAway ->
if (keyguardGoingAway) {
KeyguardState.GONE
+ } else if (occluded && !dreaming) {
+ KeyguardState.OCCLUDED
} else if (dreaming) {
KeyguardState.DREAMING
- } else if (occluded) {
- KeyguardState.OCCLUDED
} else {
KeyguardState.LOCKSCREEN
}
@@ -162,10 +162,13 @@ constructor(
// We may receive an Idle event without a corresponding Transition
// event, such as when snapping to a scene without an animation.
val targetState =
- if (idle.currentScene == CommunalScenes.Blank) {
+ if (idle.currentScene == CommunalScenes.Communal) {
+ KeyguardState.GLANCEABLE_HUB
+ } else if (currentToState == KeyguardState.GLANCEABLE_HUB) {
nextKeyguardState.value
} else {
- KeyguardState.GLANCEABLE_HUB
+ // Do nothing as we are no longer in the hub state.
+ return
}
transitionKtfTo(targetState)
repository.nextLockscreenTargetState.value = null
@@ -188,7 +191,7 @@ constructor(
from = internalTransitionInteractor.currentTransitionInfoInternal.value.to,
to = state,
animator = null,
- modeOnCanceled = TransitionModeOnCanceled.REVERSE
+ modeOnCanceled = TransitionModeOnCanceled.REVERSE,
)
currentTransitionId = internalTransitionInteractor.startTransition(newTransition)
internalTransitionInteractor.updateTransition(
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt
index 122240daed52..73c6ce30b202 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt
@@ -18,6 +18,7 @@ package com.android.systemui.communal.domain.model
import android.appwidget.AppWidgetProviderInfo
import android.appwidget.AppWidgetProviderInfo.WIDGET_FEATURE_RECONFIGURABLE
+import android.content.ComponentName
import android.content.pm.ApplicationInfo
import android.graphics.Bitmap
import android.widget.RemoteViews
@@ -46,14 +47,18 @@ sealed interface CommunalContentModel {
sealed interface WidgetContent : CommunalContentModel {
val appWidgetId: Int
+ val priority: Int
+ val componentName: ComponentName
data class Widget(
override val appWidgetId: Int,
+ override val priority: Int,
val providerInfo: AppWidgetProviderInfo,
val appWidgetHost: CommunalAppWidgetHost,
val inQuietMode: Boolean,
) : WidgetContent {
override val key = KEY.widget(appWidgetId)
+ override val componentName: ComponentName = providerInfo.provider
// Widget size is always half.
override val size = CommunalContentSize.HALF
@@ -66,9 +71,11 @@ sealed interface CommunalContentModel {
data class DisabledWidget(
override val appWidgetId: Int,
+ override val priority: Int,
val providerInfo: AppWidgetProviderInfo
) : WidgetContent {
override val key = KEY.disabledWidget(appWidgetId)
+ override val componentName: ComponentName = providerInfo.provider
// Widget size is always half.
override val size = CommunalContentSize.HALF
@@ -78,7 +85,8 @@ sealed interface CommunalContentModel {
data class PendingWidget(
override val appWidgetId: Int,
- val packageName: String,
+ override val priority: Int,
+ override val componentName: ComponentName,
val icon: Bitmap? = null,
) : WidgetContent {
override val key = KEY.pendingWidget(appWidgetId)
diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/log/CommunalMetricsLogger.kt b/packages/SystemUI/src/com/android/systemui/communal/shared/log/CommunalMetricsLogger.kt
new file mode 100644
index 000000000000..12099f708b9e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/shared/log/CommunalMetricsLogger.kt
@@ -0,0 +1,89 @@
+/*
+ * 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.communal.shared.log
+
+import com.android.systemui.communal.dagger.CommunalModule.Companion.LOGGABLE_PREFIXES
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.shared.system.SysUiStatsLog
+import javax.inject.Inject
+import javax.inject.Named
+
+@SysUISingleton
+class CommunalMetricsLogger
+@Inject
+constructor(
+ @Named(LOGGABLE_PREFIXES) private val loggablePrefixes: List<String>,
+ private val statsLogProxy: StatsLogProxy,
+) {
+ /** Logs an add widget event for metrics. No-op if widget is not loggable. */
+ fun logAddWidget(componentName: String, rank: Int) {
+ if (!componentName.isLoggable()) {
+ return
+ }
+
+ statsLogProxy.writeCommunalHubWidgetEventReported(
+ SysUiStatsLog.COMMUNAL_HUB_WIDGET_EVENT_REPORTED__ACTION__ADD,
+ componentName,
+ rank,
+ )
+ }
+
+ /** Logs a remove widget event for metrics. No-op if widget is not loggable. */
+ fun logRemoveWidget(componentName: String, rank: Int) {
+ if (!componentName.isLoggable()) {
+ return
+ }
+
+ statsLogProxy.writeCommunalHubWidgetEventReported(
+ SysUiStatsLog.COMMUNAL_HUB_WIDGET_EVENT_REPORTED__ACTION__REMOVE,
+ componentName,
+ rank,
+ )
+ }
+
+ /** Whether the component name matches any of the loggable prefixes. */
+ private fun String.isLoggable(): Boolean {
+ return loggablePrefixes.any { loggablePrefix -> startsWith(loggablePrefix) }
+ }
+
+ /** Proxy of [SysUiStatsLog] for testing purpose. */
+ interface StatsLogProxy {
+ /** Logs a [SysUiStatsLog.COMMUNAL_HUB_WIDGET_EVENT_REPORTED] stats event. */
+ fun writeCommunalHubWidgetEventReported(
+ action: Int,
+ componentName: String,
+ rank: Int,
+ )
+ }
+}
+
+/** Redirects calls to [SysUiStatsLog]. */
+@SysUISingleton
+class CommunalStatsLogProxyImpl @Inject constructor() : CommunalMetricsLogger.StatsLogProxy {
+ override fun writeCommunalHubWidgetEventReported(
+ action: Int,
+ componentName: String,
+ rank: Int,
+ ) {
+ SysUiStatsLog.write(
+ SysUiStatsLog.COMMUNAL_HUB_WIDGET_EVENT_REPORTED,
+ action,
+ componentName,
+ rank,
+ )
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/log/CommunalUiEvent.kt b/packages/SystemUI/src/com/android/systemui/communal/shared/log/CommunalUiEvent.kt
index b64c1955b43d..4ab56cc34628 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/shared/log/CommunalUiEvent.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/shared/log/CommunalUiEvent.kt
@@ -53,7 +53,15 @@ enum class CommunalUiEvent(private val id: Int) : UiEventEnum {
@UiEvent(doc = "User performs a swipe up gesture from bottom to enter bouncer")
COMMUNAL_HUB_SWIPE_UP_TO_BOUNCER(1573),
@UiEvent(doc = "User performs a swipe down gesture from top to enter shade")
- COMMUNAL_HUB_SWIPE_DOWN_TO_SHADE(1574);
+ COMMUNAL_HUB_SWIPE_DOWN_TO_SHADE(1574),
+ @UiEvent(doc = "User performs a tap gesture on the UMO in Communal Hub")
+ COMMUNAL_HUB_UMO_TAP(1858),
+ @UiEvent(
+ doc =
+ "A transition from dream to Communal Hub starts. This can be triggered by a tap on " +
+ "the dream."
+ )
+ FROM_DREAM_TO_COMMUNAL_HUB_TRANSITION_START(1859);
override fun getId(): Int {
return id
diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalWidgetContentModel.kt b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalWidgetContentModel.kt
index 53aecc199c4b..7cddb7226601 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalWidgetContentModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalWidgetContentModel.kt
@@ -17,6 +17,7 @@
package com.android.systemui.communal.shared.model
import android.appwidget.AppWidgetProviderInfo
+import android.content.ComponentName
import android.graphics.Bitmap
import android.os.UserHandle
@@ -36,7 +37,7 @@ sealed interface CommunalWidgetContentModel {
data class Pending(
override val appWidgetId: Int,
override val priority: Int,
- val packageName: String,
+ val componentName: ComponentName,
val icon: Bitmap?,
val user: UserHandle,
) : CommunalWidgetContentModel
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
index 01ed2b71bea0..623e702a85fe 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
@@ -99,18 +99,6 @@ abstract class BaseCommunalViewModel(
communalSceneInteractor.setTransitionState(transitionState)
}
- /**
- * Called when a widget is added via drag and drop from the widget picker into the communal hub.
- */
- open fun onAddWidget(
- componentName: ComponentName,
- user: UserHandle,
- priority: Int,
- configurator: WidgetConfigurator? = null
- ) {
- communalInteractor.addWidget(componentName, user, priority, configurator)
- }
-
open fun onOpenEnableWidgetDialog() {}
open fun onOpenEnableWorkProfileDialog() {}
@@ -136,8 +124,20 @@ abstract class BaseCommunalViewModel(
/** Called as the UI request to dismiss the any displaying popup */
open fun onHidePopup() {}
+ /** Called as the UI requests adding a widget. */
+ open fun onAddWidget(
+ componentName: ComponentName,
+ user: UserHandle,
+ priority: Int,
+ configurator: WidgetConfigurator? = null,
+ ) {}
+
/** Called as the UI requests deleting a widget. */
- open fun onDeleteWidget(id: Int) {}
+ open fun onDeleteWidget(
+ id: Int,
+ componentName: ComponentName,
+ priority: Int,
+ ) {}
/**
* Called as the UI requests reordering widgets.
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
index 830f543fd06f..5b825d80d9b1 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
@@ -18,9 +18,11 @@ package com.android.systemui.communal.ui.viewmodel
import android.appwidget.AppWidgetManager
import android.appwidget.AppWidgetProviderInfo
+import android.content.ComponentName
import android.content.Intent
import android.content.pm.PackageManager
import android.content.res.Resources
+import android.os.UserHandle
import android.util.Log
import androidx.activity.result.ActivityResultLauncher
import com.android.internal.logging.UiEventLogger
@@ -30,8 +32,10 @@ import com.android.systemui.communal.domain.interactor.CommunalPrefsInteractor
import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor
import com.android.systemui.communal.domain.model.CommunalContentModel
+import com.android.systemui.communal.shared.log.CommunalMetricsLogger
import com.android.systemui.communal.shared.log.CommunalUiEvent
import com.android.systemui.communal.shared.model.EditModeState
+import com.android.systemui.communal.widgets.WidgetConfigurator
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
@@ -71,6 +75,7 @@ constructor(
@CommunalLog logBuffer: LogBuffer,
@Background private val backgroundDispatcher: CoroutineDispatcher,
private val communalPrefsInteractor: CommunalPrefsInteractor,
+ private val metricsLogger: CommunalMetricsLogger,
) : BaseCommunalViewModel(communalSceneInteractor, communalInteractor, mediaHost) {
private val logger = Logger(logBuffer, "CommunalEditModeViewModel")
@@ -112,7 +117,24 @@ constructor(
override val reorderingWidgets: StateFlow<Boolean>
get() = _reorderingWidgets
- override fun onDeleteWidget(id: Int) = communalInteractor.deleteWidget(id)
+ override fun onAddWidget(
+ componentName: ComponentName,
+ user: UserHandle,
+ priority: Int,
+ configurator: WidgetConfigurator?
+ ) {
+ communalInteractor.addWidget(componentName, user, priority, configurator)
+ metricsLogger.logAddWidget(componentName.flattenToString(), priority)
+ }
+
+ override fun onDeleteWidget(
+ id: Int,
+ componentName: ComponentName,
+ priority: Int,
+ ) {
+ communalInteractor.deleteWidget(id)
+ metricsLogger.logRemoveWidget(componentName.flattenToString(), priority)
+ }
override fun onReorderWidgets(widgetIdToPriorityMap: Map<Int, Int>) =
communalInteractor.updateWidgetOrder(widgetIdToPriorityMap)
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
index 08fe42ede5d3..398576935eed 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
@@ -41,6 +41,7 @@ import com.android.systemui.communal.shared.model.EditModeState
import com.android.systemui.communal.ui.compose.CommunalHub
import com.android.systemui.communal.ui.viewmodel.CommunalEditModeViewModel
import com.android.systemui.communal.util.WidgetPickerIntentUtils.getWidgetExtraFromIntent
+import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.core.Logger
import com.android.systemui.log.dagger.CommunalLog
@@ -146,7 +147,8 @@ constructor(
communalViewModel.canShowEditMode.collect {
communalViewModel.changeScene(
CommunalScenes.Blank,
- CommunalTransitionKeys.ToEditMode
+ CommunalTransitionKeys.ToEditMode,
+ KeyguardState.GONE,
)
// wait till transitioned to Blank scene, then animate in communal content in
// edit mode
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt
index d81195060071..6b1be93c988a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt
@@ -24,6 +24,7 @@ import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor
import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.communal.shared.model.CommunalTransitionKeys
+import com.android.systemui.communal.shared.model.EditModeState
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
@@ -36,6 +37,7 @@ import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.util.kotlin.BooleanFlowOperators.allOf
import com.android.systemui.util.kotlin.BooleanFlowOperators.noneOf
import com.android.systemui.util.kotlin.BooleanFlowOperators.not
+import com.android.systemui.util.kotlin.sample
import javax.inject.Inject
import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.Duration.Companion.seconds
@@ -240,12 +242,21 @@ constructor(
),
)
.filterRelevantKeyguardStateAnd { isKeyguardGoingAway -> isKeyguardGoingAway }
- .collect {
- communalSceneInteractor.changeScene(
- newScene = CommunalScenes.Blank,
- transitionKey = CommunalTransitionKeys.SimpleFade,
- keyguardState = KeyguardState.GONE
- )
+ .sample(communalSceneInteractor.editModeState, ::Pair)
+ .collect { (_, editModeState) ->
+ if (
+ editModeState == EditModeState.STARTING ||
+ editModeState == EditModeState.SHOWING
+ ) {
+ // Don't change scenes here as that is handled by the edit activity.
+ startTransitionTo(KeyguardState.GONE)
+ } else {
+ communalSceneInteractor.changeScene(
+ newScene = CommunalScenes.Blank,
+ transitionKey = CommunalTransitionKeys.SimpleFade,
+ keyguardState = KeyguardState.GONE
+ )
+ }
}
}
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
index b084824cd348..ef76f3837889 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
@@ -19,7 +19,7 @@ package com.android.systemui.keyguard.domain.interactor
import android.animation.ValueAnimator
import com.android.app.animation.Interpolators
import com.android.app.tracing.coroutines.launch
-import com.android.systemui.communal.domain.interactor.CommunalInteractor
+import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
@@ -52,7 +52,7 @@ constructor(
@Main mainDispatcher: CoroutineDispatcher,
keyguardInteractor: KeyguardInteractor,
powerInteractor: PowerInteractor,
- private val communalInteractor: CommunalInteractor,
+ private val communalSceneInteractor: CommunalSceneInteractor,
keyguardOcclusionInteractor: KeyguardOcclusionInteractor,
private val biometricSettingsRepository: BiometricSettingsRepository,
private val keyguardRepository: KeyguardRepository,
@@ -88,7 +88,7 @@ constructor(
biometricSettingsRepository.isCurrentUserInLockdown
.distinctUntilChanged()
.filterRelevantKeyguardStateAnd { inLockdown -> inLockdown }
- .sample(communalInteractor.isIdleOnCommunal, ::Pair)
+ .sample(communalSceneInteractor.isIdleOnCommunalNotEditMode, ::Pair)
.collect { (_, isIdleOnCommunal) ->
val to =
if (isIdleOnCommunal) {
@@ -120,7 +120,7 @@ constructor(
scope.launch("$TAG#listenForGoneToLockscreenOrHub") {
keyguardInteractor.isKeyguardShowing
.filterRelevantKeyguardStateAnd { isKeyguardShowing -> isKeyguardShowing }
- .sample(communalInteractor.isIdleOnCommunal, ::Pair)
+ .sample(communalSceneInteractor.isIdleOnCommunalNotEditMode, ::Pair)
.collect { (_, isIdleOnCommunal) ->
val to =
if (isIdleOnCommunal) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
index 6c89ce055470..9adcaa229ae2 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
@@ -18,7 +18,9 @@ package com.android.systemui.keyguard.domain.interactor
import android.animation.ValueAnimator
import com.android.keyguard.KeyguardSecurityModel
-import com.android.systemui.communal.domain.interactor.CommunalInteractor
+import com.android.systemui.Flags.communalSceneKtfRefactor
+import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
+import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
@@ -55,7 +57,7 @@ constructor(
@Background bgDispatcher: CoroutineDispatcher,
@Main mainDispatcher: CoroutineDispatcher,
keyguardInteractor: KeyguardInteractor,
- private val communalInteractor: CommunalInteractor,
+ private val communalSceneInteractor: CommunalSceneInteractor,
private val keyguardSecurityModel: KeyguardSecurityModel,
private val selectedUserInteractor: SelectedUserInteractor,
powerInteractor: PowerInteractor,
@@ -94,7 +96,10 @@ constructor(
.distinctUntilChanged()
fun dismissPrimaryBouncer() {
- scope.launch { startTransitionTo(KeyguardState.GONE) }
+ scope.launch {
+ startTransitionTo(KeyguardState.GONE)
+ closeHubImmediatelyIfNeeded()
+ }
}
private fun listenForPrimaryBouncerToLockscreenHubOrOccluded() {
@@ -106,7 +111,7 @@ constructor(
.sample(
powerInteractor.isAwake,
keyguardInteractor.isActiveDreamLockscreenHosted,
- communalInteractor.isIdleOnCommunal
+ communalSceneInteractor.isIdleOnCommunal
)
.filterRelevantKeyguardState()
.collect {
@@ -135,7 +140,7 @@ constructor(
keyguardInteractor.isKeyguardOccluded,
keyguardInteractor.isDreaming,
keyguardInteractor.isActiveDreamLockscreenHosted,
- communalInteractor.isIdleOnCommunal,
+ communalSceneInteractor.isIdleOnCommunal,
)
.filterRelevantKeyguardStateAnd {
(isBouncerShowing, isAwake, _, _, isActiveDreamLockscreenHosted, _) ->
@@ -158,6 +163,19 @@ constructor(
}
}
+ private fun closeHubImmediatelyIfNeeded() {
+ // If the hub is showing, and we are not animating a widget launch nor transitioning to
+ // edit mode, then close the hub immediately.
+ if (
+ communalSceneKtfRefactor() &&
+ communalSceneInteractor.isIdleOnCommunal.value &&
+ !communalSceneInteractor.isLaunchingWidget.value &&
+ communalSceneInteractor.editModeState.value == null
+ ) {
+ communalSceneInteractor.snapToScene(CommunalScenes.Blank)
+ }
+ }
+
private fun listenForPrimaryBouncerToAsleep() {
// TODO(b/336576536): Check if adaptation for scene framework is needed
if (SceneContainerFlag.isEnabled) return
@@ -212,6 +230,7 @@ constructor(
},
modeOnCanceled = TransitionModeOnCanceled.RESET,
)
+ closeHubImmediatelyIfNeeded()
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java
index 696298e82db9..0f6f03ae8334 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java
@@ -143,9 +143,12 @@ public class VisualStabilityCoordinator implements Coordinator, Dumpable {
this::onLaunchingActivityChanged);
mJavaAdapter.alwaysCollectFlow(mCommunalInteractor.isIdleOnCommunal(),
this::onCommunalShowingChanged);
- mJavaAdapter.alwaysCollectFlow(mKeyguardTransitionInteractor.transitionValue(
- KeyguardState.LOCKSCREEN),
- this::onLockscreenKeyguardStateTransitionValueChanged);
+
+ if (SceneContainerFlag.isEnabled()) {
+ mJavaAdapter.alwaysCollectFlow(mKeyguardTransitionInteractor.transitionValue(
+ KeyguardState.LOCKSCREEN),
+ this::onLockscreenKeyguardStateTransitionValueChanged);
+ }
pipeline.setVisualStabilityManager(mNotifStabilityManager);
}
@@ -381,6 +384,10 @@ public class VisualStabilityCoordinator implements Coordinator, Dumpable {
}
final boolean isShowing = value > 0.0f;
+ if (isShowing == mLockscreenShowing) {
+ return;
+ }
+
mLockscreenShowing = isShowing;
updateAllowedStates("lockscreenShowing", isShowing);
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt
index f7ce367ebb18..c00454f3bc48 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt
@@ -2,6 +2,9 @@ package com.android.systemui.communal.data.repository
import android.appwidget.AppWidgetProviderInfo
import android.content.ComponentName
+import android.content.pm.ActivityInfo
+import android.content.pm.ApplicationInfo
+import android.graphics.Bitmap
import android.os.UserHandle
import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
import com.android.systemui.communal.widgets.WidgetConfigurator
@@ -13,6 +16,8 @@ import kotlinx.coroutines.launch
/** Fake implementation of [CommunalWidgetRepository] */
class FakeCommunalWidgetRepository(private val coroutineScope: CoroutineScope) :
CommunalWidgetRepository {
+ private val fakeDatabase = mutableMapOf<Int, CommunalWidgetContentModel>()
+
private val _communalWidgets = MutableStateFlow<List<CommunalWidgetContentModel>>(emptyList())
override val communalWidgets: Flow<List<CommunalWidgetContentModel>> = _communalWidgets
@@ -38,12 +43,54 @@ class FakeCommunalWidgetRepository(private val coroutineScope: CoroutineScope) :
}
}
- override fun deleteWidget(widgetId: Int) {
- if (_communalWidgets.value.none { it.appWidgetId == widgetId }) {
- return
- }
+ fun addWidget(
+ appWidgetId: Int,
+ componentName: String = "pkg/cls",
+ priority: Int = 0,
+ category: Int = AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD,
+ userId: Int = 0,
+ ) {
+ fakeDatabase[appWidgetId] =
+ CommunalWidgetContentModel.Available(
+ appWidgetId = appWidgetId,
+ priority = priority,
+ providerInfo =
+ AppWidgetProviderInfo().apply {
+ provider = ComponentName.unflattenFromString(componentName)!!
+ widgetCategory = category
+ providerInfo =
+ ActivityInfo().apply {
+ applicationInfo =
+ ApplicationInfo().apply {
+ uid = userId * UserHandle.PER_USER_RANGE
+ }
+ }
+ },
+ )
+ _communalWidgets.value = fakeDatabase.values.toList()
+ }
- _communalWidgets.value = _communalWidgets.value.filter { it.appWidgetId != widgetId }
+ fun addPendingWidget(
+ appWidgetId: Int,
+ componentName: String = "pkg/cls",
+ priority: Int = 0,
+ icon: Bitmap? = null,
+ userId: Int = 0,
+ ) {
+ fakeDatabase[appWidgetId] =
+ CommunalWidgetContentModel.Pending(
+ appWidgetId = appWidgetId,
+ priority = priority,
+ componentName = ComponentName.unflattenFromString(componentName)!!,
+ icon = icon,
+ user = UserHandle(userId),
+ )
+ _communalWidgets.value = fakeDatabase.values.toList()
+ }
+
+ override fun deleteWidget(widgetId: Int) {
+ fakeDatabase.remove(widgetId)
+ _communalWidgets.value = fakeDatabase.values.toList()
}
override fun restoreWidgets(oldToNewWidgetIdMap: Map<Int, Int>) {}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractorKosmos.kt
index 317294f3c884..c694114a0f47 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractorKosmos.kt
@@ -16,7 +16,7 @@
package com.android.systemui.keyguard.domain.interactor
-import com.android.systemui.communal.domain.interactor.communalInteractor
+import com.android.systemui.communal.domain.interactor.communalSceneInteractor
import com.android.systemui.keyguard.data.repository.biometricSettingsRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.data.repository.keyguardRepository
@@ -37,7 +37,7 @@ val Kosmos.fromGoneTransitionInteractor by
mainDispatcher = testDispatcher,
keyguardInteractor = keyguardInteractor,
powerInteractor = powerInteractor,
- communalInteractor = communalInteractor,
+ communalSceneInteractor = communalSceneInteractor,
keyguardOcclusionInteractor = keyguardOcclusionInteractor,
biometricSettingsRepository = biometricSettingsRepository,
keyguardRepository = keyguardRepository,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorKosmos.kt
index 42ee15216590..3c369d7d954f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorKosmos.kt
@@ -17,7 +17,7 @@
package com.android.systemui.keyguard.domain.interactor
import com.android.keyguard.keyguardSecurityModel
-import com.android.systemui.communal.domain.interactor.communalInteractor
+import com.android.systemui.communal.domain.interactor.communalSceneInteractor
import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
@@ -36,7 +36,7 @@ var Kosmos.fromPrimaryBouncerTransitionInteractor by
bgDispatcher = testDispatcher,
mainDispatcher = testDispatcher,
keyguardInteractor = keyguardInteractor,
- communalInteractor = communalInteractor,
+ communalSceneInteractor = communalSceneInteractor,
keyguardSecurityModel = keyguardSecurityModel,
selectedUserInteractor = selectedUserInteractor,
powerInteractor = powerInteractor,
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 8447cca26632..3d41f05de0b8 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -288,7 +288,6 @@ import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
-import java.util.concurrent.atomic.AtomicReference;
import java.util.function.BooleanSupplier;
import java.util.stream.Collectors;
diff --git a/services/core/java/com/android/server/hdmi/OneTouchPlayAction.java b/services/core/java/com/android/server/hdmi/OneTouchPlayAction.java
index 3c3cfe61c126..256905d50dc1 100644
--- a/services/core/java/com/android/server/hdmi/OneTouchPlayAction.java
+++ b/services/core/java/com/android/server/hdmi/OneTouchPlayAction.java
@@ -54,7 +54,9 @@ final class OneTouchPlayAction extends HdmiCecFeatureAction {
// The maximum number of times we send <Give Device Power Status> before we give up.
// We wait up to RESPONSE_TIMEOUT_MS * LOOP_COUNTER_MAX = 20 seconds.
- private static final int LOOP_COUNTER_MAX = 10;
+ // Every 3 timeouts we send a <Text View On> in case the TV missed it and ignored it.
+ @VisibleForTesting
+ static final int LOOP_COUNTER_MAX = 10;
private final int mTargetAddress;
private final boolean mIsCec20;
@@ -181,6 +183,7 @@ final class OneTouchPlayAction extends HdmiCecFeatureAction {
if (cmd.getOpcode() == Constants.MESSAGE_REPORT_POWER_STATUS) {
int status = cmd.getParams()[0];
if (status == HdmiControlManager.POWER_STATUS_ON) {
+ HdmiLogger.debug("TV's power status is on. Action finished successfully");
// If the device is still the active source, send the <Active Source> message
// again.
maySendActiveSource();
@@ -199,6 +202,12 @@ final class OneTouchPlayAction extends HdmiCecFeatureAction {
switch (state) {
case STATE_WAITING_FOR_REPORT_POWER_STATUS:
if (mPowerStatusCounter++ < LOOP_COUNTER_MAX) {
+ if (mPowerStatusCounter % 3 == 0) {
+ HdmiLogger.debug("Retry sending <Text View On> in case the TV "
+ + "missed the message.");
+ sendCommand(HdmiCecMessageBuilder.buildTextViewOn(getSourceAddress(),
+ mTargetAddress));
+ }
queryDevicePowerStatus();
addTimer(mState, HdmiConfig.TIMEOUT_MS);
} else {
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
index c82e5be7c643..13209d861e8b 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
@@ -27,6 +27,7 @@ import android.inputmethodservice.InputMethodService;
import android.os.IBinder;
import android.view.inputmethod.InlineSuggestionsRequest;
import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodSubtype;
import com.android.internal.inputmethod.IAccessibilityInputMethodSession;
import com.android.internal.inputmethod.InlineSuggestionsRequestCallback;
@@ -89,6 +90,8 @@ public abstract class InputMethodManagerInternal {
* @param userId the user ID to be queried
* @return a list of {@link InputMethodInfo}. VR-only IMEs are already excluded
*/
+ @ImfLockFree
+ @NonNull
public abstract List<InputMethodInfo> getInputMethodListAsUser(@UserIdInt int userId);
/**
@@ -97,9 +100,24 @@ public abstract class InputMethodManagerInternal {
* @param userId the user ID to be queried
* @return a list of {@link InputMethodInfo} that are enabled for {@code userId}
*/
+ @ImfLockFree
+ @NonNull
public abstract List<InputMethodInfo> getEnabledInputMethodListAsUser(@UserIdInt int userId);
/**
+ * Returns the list of installed input methods that are enabled for the specified user.
+ *
+ * @param imiId IME ID to be queried about
+ * @param allowsImplicitlyEnabledSubtypes {@code true} to return the implicitly enabled subtypes
+ * @param userId the user ID to be queried about
+ * @return a list of {@link InputMethodSubtype} that are enabled for {@code userId}
+ */
+ @ImfLockFree
+ @NonNull
+ public abstract List<InputMethodSubtype> getEnabledInputMethodSubtypeListAsUser(
+ String imiId, boolean allowsImplicitlyEnabledSubtypes, @UserIdInt int userId);
+
+ /**
* Called by the Autofill Frameworks to request an {@link InlineSuggestionsRequest} from
* the input method.
*
@@ -301,17 +319,29 @@ public abstract class InputMethodManagerInternal {
int originatingDisplayId) {
}
+ @ImfLockFree
+ @NonNull
@Override
public List<InputMethodInfo> getInputMethodListAsUser(@UserIdInt int userId) {
return Collections.emptyList();
}
+ @ImfLockFree
+ @NonNull
@Override
public List<InputMethodInfo> getEnabledInputMethodListAsUser(
@UserIdInt int userId) {
return Collections.emptyList();
}
+ @ImfLockFree
+ @NonNull
+ @Override
+ public List<InputMethodSubtype> getEnabledInputMethodSubtypeListAsUser(String imiId,
+ boolean allowsImplicitlyEnabledSubtypes, int userId) {
+ return Collections.emptyList();
+ }
+
@Override
public void onCreateInlineSuggestionsRequest(@UserIdInt int userId,
InlineSuggestionsRequestInfo requestInfo,
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index cbe202b5adc8..a9723ccc2576 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -1469,9 +1469,8 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl.
mContext.enforceCallingOrSelfPermission(
Manifest.permission.INTERACT_ACROSS_USERS_FULL, null);
}
- synchronized (ImfLock.class) {
- return queryDefaultInputMethodForUserIdLocked(userId);
- }
+ final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
+ return settings.getMethodMap().get(settings.getSelectedInputMethod());
}
@BinderThread
@@ -1486,15 +1485,13 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl.
if (!mUserManagerInternal.exists(userId)) {
return InputMethodInfoSafeList.empty();
}
- synchronized (ImfLock.class) {
- final int callingUid = Binder.getCallingUid();
- final long ident = Binder.clearCallingIdentity();
- try {
- return InputMethodInfoSafeList.create(getInputMethodListLocked(
- userId, directBootAwareness, callingUid));
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
+ final int callingUid = Binder.getCallingUid();
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ return InputMethodInfoSafeList.create(getInputMethodListInternal(
+ userId, directBootAwareness, callingUid));
+ } finally {
+ Binder.restoreCallingIdentity(ident);
}
}
@@ -1509,15 +1506,13 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl.
if (!mUserManagerInternal.exists(userId)) {
return InputMethodInfoSafeList.empty();
}
- synchronized (ImfLock.class) {
- final int callingUid = Binder.getCallingUid();
- final long ident = Binder.clearCallingIdentity();
- try {
- return InputMethodInfoSafeList.create(
- getEnabledInputMethodListLocked(userId, callingUid));
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
+ final int callingUid = Binder.getCallingUid();
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ return InputMethodInfoSafeList.create(
+ getEnabledInputMethodListInternal(userId, callingUid));
+ } finally {
+ Binder.restoreCallingIdentity(ident);
}
}
@@ -1533,14 +1528,12 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl.
if (!mUserManagerInternal.exists(userId)) {
return Collections.emptyList();
}
- synchronized (ImfLock.class) {
- final int callingUid = Binder.getCallingUid();
- final long ident = Binder.clearCallingIdentity();
- try {
- return getInputMethodListLocked(userId, directBootAwareness, callingUid);
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
+ final int callingUid = Binder.getCallingUid();
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ return getInputMethodListInternal(userId, directBootAwareness, callingUid);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
}
}
@@ -1555,14 +1548,12 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl.
if (!mUserManagerInternal.exists(userId)) {
return Collections.emptyList();
}
- synchronized (ImfLock.class) {
- final int callingUid = Binder.getCallingUid();
- final long ident = Binder.clearCallingIdentity();
- try {
- return getEnabledInputMethodListLocked(userId, callingUid);
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
+ final int callingUid = Binder.getCallingUid();
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ return getEnabledInputMethodListInternal(userId, callingUid);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
}
}
@@ -1606,8 +1597,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl.
return true;
}
- @GuardedBy("ImfLock.class")
- private List<InputMethodInfo> getInputMethodListLocked(@UserIdInt int userId,
+ private List<InputMethodInfo> getInputMethodListInternal(@UserIdInt int userId,
@DirectBootAwareness int directBootAwareness, int callingUid) {
final InputMethodSettings settings;
if (directBootAwareness == DirectBootAwareness.AUTO) {
@@ -1626,8 +1616,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl.
return methodList;
}
- @GuardedBy("ImfLock.class")
- private List<InputMethodInfo> getEnabledInputMethodListLocked(@UserIdInt int userId,
+ private List<InputMethodInfo> getEnabledInputMethodListInternal(@UserIdInt int userId,
int callingUid) {
final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
final ArrayList<InputMethodInfo> methodList = settings.getEnabledInputMethodList();
@@ -1654,20 +1643,17 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl.
Manifest.permission.INTERACT_ACROSS_USERS_FULL, null);
}
- synchronized (ImfLock.class) {
- final int callingUid = Binder.getCallingUid();
- final long ident = Binder.clearCallingIdentity();
- try {
- return getEnabledInputMethodSubtypeListLocked(imiId,
- allowsImplicitlyEnabledSubtypes, userId, callingUid);
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
+ final int callingUid = Binder.getCallingUid();
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ return getEnabledInputMethodSubtypeListInternal(imiId,
+ allowsImplicitlyEnabledSubtypes, userId, callingUid);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
}
}
- @GuardedBy("ImfLock.class")
- private List<InputMethodSubtype> getEnabledInputMethodSubtypeListLocked(String imiId,
+ private List<InputMethodSubtype> getEnabledInputMethodSubtypeListInternal(String imiId,
boolean allowsImplicitlyEnabledSubtypes, @UserIdInt int userId, int callingUid) {
final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
final InputMethodInfo imi = settings.getMethodMap().get(imiId);
@@ -5703,17 +5689,6 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl.
}
}
- /**
- * Returns the default {@link InputMethodInfo} for the specific userId.
- *
- * @param userId user ID to query
- */
- @GuardedBy("ImfLock.class")
- private InputMethodInfo queryDefaultInputMethodForUserIdLocked(@UserIdInt int userId) {
- final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
- return settings.getMethodMap().get(settings.getSelectedInputMethod());
- }
-
@GuardedBy("ImfLock.class")
private boolean switchToInputMethodLocked(@NonNull String imeId, int subtypeId,
@UserIdInt int userId) {
@@ -5850,19 +5825,27 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl.
mHandler.obtainMessage(MSG_HIDE_ALL_INPUT_METHODS, reason).sendToTarget();
}
+ @ImfLockFree
+ @NonNull
@Override
public List<InputMethodInfo> getInputMethodListAsUser(@UserIdInt int userId) {
- synchronized (ImfLock.class) {
- return getInputMethodListLocked(userId, DirectBootAwareness.AUTO,
- Process.SYSTEM_UID);
- }
+ return getInputMethodListInternal(userId, DirectBootAwareness.AUTO, Process.SYSTEM_UID);
}
+ @ImfLockFree
+ @NonNull
@Override
public List<InputMethodInfo> getEnabledInputMethodListAsUser(@UserIdInt int userId) {
- synchronized (ImfLock.class) {
- return getEnabledInputMethodListLocked(userId, Process.SYSTEM_UID);
- }
+ return getEnabledInputMethodListInternal(userId, Process.SYSTEM_UID);
+ }
+
+ @ImfLockFree
+ @NonNull
+ @Override
+ public List<InputMethodSubtype> getEnabledInputMethodSubtypeListAsUser(
+ String imiId, boolean allowsImplicitlyEnabledSubtypes, @UserIdInt int userId) {
+ return getEnabledInputMethodSubtypeListInternal(imiId, allowsImplicitlyEnabledSubtypes,
+ userId, Process.SYSTEM_UID);
}
@Override
@@ -6608,28 +6591,29 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl.
break;
}
}
+ final int[] userIds;
synchronized (ImfLock.class) {
- final int[] userIds = InputMethodUtils.resolveUserId(userIdToBeResolved,
- mCurrentUserId, shellCommand.getErrPrintWriter());
- try (PrintWriter pr = shellCommand.getOutPrintWriter()) {
- for (int userId : userIds) {
- final List<InputMethodInfo> methods = all
- ? getInputMethodListLocked(
- userId, DirectBootAwareness.AUTO, Process.SHELL_UID)
- : getEnabledInputMethodListLocked(userId, Process.SHELL_UID);
- if (userIds.length > 1) {
- pr.print("User #");
- pr.print(userId);
+ userIds = InputMethodUtils.resolveUserId(userIdToBeResolved, mCurrentUserId,
+ shellCommand.getErrPrintWriter());
+ }
+ try (PrintWriter pr = shellCommand.getOutPrintWriter()) {
+ for (int userId : userIds) {
+ final List<InputMethodInfo> methods = all
+ ? getInputMethodListInternal(
+ userId, DirectBootAwareness.AUTO, Process.SHELL_UID)
+ : getEnabledInputMethodListInternal(userId, Process.SHELL_UID);
+ if (userIds.length > 1) {
+ pr.print("User #");
+ pr.print(userId);
+ pr.println(":");
+ }
+ for (InputMethodInfo info : methods) {
+ if (brief) {
+ pr.println(info.getId());
+ } else {
+ pr.print(info.getId());
pr.println(":");
- }
- for (InputMethodInfo info : methods) {
- if (brief) {
- pr.println(info.getId());
- } else {
- pr.print(info.getId());
- pr.println(":");
- info.dump(pr::println, " ");
- }
+ info.dump(pr::println, " ");
}
}
}
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubTestModeManager.java b/services/core/java/com/android/server/location/contexthub/ContextHubTestModeManager.java
index f2714dbd7e5f..2bb3be6a3332 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubTestModeManager.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubTestModeManager.java
@@ -17,10 +17,12 @@
package com.android.server.location.contexthub;
import android.chre.flags.Flags;
+import android.hardware.location.ContextHubTransaction;
import android.hardware.location.NanoAppMessage;
import android.util.Log;
-import java.util.Random;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.concurrent.Callable;
/**
* A class to manage behaviors during test mode. This is used for testing.
@@ -29,32 +31,31 @@ import java.util.Random;
public class ContextHubTestModeManager {
private static final String TAG = "ContextHubTestModeManager";
- /** Probability of duplicating a message. */
- private static final double MESSAGE_DROP_PROBABILITY = 0.05;
-
- /** Probability of duplicating a message. */
- private static final double MESSAGE_DUPLICATION_PROBABILITY = 0.05;
+ private static final int DROP_MESSAGE_TO_HOST_EVENT = 0;
+ private static final int DROP_MESSAGE_TO_CONTEXT_HUB_EVENT = 1;
+ private static final int DUPLICATE_MESSAGE_TO_HOST_EVENT = 2;
+ private static final int DUPLICATE_MESSAGE_TO_CONTEXT_HUB_EVENT = 3;
+ private static final int NUMBER_OF_EVENTS = 4;
/** The number of total messages to send when the duplication event happens. */
private static final int NUM_MESSAGES_TO_DUPLICATE = 3;
- /**
- * The seed for the random number generator. This is used to make the
- * test more deterministic.
- */
- private static final long SEED = 0xDEADBEEF;
-
- private final Random mRandom = new Random(SEED);
+ /** The counter to track the number of interactions with the test mode manager. */
+ private final AtomicLong mCounter = new AtomicLong(0);
/**
* @return whether the message was handled
* @see ContextHubServiceCallback#handleNanoappMessage
*/
public boolean handleNanoappMessage(Runnable handleMessage, NanoAppMessage message) {
+ if (!message.isReliable()) {
+ return false;
+ }
+
+ long counterValue = mCounter.getAndIncrement();
if (Flags.reliableMessageDuplicateDetectionService()
- && message.isReliable()
- && mRandom.nextDouble() < MESSAGE_DUPLICATION_PROBABILITY) {
- Log.i(TAG, "[TEST MODE] Duplicating message ("
+ && counterValue % NUMBER_OF_EVENTS == DUPLICATE_MESSAGE_TO_HOST_EVENT) {
+ Log.i(TAG, "[TEST MODE] Duplicating message to host ("
+ NUM_MESSAGES_TO_DUPLICATE
+ " sends) with message sequence number: "
+ message.getMessageSequenceNumber());
@@ -63,6 +64,14 @@ public class ContextHubTestModeManager {
}
return true;
}
+
+ if (counterValue % NUMBER_OF_EVENTS == DROP_MESSAGE_TO_HOST_EVENT) {
+ Log.i(TAG, "[TEST MODE] Dropping message to host with "
+ + "message sequence number: "
+ + message.getMessageSequenceNumber());
+ return true;
+ }
+
return false;
}
@@ -70,14 +79,39 @@ public class ContextHubTestModeManager {
* @return whether the message was handled
* @see IContextHubWrapper#sendMessageToContextHub
*/
- public boolean sendMessageToContextHub(NanoAppMessage message) {
+ public boolean sendMessageToContextHub(Callable<Integer> sendMessage, NanoAppMessage message) {
+ if (!message.isReliable()) {
+ return false;
+ }
+
+ long counterValue = mCounter.getAndIncrement();
+ if (counterValue % NUMBER_OF_EVENTS == DUPLICATE_MESSAGE_TO_CONTEXT_HUB_EVENT) {
+ Log.i(TAG, "[TEST MODE] Duplicating message to the Context Hub ("
+ + NUM_MESSAGES_TO_DUPLICATE
+ + " sends) with message sequence number: "
+ + message.getMessageSequenceNumber());
+ for (int i = 0; i < NUM_MESSAGES_TO_DUPLICATE; ++i) {
+ try {
+ int result = sendMessage.call();
+ if (result != ContextHubTransaction.RESULT_SUCCESS) {
+ Log.e(TAG, "sendMessage returned an error: " + result);
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "Exception in sendMessageToContextHub: "
+ + e.getMessage());
+ }
+ }
+ return true;
+ }
+
if (Flags.reliableMessageRetrySupportService()
- && message.isReliable()
- && mRandom.nextDouble() < MESSAGE_DROP_PROBABILITY) {
- Log.i(TAG, "[TEST MODE] Dropping message with message sequence number: "
+ && counterValue % NUMBER_OF_EVENTS == DROP_MESSAGE_TO_CONTEXT_HUB_EVENT) {
+ Log.i(TAG, "[TEST MODE] Dropping message to the Context Hub with "
+ + "message sequence number: "
+ message.getMessageSequenceNumber());
return true;
}
+
return false;
}
}
diff --git a/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java b/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java
index 4fc3d8715a88..a8ad41853d34 100644
--- a/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java
+++ b/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java
@@ -53,6 +53,7 @@ import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.Callable;
/**
* @hide
@@ -659,32 +660,40 @@ public abstract class IContextHubWrapper {
@ContextHubTransaction.Result
public int sendMessageToContextHub(short hostEndpointId, int contextHubId,
- NanoAppMessage message) throws RemoteException {
+ NanoAppMessage message) {
android.hardware.contexthub.IContextHub hub = getHub();
if (hub == null) {
return ContextHubTransaction.RESULT_FAILED_BAD_PARAMS;
}
- try {
- var msg = ContextHubServiceUtil.createAidlContextHubMessage(
- hostEndpointId, message);
-
- // Only process the message normally if not using test mode manager or if
- // the test mode manager call returned false as this indicates it did not
- // process the message.
- boolean useTestModeManager = Flags.reliableMessageImplementation()
- && Flags.reliableMessageTestModeBehavior()
- && mIsTestModeEnabled.get();
- if (!useTestModeManager || !mTestModeManager.sendMessageToContextHub(message)) {
+ Callable<Integer> sendMessage = () -> {
+ try {
+ var msg = ContextHubServiceUtil.createAidlContextHubMessage(
+ hostEndpointId, message);
hub.sendMessageToHub(contextHubId, msg);
+ return ContextHubTransaction.RESULT_SUCCESS;
+ } catch (RemoteException | ServiceSpecificException e) {
+ return ContextHubTransaction.RESULT_FAILED_UNKNOWN;
+ } catch (IllegalArgumentException e) {
+ return ContextHubTransaction.RESULT_FAILED_BAD_PARAMS;
+ }
+ };
+
+ // Only process the message normally if not using test mode manager or if
+ // the test mode manager call returned false as this indicates it did not
+ // process the message.
+ boolean useTestModeManager = Flags.reliableMessageImplementation()
+ && Flags.reliableMessageTestModeBehavior()
+ && mIsTestModeEnabled.get();
+ if (!useTestModeManager || !mTestModeManager.sendMessageToContextHub(
+ sendMessage, message)) {
+ try {
+ return sendMessage.call();
+ } catch (Exception e) {
+ return ContextHubTransaction.RESULT_FAILED_UNKNOWN;
}
-
- return ContextHubTransaction.RESULT_SUCCESS;
- } catch (RemoteException | ServiceSpecificException e) {
- return ContextHubTransaction.RESULT_FAILED_UNKNOWN;
- } catch (IllegalArgumentException e) {
- return ContextHubTransaction.RESULT_FAILED_BAD_PARAMS;
}
+ return ContextHubTransaction.RESULT_SUCCESS;
}
@ContextHubTransaction.Result
diff --git a/services/core/java/com/android/server/pm/TEST_MAPPING b/services/core/java/com/android/server/pm/TEST_MAPPING
index e12b70f033c3..c40608d12a0e 100644
--- a/services/core/java/com/android/server/pm/TEST_MAPPING
+++ b/services/core/java/com/android/server/pm/TEST_MAPPING
@@ -160,6 +160,9 @@
"exclude-annotation": "androidx.test.filters.FlakyTest"
}
]
+ },
+ {
+ "name": "CtsUpdateOwnershipEnforcementTestCases"
}
],
"imports": [
diff --git a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
index 8be20b0e4904..aaa38a3a1331 100644
--- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
+++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
@@ -149,6 +149,13 @@ final class DefaultPermissionGrantPolicy {
CONTACTS_PERMISSIONS.add(Manifest.permission.GET_ACCOUNTS);
}
+ private static final Set<String> CALL_LOG_PERMISSIONS = new ArraySet<>();
+ static {
+ CALL_LOG_PERMISSIONS.add(Manifest.permission.READ_CALL_LOG);
+ CALL_LOG_PERMISSIONS.add(Manifest.permission.WRITE_CALL_LOG);
+ }
+
+
private static final Set<String> ALWAYS_LOCATION_PERMISSIONS = new ArraySet<>();
static {
ALWAYS_LOCATION_PERMISSIONS.add(Manifest.permission.ACCESS_FINE_LOCATION);
@@ -753,7 +760,7 @@ final class DefaultPermissionGrantPolicy {
String contactsProviderPackage =
getDefaultProviderAuthorityPackage(ContactsContract.AUTHORITY, userId);
grantSystemFixedPermissionsToSystemPackage(pm, contactsProviderPackage, userId,
- CONTACTS_PERMISSIONS, PHONE_PERMISSIONS);
+ CONTACTS_PERMISSIONS, PHONE_PERMISSIONS, CALL_LOG_PERMISSIONS);
grantPermissionsToSystemPackage(pm, contactsProviderPackage, userId, STORAGE_PERMISSIONS);
// Device provisioning
diff --git a/services/permission/java/com/android/server/permission/access/util/AtomicFileExtensions.kt b/services/permission/java/com/android/server/permission/access/util/AtomicFileExtensions.kt
index 996daf5a5f68..95ee958f3ce4 100644
--- a/services/permission/java/com/android/server/permission/access/util/AtomicFileExtensions.kt
+++ b/services/permission/java/com/android/server/permission/access/util/AtomicFileExtensions.kt
@@ -19,6 +19,7 @@ package com.android.server.permission.access.util
import android.os.FileUtils
import android.util.AtomicFile
import android.util.Slog
+import com.android.server.security.FileIntegrity;
import java.io.File
import java.io.FileInputStream
import java.io.FileNotFoundException
@@ -49,6 +50,7 @@ inline fun AtomicFile.readWithReserveCopy(block: (FileInputStream) -> Unit) {
inline fun AtomicFile.writeWithReserveCopy(block: (FileOutputStream) -> Unit) {
writeInlined(block)
val reserveFile = File(baseFile.parentFile, baseFile.name + ".reservecopy")
+ reserveFile.delete()
try {
FileInputStream(baseFile).use { inputStream ->
FileOutputStream(reserveFile).use { outputStream ->
@@ -59,6 +61,12 @@ inline fun AtomicFile.writeWithReserveCopy(block: (FileOutputStream) -> Unit) {
} catch (e: Exception) {
Slog.e("AccessPersistence", "Failed to write $reserveFile", e)
}
+ try {
+ FileIntegrity.setUpFsVerity(baseFile)
+ FileIntegrity.setUpFsVerity(reserveFile)
+ } catch (e: Exception) {
+ Slog.e("AccessPersistence", "Failed to verity-protect runtime-permissions", e)
+ }
}
/** Write to an [AtomicFile] and close everything safely when done. */
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/OneTouchPlayActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/OneTouchPlayActionTest.java
index 2f4a6604441f..a8856dd24165 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/OneTouchPlayActionTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/OneTouchPlayActionTest.java
@@ -20,6 +20,7 @@ import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY;
import static com.android.server.hdmi.Constants.ADDR_TV;
import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC;
import static com.android.server.hdmi.HdmiControlService.WAKE_UP_SCREEN_ON;
+import static com.android.server.hdmi.OneTouchPlayAction.LOOP_COUNTER_MAX;
import static com.android.server.hdmi.OneTouchPlayAction.STATE_WAITING_FOR_REPORT_POWER_STATUS;
import static com.google.common.truth.Truth.assertThat;
@@ -335,7 +336,7 @@ public class OneTouchPlayActionTest {
mTestLooper.dispatchAll();
}
- assertThat(mNativeWrapper.getResultMessages()).doesNotContain(textViewOn);
+ assertThat(mNativeWrapper.getResultMessages()).contains(textViewOn);
assertThat(mNativeWrapper.getResultMessages()).doesNotContain(activeSource);
assertThat(mNativeWrapper.getResultMessages()).contains(giveDevicePowerStatus);
action.handleTimerEvent(STATE_WAITING_FOR_REPORT_POWER_STATUS);
@@ -672,7 +673,122 @@ public class OneTouchPlayActionTest {
mHdmiControlService.playback().getDeviceInfo().getLogicalAddress(),
ADDR_TV);
assertThat(mNativeWrapper.getResultMessages()).doesNotContain(textViewOn);
+ }
+
+ @Test
+ public void waitForReportPowerStatus_resendTextViewOn_timeout() throws Exception {
+ setUp(true);
+
+ HdmiCecLocalDevicePlayback playbackDevice = mHdmiControlService.playback();
+ mTestLooper.dispatchAll();
+
+ mNativeWrapper.setPollAddressResponse(ADDR_TV, SendMessageResult.SUCCESS);
+ mHdmiControlService.getHdmiCecNetwork().addCecDevice(INFO_TV);
+ mTestLooper.dispatchAll();
+ mNativeWrapper.clearResultMessages();
+
+ TestActionTimer actionTimer = new TestActionTimer();
+ TestCallback callback = new TestCallback();
+ OneTouchPlayAction action = createOneTouchPlayAction(playbackDevice, actionTimer, callback,
+ false);
+ playbackDevice.addAndStartAction(action);
+ mTestLooper.dispatchAll();
+
+ HdmiCecMessage activeSource =
+ HdmiCecMessageBuilder.buildActiveSource(
+ playbackDevice.getDeviceInfo().getLogicalAddress(), mPhysicalAddress);
+ HdmiCecMessage textViewOn =
+ HdmiCecMessageBuilder.buildTextViewOn(
+ playbackDevice.getDeviceInfo().getLogicalAddress(), ADDR_TV);
+ HdmiCecMessage giveDevicePowerStatus =
+ HdmiCecMessageBuilder.buildGiveDevicePowerStatus(
+ playbackDevice.getDeviceInfo().getLogicalAddress(), ADDR_TV);
+
+ assertThat(mNativeWrapper.getResultMessages()).contains(textViewOn);
+ assertThat(mNativeWrapper.getResultMessages()).contains(activeSource);
+ assertThat(mNativeWrapper.getResultMessages()).contains(giveDevicePowerStatus);
+ mNativeWrapper.clearResultMessages();
+ assertThat(actionTimer.getState()).isEqualTo(STATE_WAITING_FOR_REPORT_POWER_STATUS);
+
+ int counter = 0;
+ while (counter++ < LOOP_COUNTER_MAX) {
+ action.handleTimerEvent(STATE_WAITING_FOR_REPORT_POWER_STATUS);
+ mTestLooper.dispatchAll();
+ if (counter % 3 == 0) {
+ assertThat(mNativeWrapper.getResultMessages()).contains(textViewOn);
+ }
+ assertThat(mNativeWrapper.getResultMessages()).contains(giveDevicePowerStatus);
+ mNativeWrapper.clearResultMessages();
+ mTestLooper.dispatchAll();
+ }
+
+ action.handleTimerEvent(STATE_WAITING_FOR_REPORT_POWER_STATUS);
+ assertThat(callback.getResult()).isEqualTo(HdmiControlManager.RESULT_TIMEOUT);
+ }
+
+ @Test
+ public void waitForReportPowerStatus_resendTextViewOn_success() throws Exception {
+ setUp(true);
+
+ HdmiCecLocalDevicePlayback playbackDevice = mHdmiControlService.playback();
+ mTestLooper.dispatchAll();
+
+ mNativeWrapper.setPollAddressResponse(ADDR_TV, SendMessageResult.SUCCESS);
+ mHdmiControlService.getHdmiCecNetwork().addCecDevice(INFO_TV);
+ mTestLooper.dispatchAll();
+ mNativeWrapper.clearResultMessages();
+
+ TestActionTimer actionTimer = new TestActionTimer();
+ TestCallback callback = new TestCallback();
+ OneTouchPlayAction action = createOneTouchPlayAction(playbackDevice, actionTimer, callback,
+ false);
+ playbackDevice.addAndStartAction(action);
+ mTestLooper.dispatchAll();
+
+ HdmiCecMessage activeSource =
+ HdmiCecMessageBuilder.buildActiveSource(
+ playbackDevice.getDeviceInfo().getLogicalAddress(), mPhysicalAddress);
+ HdmiCecMessage textViewOn =
+ HdmiCecMessageBuilder.buildTextViewOn(
+ playbackDevice.getDeviceInfo().getLogicalAddress(), ADDR_TV);
+ HdmiCecMessage giveDevicePowerStatus =
+ HdmiCecMessageBuilder.buildGiveDevicePowerStatus(
+ playbackDevice.getDeviceInfo().getLogicalAddress(), ADDR_TV);
+
+ assertThat(mNativeWrapper.getResultMessages()).contains(textViewOn);
+ assertThat(mNativeWrapper.getResultMessages()).contains(activeSource);
+ assertThat(mNativeWrapper.getResultMessages()).contains(giveDevicePowerStatus);
+ mNativeWrapper.clearResultMessages();
+ assertThat(actionTimer.getState()).isEqualTo(STATE_WAITING_FOR_REPORT_POWER_STATUS);
+
+ int counter = 0;
+ while (counter++ < LOOP_COUNTER_MAX) {
+ action.handleTimerEvent(STATE_WAITING_FOR_REPORT_POWER_STATUS);
+ mTestLooper.dispatchAll();
+
+ if (counter % 3 == 0) {
+ assertThat(mNativeWrapper.getResultMessages()).contains(textViewOn);
+ }
+ assertThat(mNativeWrapper.getResultMessages()).contains(giveDevicePowerStatus);
+ mNativeWrapper.clearResultMessages();
+ mTestLooper.dispatchAll();
+ }
+
+ assertThat(actionTimer.getState()).isEqualTo(STATE_WAITING_FOR_REPORT_POWER_STATUS);
+ HdmiCecMessage reportPowerStatusOn =
+ HdmiCecMessage.build(
+ ADDR_TV,
+ playbackDevice.getDeviceInfo().getLogicalAddress(),
+ Constants.MESSAGE_REPORT_POWER_STATUS,
+ POWER_ON);
+ action.processCommand(reportPowerStatusOn);
+ mTestLooper.dispatchAll();
+
+ assertThat(mNativeWrapper.getResultMessages()).doesNotContain(textViewOn);
+ assertThat(mNativeWrapper.getResultMessages()).contains(activeSource);
+ assertThat(mNativeWrapper.getResultMessages()).doesNotContain(giveDevicePowerStatus);
+ assertThat(callback.getResult()).isEqualTo(HdmiControlManager.RESULT_SUCCESS);
}
private static class TestActionTimer implements ActionTimer {
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/PackageFilter.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/PackageFilter.kt
new file mode 100644
index 000000000000..c67e6714d4c2
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/PackageFilter.kt
@@ -0,0 +1,61 @@
+/*
+ * 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.hoststubgen.filters
+
+import com.android.hoststubgen.asm.toHumanReadableClassName
+import com.android.hoststubgen.utils.Trie
+
+/**
+ * Filter to apply a policy to classes inside a package, either directly or indirectly.
+ */
+class PackageFilter(
+ fallback: OutputFilter
+) : DelegatingFilter(fallback) {
+
+ private val mPackagePolicies = PackagePolicyTrie()
+
+ // We want to pick the most specific filter for a package name.
+ // Since any package with a matching prefix is a valid match, we can use a prefix tree
+ // to help us find the nearest matching filter.
+ private class PackagePolicyTrie : Trie<String, String, FilterPolicyWithReason>() {
+ // Split package name into individual component
+ override fun splitToComponents(key: String): Iterator<String> {
+ return key.split('.').iterator()
+ }
+ }
+
+ private fun getPackageKey(packageName: String): String {
+ return packageName.toHumanReadableClassName()
+ }
+
+ private fun getPackageKeyFromClass(className: String): String {
+ val clazz = className.toHumanReadableClassName()
+ val idx = clazz.lastIndexOf('.')
+ return if (idx >= 0) clazz.substring(0, idx) else ""
+ }
+
+ /**
+ * Add a policy to all classes inside a package, either directly or indirectly.
+ */
+ fun addPolicy(packageName: String, policy: FilterPolicyWithReason) {
+ mPackagePolicies[getPackageKey(packageName)] = policy
+ }
+
+ override fun getPolicyForClass(className: String): FilterPolicyWithReason {
+ return mPackagePolicies[getPackageKeyFromClass(className)]
+ ?: super.getPolicyForClass(className)
+ }
+}
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt
index c5acd81f1cf2..a89824eaf0b0 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt
@@ -64,7 +64,8 @@ fun createFilterFromTextPolicyFile(
log.i("Loading offloaded annotations from $filename ...")
log.withIndent {
val subclassFilter = SubclassFilter(classes, fallback)
- val imf = InMemoryOutputFilter(classes, subclassFilter)
+ val packageFilter = PackageFilter(subclassFilter)
+ val imf = InMemoryOutputFilter(classes, packageFilter)
var lineNo = 0
@@ -78,10 +79,7 @@ fun createFilterFromTextPolicyFile(
var className = ""
while (true) {
- var line = reader.readLine()
- if (line == null) {
- break
- }
+ var line = reader.readLine() ?: break
lineNo++
line = normalizeTextLine(line)
@@ -95,6 +93,31 @@ fun createFilterFromTextPolicyFile(
val fields = line.split(whitespaceRegex).toTypedArray()
when (fields[0].lowercase()) {
+ "p", "package" -> {
+ if (fields.size < 3) {
+ throw ParseException("Package ('p') expects 2 fields.")
+ }
+ val name = fields[1]
+ val rawPolicy = fields[2]
+ if (resolveExtendingClass(name) != null) {
+ throw ParseException("Package can't be a super class type")
+ }
+ if (resolveSpecialClass(name) != SpecialClass.NotSpecial) {
+ throw ParseException("Package can't be a special class type")
+ }
+ if (rawPolicy.startsWith("!")) {
+ throw ParseException("Package can't have a substitution")
+ }
+ if (rawPolicy.startsWith("~")) {
+ throw ParseException("Package can't have a class load hook")
+ }
+ val policy = parsePolicy(rawPolicy)
+ if (!policy.isUsableWithClasses) {
+ throw ParseException("Package can't have policy '$policy'")
+ }
+ packageFilter.addPolicy(name, policy.withReason(FILTER_REASON))
+ }
+
"c", "class" -> {
if (fields.size < 3) {
throw ParseException("Class ('c') expects 2 fields.")
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/utils/Trie.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/utils/Trie.kt
new file mode 100644
index 000000000000..1b3d79cddb8e
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/utils/Trie.kt
@@ -0,0 +1,58 @@
+/*
+ * 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.hoststubgen.utils
+
+abstract class Trie<Key, Component, Value> {
+
+ private val root = TrieNode<Component, Value>()
+
+ abstract fun splitToComponents(key: Key): Iterator<Component>
+
+ operator fun set(key: Key, value: Value) {
+ val node = root.getExactNode(splitToComponents(key))
+ node.value = value
+ }
+
+ operator fun get(key: Key): Value? {
+ return root.getNearestValue(null, splitToComponents(key))
+ }
+
+ private class TrieNode<Component, Value> {
+ private val children = mutableMapOf<Component, TrieNode<Component, Value>>()
+ var value: Value? = null
+
+ fun getExactNode(components: Iterator<Component>): TrieNode<Component, Value> {
+ val n = components.next()
+ val child = children.getOrPut(n) { TrieNode() }
+ return if (components.hasNext()) {
+ child.getExactNode(components)
+ } else {
+ child
+ }
+ }
+
+ fun getNearestValue(current: Value?, components: Iterator<Component>): Value? {
+ val n = components.next()
+ val child = children[n] ?: return current
+ val newValue = child.value ?: current
+ return if (components.hasNext()) {
+ child.getNearestValue(newValue, components)
+ } else {
+ newValue
+ }
+ }
+ }
+}
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt
index dd638925a5bc..3ef117567482 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt
@@ -2706,6 +2706,98 @@ SourceFile: "TinyFrameworkPackageRedirect.java"
RuntimeInvisibleAnnotations:
x: #x()
android.hosttest.annotation.HostSideTestWholeClassStub
+## Class: com/android/hoststubgen/test/tinyframework/packagetest/A.class
+ Compiled from "A.java"
+public class com.android.hoststubgen.test.tinyframework.packagetest.A
+ minor version: 0
+ major version: 61
+ flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+ this_class: #x // com/android/hoststubgen/test/tinyframework/packagetest/A
+ super_class: #x // java/lang/Object
+ interfaces: 0, fields: 0, methods: 1, attributes: 1
+ public com.android.hoststubgen.test.tinyframework.packagetest.A();
+ descriptor: ()V
+ flags: (0x0001) ACC_PUBLIC
+ Code:
+ stack=1, locals=1, args_size=1
+ x: aload_0
+ x: invokespecial #x // Method java/lang/Object."<init>":()V
+ x: return
+ LineNumberTable:
+ LocalVariableTable:
+ Start Length Slot Name Signature
+ 0 5 0 this Lcom/android/hoststubgen/test/tinyframework/packagetest/A;
+}
+SourceFile: "A.java"
+## Class: com/android/hoststubgen/test/tinyframework/packagetest/B.class
+ Compiled from "B.java"
+public class com.android.hoststubgen.test.tinyframework.packagetest.B
+ minor version: 0
+ major version: 61
+ flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+ this_class: #x // com/android/hoststubgen/test/tinyframework/packagetest/B
+ super_class: #x // java/lang/Object
+ interfaces: 0, fields: 0, methods: 1, attributes: 1
+ public com.android.hoststubgen.test.tinyframework.packagetest.B();
+ descriptor: ()V
+ flags: (0x0001) ACC_PUBLIC
+ Code:
+ stack=1, locals=1, args_size=1
+ x: aload_0
+ x: invokespecial #x // Method java/lang/Object."<init>":()V
+ x: return
+ LineNumberTable:
+ LocalVariableTable:
+ Start Length Slot Name Signature
+ 0 5 0 this Lcom/android/hoststubgen/test/tinyframework/packagetest/B;
+}
+SourceFile: "B.java"
+## Class: com/android/hoststubgen/test/tinyframework/packagetest/sub/A.class
+ Compiled from "A.java"
+public class com.android.hoststubgen.test.tinyframework.packagetest.sub.A
+ minor version: 0
+ major version: 61
+ flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+ this_class: #x // com/android/hoststubgen/test/tinyframework/packagetest/sub/A
+ super_class: #x // java/lang/Object
+ interfaces: 0, fields: 0, methods: 1, attributes: 1
+ public com.android.hoststubgen.test.tinyframework.packagetest.sub.A();
+ descriptor: ()V
+ flags: (0x0001) ACC_PUBLIC
+ Code:
+ stack=1, locals=1, args_size=1
+ x: aload_0
+ x: invokespecial #x // Method java/lang/Object."<init>":()V
+ x: return
+ LineNumberTable:
+ LocalVariableTable:
+ Start Length Slot Name Signature
+ 0 5 0 this Lcom/android/hoststubgen/test/tinyframework/packagetest/sub/A;
+}
+SourceFile: "A.java"
+## Class: com/android/hoststubgen/test/tinyframework/packagetest/sub/B.class
+ Compiled from "B.java"
+public class com.android.hoststubgen.test.tinyframework.packagetest.sub.B
+ minor version: 0
+ major version: 61
+ flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+ this_class: #x // com/android/hoststubgen/test/tinyframework/packagetest/sub/B
+ super_class: #x // java/lang/Object
+ interfaces: 0, fields: 0, methods: 1, attributes: 1
+ public com.android.hoststubgen.test.tinyframework.packagetest.sub.B();
+ descriptor: ()V
+ flags: (0x0001) ACC_PUBLIC
+ Code:
+ stack=1, locals=1, args_size=1
+ x: aload_0
+ x: invokespecial #x // Method java/lang/Object."<init>":()V
+ x: return
+ LineNumberTable:
+ LocalVariableTable:
+ Start Length Slot Name Signature
+ 0 5 0 this Lcom/android/hoststubgen/test/tinyframework/packagetest/sub/B;
+}
+SourceFile: "B.java"
## Class: com/android/hoststubgen/test/tinyframework/subclasstest/C1.class
Compiled from "C1.java"
public class com.android.hoststubgen.test.tinyframework.subclasstest.C1
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/02-hoststubgen-test-tiny-framework-host-stub-dump.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/02-hoststubgen-test-tiny-framework-host-stub-dump.txt
index 906a81cf45e3..0bbb4182859b 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/02-hoststubgen-test-tiny-framework-host-stub-dump.txt
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/02-hoststubgen-test-tiny-framework-host-stub-dump.txt
@@ -2177,6 +2177,38 @@ RuntimeVisibleAnnotations:
RuntimeInvisibleAnnotations:
x: #x()
android.hosttest.annotation.HostSideTestWholeClassStub
+## Class: com/android/hoststubgen/test/tinyframework/packagetest/A.class
+ Compiled from "A.java"
+public class com.android.hoststubgen.test.tinyframework.packagetest.A
+ minor version: 0
+ major version: 61
+ flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+ this_class: #x // com/android/hoststubgen/test/tinyframework/packagetest/A
+ super_class: #x // java/lang/Object
+ interfaces: 0, fields: 0, methods: 0, attributes: 2
+}
+SourceFile: "A.java"
+RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+## Class: com/android/hoststubgen/test/tinyframework/packagetest/sub/A.class
+ Compiled from "A.java"
+public class com.android.hoststubgen.test.tinyframework.packagetest.sub.A
+ minor version: 0
+ major version: 61
+ flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+ this_class: #x // com/android/hoststubgen/test/tinyframework/packagetest/sub/A
+ super_class: #x // java/lang/Object
+ interfaces: 0, fields: 0, methods: 0, attributes: 2
+}
+SourceFile: "A.java"
+RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
## Class: com/android/hoststubgen/test/tinyframework/subclasstest/C1.class
Compiled from "C1.java"
public class com.android.hoststubgen.test.tinyframework.subclasstest.C1
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/03-hoststubgen-test-tiny-framework-host-impl-dump.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/03-hoststubgen-test-tiny-framework-host-impl-dump.txt
index 10bc91da2544..57f3783e8a73 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/03-hoststubgen-test-tiny-framework-host-impl-dump.txt
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/03-hoststubgen-test-tiny-framework-host-impl-dump.txt
@@ -3540,6 +3540,38 @@ RuntimeVisibleAnnotations:
RuntimeInvisibleAnnotations:
x: #x()
android.hosttest.annotation.HostSideTestWholeClassStub
+## Class: com/android/hoststubgen/test/tinyframework/packagetest/A.class
+ Compiled from "A.java"
+public class com.android.hoststubgen.test.tinyframework.packagetest.A
+ minor version: 0
+ major version: 61
+ flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+ this_class: #x // com/android/hoststubgen/test/tinyframework/packagetest/A
+ super_class: #x // java/lang/Object
+ interfaces: 0, fields: 0, methods: 0, attributes: 2
+}
+SourceFile: "A.java"
+RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+## Class: com/android/hoststubgen/test/tinyframework/packagetest/sub/A.class
+ Compiled from "A.java"
+public class com.android.hoststubgen.test.tinyframework.packagetest.sub.A
+ minor version: 0
+ major version: 61
+ flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+ this_class: #x // com/android/hoststubgen/test/tinyframework/packagetest/sub/A
+ super_class: #x // java/lang/Object
+ interfaces: 0, fields: 0, methods: 0, attributes: 2
+}
+SourceFile: "A.java"
+RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
## Class: com/android/hoststubgen/test/tinyframework/subclasstest/C1.class
Compiled from "C1.java"
public class com.android.hoststubgen.test.tinyframework.subclasstest.C1
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/12-hoststubgen-test-tiny-framework-host-ext-stub-dump.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/12-hoststubgen-test-tiny-framework-host-ext-stub-dump.txt
index 906a81cf45e3..0bbb4182859b 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/12-hoststubgen-test-tiny-framework-host-ext-stub-dump.txt
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/12-hoststubgen-test-tiny-framework-host-ext-stub-dump.txt
@@ -2177,6 +2177,38 @@ RuntimeVisibleAnnotations:
RuntimeInvisibleAnnotations:
x: #x()
android.hosttest.annotation.HostSideTestWholeClassStub
+## Class: com/android/hoststubgen/test/tinyframework/packagetest/A.class
+ Compiled from "A.java"
+public class com.android.hoststubgen.test.tinyframework.packagetest.A
+ minor version: 0
+ major version: 61
+ flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+ this_class: #x // com/android/hoststubgen/test/tinyframework/packagetest/A
+ super_class: #x // java/lang/Object
+ interfaces: 0, fields: 0, methods: 0, attributes: 2
+}
+SourceFile: "A.java"
+RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+## Class: com/android/hoststubgen/test/tinyframework/packagetest/sub/A.class
+ Compiled from "A.java"
+public class com.android.hoststubgen.test.tinyframework.packagetest.sub.A
+ minor version: 0
+ major version: 61
+ flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+ this_class: #x // com/android/hoststubgen/test/tinyframework/packagetest/sub/A
+ super_class: #x // java/lang/Object
+ interfaces: 0, fields: 0, methods: 0, attributes: 2
+}
+SourceFile: "A.java"
+RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
## Class: com/android/hoststubgen/test/tinyframework/subclasstest/C1.class
Compiled from "C1.java"
public class com.android.hoststubgen.test.tinyframework.subclasstest.C1
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/13-hoststubgen-test-tiny-framework-host-ext-impl-dump.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/13-hoststubgen-test-tiny-framework-host-ext-impl-dump.txt
index fcf9a8c663ad..91104dea3f7b 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/13-hoststubgen-test-tiny-framework-host-ext-impl-dump.txt
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/13-hoststubgen-test-tiny-framework-host-ext-impl-dump.txt
@@ -4408,6 +4408,56 @@ RuntimeVisibleAnnotations:
RuntimeInvisibleAnnotations:
x: #x()
android.hosttest.annotation.HostSideTestWholeClassStub
+## Class: com/android/hoststubgen/test/tinyframework/packagetest/A.class
+ Compiled from "A.java"
+public class com.android.hoststubgen.test.tinyframework.packagetest.A
+ minor version: 0
+ major version: 61
+ flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+ this_class: #x // com/android/hoststubgen/test/tinyframework/packagetest/A
+ super_class: #x // java/lang/Object
+ interfaces: 0, fields: 0, methods: 1, attributes: 2
+ private static {};
+ descriptor: ()V
+ flags: (0x000a) ACC_PRIVATE, ACC_STATIC
+ Code:
+ stack=2, locals=0, args_size=0
+ x: ldc #x // class com/android/hoststubgen/test/tinyframework/packagetest/A
+ x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded
+ x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V
+ x: return
+}
+SourceFile: "A.java"
+RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+## Class: com/android/hoststubgen/test/tinyframework/packagetest/sub/A.class
+ Compiled from "A.java"
+public class com.android.hoststubgen.test.tinyframework.packagetest.sub.A
+ minor version: 0
+ major version: 61
+ flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+ this_class: #x // com/android/hoststubgen/test/tinyframework/packagetest/sub/A
+ super_class: #x // java/lang/Object
+ interfaces: 0, fields: 0, methods: 1, attributes: 2
+ private static {};
+ descriptor: ()V
+ flags: (0x000a) ACC_PRIVATE, ACC_STATIC
+ Code:
+ stack=2, locals=0, args_size=0
+ x: ldc #x // class com/android/hoststubgen/test/tinyframework/packagetest/sub/A
+ x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded
+ x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V
+ x: return
+}
+SourceFile: "A.java"
+RuntimeVisibleAnnotations:
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+ x: #x()
+ com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
## Class: com/android/hoststubgen/test/tinyframework/subclasstest/C1.class
Compiled from "C1.java"
public class com.android.hoststubgen.test.tinyframework.subclasstest.C1
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/policy-override-tiny-framework.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/policy-override-tiny-framework.txt
index 696b6d009dc2..530de431828e 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/policy-override-tiny-framework.txt
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/policy-override-tiny-framework.txt
@@ -40,3 +40,11 @@ class *com.android.hoststubgen.test.tinyframework.subclasstest.CA remove
class *com.android.hoststubgen.test.tinyframework.subclasstest.I1 keep
class *com.android.hoststubgen.test.tinyframework.subclasstest.IA remove
+
+# Test package directive
+package com.android.hoststubgen.test.tinyframework.packagetest stub
+class com.android.hoststubgen.test.tinyframework.packagetest.B remove
+class com.android.hoststubgen.test.tinyframework.packagetest.sub.B remove
+# The following rules are the same as above
+# class com.android.hoststubgen.test.tinyframework.packagetest.A stub
+# class com.android.hoststubgen.test.tinyframework.packagetest.sub.A stub
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/packagetest/A.java b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/packagetest/A.java
new file mode 100644
index 000000000000..6a52e4401b45
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/packagetest/A.java
@@ -0,0 +1,19 @@
+/*
+ * 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.hoststubgen.test.tinyframework.packagetest;
+
+public class A {
+}
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/packagetest/B.java b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/packagetest/B.java
new file mode 100644
index 000000000000..1374a288f7aa
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/packagetest/B.java
@@ -0,0 +1,19 @@
+/*
+ * 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.hoststubgen.test.tinyframework.packagetest;
+
+public class B {
+}
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/packagetest/sub/A.java b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/packagetest/sub/A.java
new file mode 100644
index 000000000000..361a7fd04842
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/packagetest/sub/A.java
@@ -0,0 +1,19 @@
+/*
+ * 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.hoststubgen.test.tinyframework.packagetest.sub;
+
+public class A {
+}
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/packagetest/sub/B.java b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/packagetest/sub/B.java
new file mode 100644
index 000000000000..716595a44243
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/packagetest/sub/B.java
@@ -0,0 +1,19 @@
+/*
+ * 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.hoststubgen.test.tinyframework.packagetest.sub;
+
+public class B {
+}
diff --git a/tools/hoststubgen/hoststubgen/test/com/android/hoststubgen/utils/TrieTest.kt b/tools/hoststubgen/hoststubgen/test/com/android/hoststubgen/utils/TrieTest.kt
new file mode 100644
index 000000000000..081d03909926
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/test/com/android/hoststubgen/utils/TrieTest.kt
@@ -0,0 +1,47 @@
+/*
+ * 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.hoststubgen.utils
+
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertNull
+import org.junit.Test
+
+class TrieTest {
+
+ private class TestTrie : Trie<String, Char, Int>() {
+ override fun splitToComponents(key: String): Iterator<Char> {
+ return key.toCharArray().iterator()
+ }
+ }
+
+ @Test
+ fun testPrefixTree() {
+ val trie = TestTrie()
+ trie["ab"] = 1
+ trie["abc"] = 2
+ trie["ab123"] = 3
+ assertNull(trie["a"])
+ assertNull(trie["x"])
+ assertNull(trie["a1"])
+ assertEquals(1, trie["ab"])
+ assertEquals(2, trie["abc"])
+ assertEquals(2, trie["abcd"])
+ assertEquals(1, trie["ab1"])
+ assertEquals(1, trie["ab12"])
+ assertEquals(3, trie["ab123"])
+ assertEquals(1, trie["ab@"])
+ }
+}