=
+ ServiceConnector.Impl(
+ context,
+ Intent(context, NoteTaskBubblesService::class.java),
+ Context.BIND_AUTO_CREATE or Context.BIND_WAIVE_PRIORITY or Context.BIND_NOT_VISIBLE,
+ UserHandle.USER_SYSTEM,
+ INoteTaskBubblesService.Stub::asInterface
+ )
+
+ /** Returns whether notes app bubble is supported. */
+ open suspend fun areBubblesAvailable(): Boolean =
+ withContext(bgDispatcher) {
+ suspendCoroutine { continuation ->
+ serviceConnector
+ .postForResult { it.areBubblesAvailable() }
+ .whenComplete { available, error ->
+ if (error != null) {
+ debugLog(error = error) { "Failed to query Bubbles as system user." }
+ }
+ continuation.resume(available ?: false)
+ }
+ }
+ }
+
+ /** Calls the [Bubbles.showOrHideAppBubble] API as [UserHandle.USER_SYSTEM]. */
+ open suspend fun showOrHideAppBubble(
+ intent: Intent,
+ userHandle: UserHandle,
+ icon: Icon
+ ) {
+ withContext(bgDispatcher) {
+ serviceConnector
+ .post { it.showOrHideAppBubble(intent, userHandle, icon) }
+ .whenComplete { _, error ->
+ if (error != null) {
+ debugLog(error = error) {
+ "Failed to show notes app bubble for intent $intent, " +
+ "user $userHandle, and icon $icon."
+ }
+ } else {
+ debugLog {
+ "Call to show notes app bubble for intent $intent, " +
+ "user $userHandle, and icon $icon successful."
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * A helper service to call [Bubbles] APIs that should always be called from the system user
+ * instance of SysUI.
+ *
+ * Note: This service always runs in the SysUI process running on the system user
+ * irrespective of which user started the service. This is required so that the correct instance
+ * of {@link Bubbles} is injected. This is set via attribute {@code android:singleUser=”true”}
+ * in AndroidManifest.
+ */
+ class NoteTaskBubblesService
+ @Inject
+ constructor(private val mOptionalBubbles: Optional) : Service() {
+
+ override fun onBind(intent: Intent): IBinder {
+ return object : INoteTaskBubblesService.Stub() {
+ override fun areBubblesAvailable() = mOptionalBubbles.isPresent
+
+ override fun showOrHideAppBubble(
+ intent: Intent,
+ userHandle: UserHandle,
+ icon: Icon
+ ) {
+ mOptionalBubbles.ifPresentOrElse(
+ { bubbles -> bubbles.showOrHideAppBubble(intent, userHandle, icon) },
+ {
+ debugLog {
+ "Failed to show or hide bubble for intent $intent," +
+ "user $user, and icon $icon as bubble is empty."
+ }
+ }
+ )
+ }
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
index c84e0067c2e84..7627c0c22d1ef 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
@@ -38,22 +38,22 @@ import android.widget.Toast
import androidx.annotation.VisibleForTesting
import com.android.systemui.R
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.devicepolicy.areKeyguardShortcutsDisabled
import com.android.systemui.log.DebugLogger.debugLog
import com.android.systemui.notetask.NoteTaskRoleManagerExt.createNoteShortcutInfoAsUser
import com.android.systemui.notetask.NoteTaskRoleManagerExt.getDefaultRoleHolderAsUser
import com.android.systemui.notetask.shortcut.CreateNoteTaskShortcutActivity
-import com.android.systemui.notetask.shortcut.LaunchNoteTaskManagedProfileProxyActivity
import com.android.systemui.settings.UserTracker
import com.android.systemui.shared.system.ActivityManagerKt.isInForeground
import com.android.systemui.util.kotlin.getOrNull
import com.android.systemui.util.settings.SecureSettings
import com.android.wm.shell.bubbles.Bubble
-import com.android.wm.shell.bubbles.Bubbles
import com.android.wm.shell.bubbles.Bubbles.BubbleExpandListener
-import java.util.Optional
import java.util.concurrent.atomic.AtomicReference
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
/**
* Entry point for creating and managing note.
@@ -71,7 +71,7 @@ constructor(
private val shortcutManager: ShortcutManager,
private val resolver: NoteTaskInfoResolver,
private val eventLogger: NoteTaskEventLogger,
- private val optionalBubbles: Optional,
+ private val noteTaskBubblesController: NoteTaskBubblesController,
private val userManager: UserManager,
private val keyguardManager: KeyguardManager,
private val activityManager: ActivityManager,
@@ -79,6 +79,7 @@ constructor(
private val devicePolicyManager: DevicePolicyManager,
private val userTracker: UserTracker,
private val secureSettings: SecureSettings,
+ @Application private val applicationScope: CoroutineScope
) {
@VisibleForTesting val infoReference = AtomicReference()
@@ -103,18 +104,6 @@ constructor(
}
}
- /** Starts [LaunchNoteTaskProxyActivity] on the given [user]. */
- fun startNoteTaskProxyActivityForUser(user: UserHandle) {
- context.startActivityAsUser(
- Intent().apply {
- component =
- ComponentName(context, LaunchNoteTaskManagedProfileProxyActivity::class.java)
- addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
- },
- user
- )
- }
-
/** Starts the notes role setting. */
fun startNotesRoleSetting(activityContext: Context, entryPoint: NoteTaskEntryPoint?) {
val user =
@@ -178,7 +167,19 @@ constructor(
) {
if (!isEnabled) return
- val bubbles = optionalBubbles.getOrNull() ?: return
+ applicationScope.launch { awaitShowNoteTaskAsUser(entryPoint, user) }
+ }
+
+ private suspend fun awaitShowNoteTaskAsUser(
+ entryPoint: NoteTaskEntryPoint,
+ user: UserHandle,
+ ) {
+ if (!isEnabled) return
+
+ if (!noteTaskBubblesController.areBubblesAvailable()) {
+ debugLog { "Bubbles not available in the system user SysUI instance" }
+ return
+ }
// TODO(b/249954038): We should handle direct boot (isUserUnlocked). For now, we do nothing.
if (!userManager.isUserUnlocked) return
@@ -213,7 +214,7 @@ constructor(
val intent = createNoteTaskIntent(info)
val icon =
Icon.createWithResource(context, R.drawable.ic_note_task_shortcut_widget)
- bubbles.showOrHideAppBubble(intent, user, icon)
+ noteTaskBubblesController.showOrHideAppBubble(intent, user, icon)
// App bubble logging happens on `onBubbleExpandChanged`.
debugLog { "onShowNoteTask - opened as app bubble: $info" }
}
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt
index 109cfeec07233..c0e688f0f82fa 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt
@@ -26,7 +26,6 @@ import com.android.systemui.flags.Flags
import com.android.systemui.notetask.quickaffordance.NoteTaskQuickAffordanceModule
import com.android.systemui.notetask.shortcut.CreateNoteTaskShortcutActivity
import com.android.systemui.notetask.shortcut.LaunchNoteTaskActivity
-import com.android.systemui.notetask.shortcut.LaunchNoteTaskManagedProfileProxyActivity
import dagger.Binds
import dagger.Module
import dagger.Provides
@@ -40,12 +39,12 @@ interface NoteTaskModule {
@[Binds IntoMap ClassKey(NoteTaskControllerUpdateService::class)]
fun NoteTaskControllerUpdateService.bindNoteTaskControllerUpdateService(): Service
+ @[Binds IntoMap ClassKey(NoteTaskBubblesController.NoteTaskBubblesService::class)]
+ fun NoteTaskBubblesController.NoteTaskBubblesService.bindNoteTaskBubblesService(): Service
+
@[Binds IntoMap ClassKey(LaunchNoteTaskActivity::class)]
fun LaunchNoteTaskActivity.bindNoteTaskLauncherActivity(): Activity
- @[Binds IntoMap ClassKey(LaunchNoteTaskManagedProfileProxyActivity::class)]
- fun LaunchNoteTaskManagedProfileProxyActivity.bindNoteTaskLauncherProxyActivity(): Activity
-
@[Binds IntoMap ClassKey(LaunchNotesRoleSettingsTrampolineActivity::class)]
fun LaunchNotesRoleSettingsTrampolineActivity.bindLaunchNotesRoleSettingsTrampolineActivity():
Activity
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivity.kt b/packages/SystemUI/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivity.kt
index 8ca13b9776bb8..493330af56ce8 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivity.kt
@@ -19,61 +19,18 @@ package com.android.systemui.notetask.shortcut
import android.content.Context
import android.content.Intent
import android.os.Bundle
-import android.os.UserHandle
-import android.os.UserManager
import androidx.activity.ComponentActivity
-import com.android.systemui.log.DebugLogger.debugLog
import com.android.systemui.notetask.NoteTaskController
import com.android.systemui.notetask.NoteTaskEntryPoint
-import com.android.systemui.settings.UserTracker
import javax.inject.Inject
/** Activity responsible for launching the note experience, and finish. */
-class LaunchNoteTaskActivity
-@Inject
-constructor(
- private val controller: NoteTaskController,
- private val userManager: UserManager,
- private val userTracker: UserTracker,
-) : ComponentActivity() {
+class LaunchNoteTaskActivity @Inject constructor(private val controller: NoteTaskController) :
+ ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
-
- // Under the hood, notes app shortcuts are shown in a floating window, called Bubble.
- // Bubble API is only available in the main user but not work profile.
- //
- // On devices with work profile (WP), SystemUI provides both personal notes app shortcuts &
- // work profile notes app shortcuts. In order to make work profile notes app shortcuts to
- // show in Bubble, a few redirections across users are required:
- // 1. When `LaunchNoteTaskActivity` is started in the work profile user, we launch
- // `LaunchNoteTaskManagedProfileProxyActivity` on the main user, which has access to the
- // Bubble API.
- // 2. `LaunchNoteTaskManagedProfileProxyActivity` calls `Bubble#showOrHideAppBubble` with
- // the work profile user ID.
- // 3. Bubble renders the work profile notes app activity in a floating window, which is
- // hosted in the main user.
- //
- // WP main user
- // ------------------------ -------------------------------------------
- // | LaunchNoteTaskActivity | -> | LaunchNoteTaskManagedProfileProxyActivity |
- // ------------------------ -------------------------------------------
- // |
- // main user |
- // ---------------------------- |
- // | Bubble#showOrHideAppBubble | <--------------
- // | (with WP user ID) |
- // ----------------------------
- val mainUser: UserHandle? = userManager.mainUser
- if (userManager.isManagedProfile) {
- if (mainUser == null) {
- debugLog { "Can't find the main user. Skipping the notes app launch." }
- } else {
- controller.startNoteTaskProxyActivityForUser(mainUser)
- }
- } else {
- controller.showNoteTask(entryPoint = NoteTaskEntryPoint.WIDGET_PICKER_SHORTCUT)
- }
+ controller.showNoteTaskAsUser(entryPoint = NoteTaskEntryPoint.WIDGET_PICKER_SHORTCUT, user)
finish()
}
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskManagedProfileProxyActivity.kt b/packages/SystemUI/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskManagedProfileProxyActivity.kt
deleted file mode 100644
index 3259b0dcc53dd..0000000000000
--- a/packages/SystemUI/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskManagedProfileProxyActivity.kt
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.notetask.shortcut
-
-import android.os.Build
-import android.os.Bundle
-import android.os.UserManager
-import android.util.Log
-import androidx.activity.ComponentActivity
-import com.android.systemui.notetask.NoteTaskController
-import com.android.systemui.notetask.NoteTaskEntryPoint
-import com.android.systemui.settings.UserTracker
-import javax.inject.Inject
-
-/**
- * An internal proxy activity that starts notes app in the work profile.
- *
- * If there is no work profile, this activity finishes gracefully.
- *
- * This activity MUST NOT be exported because that would expose the INTERACT_ACROSS_USER privilege
- * to any apps.
- */
-class LaunchNoteTaskManagedProfileProxyActivity
-@Inject
-constructor(
- private val controller: NoteTaskController,
- private val userTracker: UserTracker,
- private val userManager: UserManager,
-) : ComponentActivity() {
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
-
- val managedProfileUser =
- userTracker.userProfiles.firstOrNull { userManager.isManagedProfile(it.id) }
-
- if (managedProfileUser == null) {
- logDebug { "Fail to find the work profile user." }
- } else {
- controller.showNoteTaskAsUser(
- entryPoint = NoteTaskEntryPoint.WIDGET_PICKER_SHORTCUT,
- user = managedProfileUser.userHandle
- )
- }
- finish()
- }
-}
-
-private inline fun logDebug(message: () -> String) {
- if (Build.IS_DEBUGGABLE) {
- Log.d(NoteTaskController.TAG, message())
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsCrossProcessHelper.java b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsCrossProcessHelper.java
index afc8bff91766b..7de22b1a9c775 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsCrossProcessHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsCrossProcessHelper.java
@@ -19,7 +19,7 @@ package com.android.systemui.screenshot.appclips;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
-import android.os.UserManager;
+import android.os.UserHandle;
import androidx.annotation.Nullable;
@@ -39,15 +39,14 @@ class AppClipsCrossProcessHelper {
private final DisplayTracker mDisplayTracker;
@Inject
- AppClipsCrossProcessHelper(@Application Context context, UserManager userManager,
- DisplayTracker displayTracker) {
+ AppClipsCrossProcessHelper(@Application Context context, DisplayTracker displayTracker) {
// Start a service as main user so that even if the app clips activity is running as work
// profile user the service is able to use correct instance of Bubbles to grab a screenshot
// excluding the bubble layer.
mProxyConnector = new ServiceConnector.Impl<>(context,
new Intent(context, AppClipsScreenshotHelperService.class),
Context.BIND_AUTO_CREATE | Context.BIND_WAIVE_PRIORITY
- | Context.BIND_NOT_VISIBLE, userManager.getMainUser().getIdentifier(),
+ | Context.BIND_NOT_VISIBLE, UserHandle.USER_SYSTEM,
IAppClipsScreenshotHelperService.Stub::asInterface);
mDisplayTracker = displayTracker;
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsScreenshotHelperService.java b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsScreenshotHelperService.java
index 83ff020362f1a..e0b9f9b7ad936 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsScreenshotHelperService.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsScreenshotHelperService.java
@@ -33,6 +33,11 @@ import javax.inject.Inject;
/**
* A helper service that runs in SysUI process and helps {@link AppClipsActivity} which runs in its
* own separate process take a screenshot.
+ *
+ * Note: This service always runs in the SysUI process running on the system user irrespective of
+ * which user started the service. This is required so that the correct instance of {@link Bubbles}
+ * instance is injected. This is set via attribute {@code android:singleUser=”true”} in
+ * AndroidManifest.
*/
public class AppClipsScreenshotHelperService extends Service {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsService.java b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsService.java
index 394949297d6d6..dce8c81c34626 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsService.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsService.java
@@ -16,6 +16,11 @@
package com.android.systemui.screenshot.appclips;
+import static android.content.Intent.CAPTURE_CONTENT_FOR_NOTE_BLOCKED_BY_ADMIN;
+import static android.content.Intent.CAPTURE_CONTENT_FOR_NOTE_FAILED;
+import static android.content.Intent.CAPTURE_CONTENT_FOR_NOTE_SUCCESS;
+import static android.content.Intent.CAPTURE_CONTENT_FOR_NOTE_WINDOW_MODE_UNSUPPORTED;
+
import static com.android.systemui.flags.Flags.SCREENSHOT_APP_CLIPS;
import android.app.Activity;
@@ -25,17 +30,12 @@ import android.app.admin.DevicePolicyManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.Intent.CaptureContentForNoteStatusCodes;
import android.content.res.Resources;
import android.os.IBinder;
-import android.os.UserHandle;
-import android.os.UserManager;
-import android.util.Log;
import androidx.annotation.Nullable;
-import androidx.annotation.VisibleForTesting;
-import com.android.internal.infra.AndroidFuture;
-import com.android.internal.infra.ServiceConnector;
import com.android.internal.statusbar.IAppClipsService;
import com.android.systemui.R;
import com.android.systemui.dagger.qualifiers.Application;
@@ -43,73 +43,36 @@ import com.android.systemui.flags.FeatureFlags;
import com.android.wm.shell.bubbles.Bubbles;
import java.util.Optional;
-import java.util.concurrent.ExecutionException;
import javax.inject.Inject;
/**
* A service that communicates with {@link StatusBarManager} to support the
- * {@link StatusBarManager#canLaunchCaptureContentActivityForNote(Activity)} API.
+ * {@link StatusBarManager#canLaunchCaptureContentActivityForNote(Activity)} API. Also used by
+ * {@link AppClipsTrampolineActivity} to query if an app should be allowed to user App Clips.
+ *
+ *
Note: This service always runs in the SysUI process running on the system user irrespective of
+ * which user started the service. This is required so that the correct instance of {@link Bubbles}
+ * instance is injected. This is set via attribute {@code android:singleUser=”true”} in
+ * AndroidManifest.
*/
public class AppClipsService extends Service {
- private static final String TAG = AppClipsService.class.getSimpleName();
-
@Application private final Context mContext;
private final FeatureFlags mFeatureFlags;
private final Optional mOptionalBubbles;
private final DevicePolicyManager mDevicePolicyManager;
- private final UserManager mUserManager;
-
private final boolean mAreTaskAndTimeIndependentPrerequisitesMet;
- @VisibleForTesting()
- @Nullable ServiceConnector mProxyConnectorToMainProfile;
-
@Inject
public AppClipsService(@Application Context context, FeatureFlags featureFlags,
- Optional optionalBubbles, DevicePolicyManager devicePolicyManager,
- UserManager userManager) {
+ Optional optionalBubbles, DevicePolicyManager devicePolicyManager) {
mContext = context;
mFeatureFlags = featureFlags;
mOptionalBubbles = optionalBubbles;
mDevicePolicyManager = devicePolicyManager;
- mUserManager = userManager;
-
- // The consumer of this service are apps that call through StatusBarManager API to query if
- // it can use app clips API. Since these apps can be launched as work profile users, this
- // service will start as work profile user. SysUI doesn't share injected instances for
- // different users. This is why the bubbles instance injected will be incorrect. As the apps
- // don't generally have permission to connect to a service running as different user, we
- // start a proxy connection to communicate with the main user's version of this service.
- if (mUserManager.isManagedProfile()) {
- // No need to check for prerequisites in this case as those are incorrect for work
- // profile user instance of the service and the main user version of the service will
- // take care of this check.
- mAreTaskAndTimeIndependentPrerequisitesMet = false;
-
- // Get the main user so that we can connect to the main user's version of the service.
- UserHandle mainUser = mUserManager.getMainUser();
- if (mainUser == null) {
- // If main user is not available there isn't much we can do, no apps can use app
- // clips.
- return;
- }
-
- // Set up the connection to be used later during onBind callback.
- mProxyConnectorToMainProfile =
- new ServiceConnector.Impl<>(
- context,
- new Intent(context, AppClipsService.class),
- Context.BIND_AUTO_CREATE | Context.BIND_WAIVE_PRIORITY
- | Context.BIND_NOT_VISIBLE,
- mainUser.getIdentifier(),
- IAppClipsService.Stub::asInterface);
- return;
- }
mAreTaskAndTimeIndependentPrerequisitesMet = checkIndependentVariables();
- mProxyConnectorToMainProfile = null;
}
private boolean checkIndependentVariables() {
@@ -144,40 +107,25 @@ public class AppClipsService extends Service {
return new IAppClipsService.Stub() {
@Override
public boolean canLaunchCaptureContentActivityForNote(int taskId) {
- // In case of managed profile, use the main user's instance of the service. Callers
- // cannot directly connect to the main user's instance as they may not have the
- // permission to interact across users.
- if (mUserManager.isManagedProfile()) {
- return canLaunchCaptureContentActivityForNoteFromMainUser(taskId);
- }
+ return canLaunchCaptureContentActivityForNoteInternal(taskId)
+ == CAPTURE_CONTENT_FOR_NOTE_SUCCESS;
+ }
+ @Override
+ @CaptureContentForNoteStatusCodes
+ public int canLaunchCaptureContentActivityForNoteInternal(int taskId) {
if (!mAreTaskAndTimeIndependentPrerequisitesMet) {
- return false;
+ return CAPTURE_CONTENT_FOR_NOTE_FAILED;
}
if (!mOptionalBubbles.get().isAppBubbleTaskId(taskId)) {
- return false;
+ return CAPTURE_CONTENT_FOR_NOTE_WINDOW_MODE_UNSUPPORTED;
}
- return !mDevicePolicyManager.getScreenCaptureDisabled(null);
+ return mDevicePolicyManager.getScreenCaptureDisabled(null)
+ ? CAPTURE_CONTENT_FOR_NOTE_BLOCKED_BY_ADMIN
+ : CAPTURE_CONTENT_FOR_NOTE_SUCCESS;
}
};
}
-
- /** Returns whether the app clips API can be used by querying the service as the main user. */
- private boolean canLaunchCaptureContentActivityForNoteFromMainUser(int taskId) {
- if (mProxyConnectorToMainProfile == null) {
- return false;
- }
-
- try {
- AndroidFuture future = mProxyConnectorToMainProfile.postForResult(
- service -> service.canLaunchCaptureContentActivityForNote(taskId));
- return future.get();
- } catch (ExecutionException | InterruptedException e) {
- Log.d(TAG, "Exception from service\n" + e);
- }
-
- return false;
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivity.java b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivity.java
index f00803c6d64bc..6e5cef47400fe 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivity.java
@@ -22,41 +22,41 @@ import static android.content.Intent.CAPTURE_CONTENT_FOR_NOTE_SUCCESS;
import static android.content.Intent.CAPTURE_CONTENT_FOR_NOTE_WINDOW_MODE_UNSUPPORTED;
import static android.content.Intent.EXTRA_CAPTURE_CONTENT_FOR_NOTE_STATUS_CODE;
-import static com.android.systemui.flags.Flags.SCREENSHOT_APP_CLIPS;
import static com.android.systemui.screenshot.appclips.AppClipsEvent.SCREENSHOT_FOR_NOTE_TRIGGERED;
import android.app.Activity;
-import android.app.admin.DevicePolicyManager;
import android.content.ActivityNotFoundException;
import android.content.ComponentName;
+import android.content.Context;
import android.content.Intent;
+import android.content.Intent.CaptureContentForNoteStatusCodes;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.ApplicationInfoFlags;
import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.pm.UserInfo;
-import android.content.res.Resources;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Parcel;
import android.os.ResultReceiver;
import android.os.UserHandle;
-import android.os.UserManager;
import android.util.Log;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
+import com.android.internal.infra.AndroidFuture;
+import com.android.internal.infra.ServiceConnector;
import com.android.internal.logging.UiEventLogger;
+import com.android.internal.statusbar.IAppClipsService;
import com.android.systemui.R;
+import com.android.systemui.broadcast.BroadcastSender;
+import com.android.systemui.dagger.qualifiers.Application;
+import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.notetask.NoteTaskController;
import com.android.systemui.notetask.NoteTaskEntryPoint;
-import com.android.systemui.settings.UserTracker;
-import com.android.wm.shell.bubbles.Bubbles;
-import java.util.Optional;
+import java.util.concurrent.Executor;
import javax.inject.Inject;
@@ -82,39 +82,57 @@ public class AppClipsTrampolineActivity extends Activity {
private static final String TAG = AppClipsTrampolineActivity.class.getSimpleName();
static final String PERMISSION_SELF = "com.android.systemui.permission.SELF";
static final String EXTRA_SCREENSHOT_URI = TAG + "SCREENSHOT_URI";
- @VisibleForTesting
- static final String EXTRA_USE_WP_USER = TAG + "USE_WP_USER";
static final String ACTION_FINISH_FROM_TRAMPOLINE = TAG + "FINISH_FROM_TRAMPOLINE";
static final String EXTRA_RESULT_RECEIVER = TAG + "RESULT_RECEIVER";
static final String EXTRA_CALLING_PACKAGE_NAME = TAG + "CALLING_PACKAGE_NAME";
private static final ApplicationInfoFlags APPLICATION_INFO_FLAGS = ApplicationInfoFlags.of(0);
- private final DevicePolicyManager mDevicePolicyManager;
- private final FeatureFlags mFeatureFlags;
- private final Optional mOptionalBubbles;
private final NoteTaskController mNoteTaskController;
private final PackageManager mPackageManager;
- private final UserTracker mUserTracker;
private final UiEventLogger mUiEventLogger;
- private final UserManager mUserManager;
+ private final BroadcastSender mBroadcastSender;
+ @Background
+ private final Executor mBgExecutor;
+ @Main
+ private final Executor mMainExecutor;
private final ResultReceiver mResultReceiver;
+ private final ServiceConnector mAppClipsServiceConnector;
+
+ private UserHandle mUserHandle;
private Intent mKillAppClipsBroadcastIntent;
- private UserHandle mNotesAppUser;
@Inject
- public AppClipsTrampolineActivity(DevicePolicyManager devicePolicyManager, FeatureFlags flags,
- Optional optionalBubbles, NoteTaskController noteTaskController,
- PackageManager packageManager, UserTracker userTracker, UiEventLogger uiEventLogger,
- UserManager userManager, @Main Handler mainHandler) {
- mDevicePolicyManager = devicePolicyManager;
- mFeatureFlags = flags;
- mOptionalBubbles = optionalBubbles;
+ public AppClipsTrampolineActivity(@Application Context context,
+ NoteTaskController noteTaskController, PackageManager packageManager,
+ UiEventLogger uiEventLogger, BroadcastSender broadcastSender,
+ @Background Executor bgExecutor, @Main Executor mainExecutor,
+ @Main Handler mainHandler) {
mNoteTaskController = noteTaskController;
mPackageManager = packageManager;
- mUserTracker = userTracker;
mUiEventLogger = uiEventLogger;
- mUserManager = userManager;
+ mBroadcastSender = broadcastSender;
+ mBgExecutor = bgExecutor;
+ mMainExecutor = mainExecutor;
+
+ mResultReceiver = createResultReceiver(mainHandler);
+ mAppClipsServiceConnector = createServiceConnector(context);
+ }
+
+ /** A constructor used only for testing to verify interactions with {@link ServiceConnector}. */
+ @VisibleForTesting
+ AppClipsTrampolineActivity(ServiceConnector appClipsServiceConnector,
+ NoteTaskController noteTaskController, PackageManager packageManager,
+ UiEventLogger uiEventLogger, BroadcastSender broadcastSender,
+ @Background Executor bgExecutor, @Main Executor mainExecutor,
+ @Main Handler mainHandler) {
+ mAppClipsServiceConnector = appClipsServiceConnector;
+ mNoteTaskController = noteTaskController;
+ mPackageManager = packageManager;
+ mUiEventLogger = uiEventLogger;
+ mBroadcastSender = broadcastSender;
+ mBgExecutor = bgExecutor;
+ mMainExecutor = mainExecutor;
mResultReceiver = createResultReceiver(mainHandler);
}
@@ -127,62 +145,62 @@ public class AppClipsTrampolineActivity extends Activity {
return;
}
- if (mUserManager.isManagedProfile()) {
- maybeStartActivityForWPUser();
- finish();
+ mUserHandle = getUser();
+
+ mBgExecutor.execute(() -> {
+ AndroidFuture statusCodeFuture = mAppClipsServiceConnector.postForResult(
+ service -> service.canLaunchCaptureContentActivityForNoteInternal(getTaskId()));
+ statusCodeFuture.whenCompleteAsync(this::handleAppClipsStatusCode, mMainExecutor);
+ });
+ }
+
+ @Override
+ protected void onDestroy() {
+ if (isFinishing() && mKillAppClipsBroadcastIntent != null) {
+ mBroadcastSender.sendBroadcast(mKillAppClipsBroadcastIntent, PERMISSION_SELF);
+ }
+
+ super.onDestroy();
+ }
+
+ private void handleAppClipsStatusCode(@CaptureContentForNoteStatusCodes int statusCode,
+ Throwable error) {
+ if (isFinishing()) {
+ // It's too late, trampoline activity is finishing or already finished. Return early.
return;
}
- if (!mFeatureFlags.isEnabled(SCREENSHOT_APP_CLIPS)) {
- finish();
+ if (error != null) {
+ Log.d(TAG, "Error querying app clips service", error);
+ setErrorResultAndFinish(statusCode);
return;
}
- if (mOptionalBubbles.isEmpty()) {
- setErrorResultAndFinish(CAPTURE_CONTENT_FOR_NOTE_FAILED);
- return;
- }
+ switch (statusCode) {
+ case CAPTURE_CONTENT_FOR_NOTE_SUCCESS:
+ launchAppClipsActivity();
+ break;
- if (!mOptionalBubbles.get().isAppBubbleTaskId(getTaskId())) {
- setErrorResultAndFinish(CAPTURE_CONTENT_FOR_NOTE_WINDOW_MODE_UNSUPPORTED);
- return;
+ case CAPTURE_CONTENT_FOR_NOTE_FAILED:
+ case CAPTURE_CONTENT_FOR_NOTE_WINDOW_MODE_UNSUPPORTED:
+ case CAPTURE_CONTENT_FOR_NOTE_BLOCKED_BY_ADMIN:
+ default:
+ setErrorResultAndFinish(statusCode);
}
+ }
- if (mDevicePolicyManager.getScreenCaptureDisabled(null)) {
- setErrorResultAndFinish(CAPTURE_CONTENT_FOR_NOTE_BLOCKED_BY_ADMIN);
- return;
- }
-
- ComponentName componentName;
- try {
- componentName = ComponentName.unflattenFromString(
+ private void launchAppClipsActivity() {
+ ComponentName componentName = ComponentName.unflattenFromString(
getString(R.string.config_screenshotAppClipsActivityComponent));
- } catch (Resources.NotFoundException e) {
- setErrorResultAndFinish(CAPTURE_CONTENT_FOR_NOTE_FAILED);
- return;
- }
-
- if (componentName == null || componentName.getPackageName().isEmpty()
- || componentName.getClassName().isEmpty()) {
- setErrorResultAndFinish(CAPTURE_CONTENT_FOR_NOTE_FAILED);
- return;
- }
-
- mNotesAppUser = getUser();
- if (getIntent().getBooleanExtra(EXTRA_USE_WP_USER, /* defaultValue= */ false)) {
- // Get the work profile user internally instead of passing around via intent extras as
- // this activity is exported apps could potentially mess around with intent extras.
- mNotesAppUser = getWorkProfileUser().orElse(mNotesAppUser);
- }
-
String callingPackageName = getCallingPackage();
- Intent intent = new Intent().setComponent(componentName)
+
+ Intent intent = new Intent()
+ .setComponent(componentName)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
.putExtra(EXTRA_RESULT_RECEIVER, mResultReceiver)
.putExtra(EXTRA_CALLING_PACKAGE_NAME, callingPackageName);
try {
- // Start the App Clips activity for the user corresponding to the notes app user.
- startActivityAsUser(intent, mNotesAppUser);
+ startActivity(intent);
// Set up the broadcast intent that will inform the above App Clips activity to finish
// when this trampoline activity is finished.
@@ -198,39 +216,6 @@ public class AppClipsTrampolineActivity extends Activity {
}
}
- @Override
- protected void onDestroy() {
- super.onDestroy();
-
- if (isFinishing() && mKillAppClipsBroadcastIntent != null) {
- sendBroadcast(mKillAppClipsBroadcastIntent, PERMISSION_SELF);
- }
- }
-
- private Optional getWorkProfileUser() {
- return mUserTracker.getUserProfiles().stream()
- .filter(profile -> mUserManager.isManagedProfile(profile.id))
- .findFirst()
- .map(UserInfo::getUserHandle);
- }
-
- private void maybeStartActivityForWPUser() {
- UserHandle mainUser = mUserManager.getMainUser();
- if (mainUser == null) {
- setErrorResultAndFinish(CAPTURE_CONTENT_FOR_NOTE_FAILED);
- return;
- }
-
- // Start the activity as the main user with activity result forwarding. Set the intent extra
- // so that the newly started trampoline activity starts the actual app clips activity as the
- // work profile user. Starting the app clips activity as the work profile user is required
- // to save the screenshot in work profile user storage and grant read permission to the URI.
- startActivityAsUser(
- new Intent(this, AppClipsTrampolineActivity.class)
- .putExtra(EXTRA_USE_WP_USER, /* value= */ true)
- .addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT), mainUser);
- }
-
private void setErrorResultAndFinish(int errorCode) {
setResult(RESULT_OK,
new Intent().putExtra(EXTRA_CAPTURE_CONTENT_FOR_NOTE_STATUS_CODE, errorCode));
@@ -241,7 +226,7 @@ public class AppClipsTrampolineActivity extends Activity {
int callingPackageUid = 0;
try {
callingPackageUid = mPackageManager.getApplicationInfoAsUser(callingPackageName,
- APPLICATION_INFO_FLAGS, mNotesAppUser.getIdentifier()).uid;
+ APPLICATION_INFO_FLAGS, mUserHandle.getIdentifier()).uid;
} catch (NameNotFoundException e) {
Log.d(TAG, "Couldn't find notes app UID " + e);
}
@@ -281,7 +266,7 @@ public class AppClipsTrampolineActivity extends Activity {
mKillAppClipsBroadcastIntent = null;
// Expand the note bubble before returning the result.
- mNoteTaskController.showNoteTaskAsUser(NoteTaskEntryPoint.APP_CLIPS, mNotesAppUser);
+ mNoteTaskController.showNoteTaskAsUser(NoteTaskEntryPoint.APP_CLIPS, mUserHandle);
setResult(RESULT_OK, convertedData);
finish();
}
@@ -298,11 +283,18 @@ public class AppClipsTrampolineActivity extends Activity {
appClipsResultReceiver.writeToParcel(parcel, 0);
parcel.setDataPosition(0);
- ResultReceiver resultReceiver = ResultReceiver.CREATOR.createFromParcel(parcel);
+ ResultReceiver resultReceiver = ResultReceiver.CREATOR.createFromParcel(parcel);
parcel.recycle();
return resultReceiver;
}
+ private ServiceConnector createServiceConnector(
+ @Application Context context) {
+ return new ServiceConnector.Impl<>(context, new Intent(context, AppClipsService.class),
+ Context.BIND_AUTO_CREATE | Context.BIND_WAIVE_PRIORITY | Context.BIND_NOT_VISIBLE,
+ UserHandle.USER_SYSTEM, IAppClipsService.Stub::asInterface);
+ }
+
/** This is a test only API for mocking response from {@link AppClipsActivity}. */
@VisibleForTesting
public ResultReceiver getResultReceiverForTest() {
diff --git a/packages/SystemUI/tests/AndroidManifest.xml b/packages/SystemUI/tests/AndroidManifest.xml
index 080be6d8cf25a..12da17f6a3012 100644
--- a/packages/SystemUI/tests/AndroidManifest.xml
+++ b/packages/SystemUI/tests/AndroidManifest.xml
@@ -191,12 +191,6 @@
android:permission="com.android.systemui.permission.SELF"
android:excludeFromRecents="true" />
-
-
+) : NoteTaskBubblesController(unUsed1, unsUsed2) {
+ override suspend fun areBubblesAvailable() = optionalBubbles.isPresent
+
+ override suspend fun showOrHideAppBubble(intent: Intent, userHandle: UserHandle, icon: Icon) {
+ optionalBubbles.ifPresentOrElse(
+ { bubbles -> bubbles.showOrHideAppBubble(intent, userHandle, icon) },
+ { throw IllegalAccessException() }
+ )
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskBubblesServiceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskBubblesServiceTest.kt
new file mode 100644
index 0000000000000..baac9e0202803
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskBubblesServiceTest.kt
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.notetask
+
+import android.content.Intent
+import android.graphics.drawable.Icon
+import android.os.UserHandle
+import androidx.test.filters.SmallTest
+import androidx.test.runner.AndroidJUnit4
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.notetask.NoteTaskBubblesController.NoteTaskBubblesService
+import com.android.wm.shell.bubbles.Bubbles
+import com.google.common.truth.Truth.assertThat
+import java.util.Optional
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+/** atest SystemUITests:NoteTaskBubblesServiceTest */
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+internal class NoteTaskBubblesServiceTest : SysuiTestCase() {
+
+ @Mock private lateinit var bubbles: Bubbles
+
+ private fun createServiceBinder(bubbles: Bubbles? = this.bubbles) =
+ NoteTaskBubblesService(Optional.ofNullable(bubbles)).onBind(Intent())
+ as INoteTaskBubblesService
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ }
+
+ @Test
+ fun areBubblesAvailable_bubblesNotNull_shouldReturnTrue() {
+ assertThat(createServiceBinder().areBubblesAvailable()).isTrue()
+ }
+
+ @Test
+ fun areBubblesAvailable_bubblesNull_shouldReturnFalse() {
+ assertThat(createServiceBinder(bubbles = null).areBubblesAvailable()).isFalse()
+ }
+
+ @Test
+ fun showOrHideAppBubble() {
+ val intent = Intent()
+ val user = UserHandle.SYSTEM
+ val icon = Icon.createWithResource(context, R.drawable.ic_note_task_shortcut_widget)
+
+ createServiceBinder().showOrHideAppBubble(intent, user, icon)
+
+ verify(bubbles).showOrHideAppBubble(intent, user, icon)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
index 967e44bb6b848..8b070ef875042 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
@@ -57,7 +57,6 @@ import com.android.systemui.notetask.NoteTaskEntryPoint.TAIL_BUTTON
import com.android.systemui.notetask.NoteTaskEntryPoint.WIDGET_PICKER_SHORTCUT
import com.android.systemui.notetask.shortcut.CreateNoteTaskShortcutActivity
import com.android.systemui.notetask.shortcut.LaunchNoteTaskActivity
-import com.android.systemui.notetask.shortcut.LaunchNoteTaskManagedProfileProxyActivity
import com.android.systemui.settings.FakeUserTracker
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.argumentCaptor
@@ -69,11 +68,15 @@ import com.android.wm.shell.bubbles.Bubble
import com.android.wm.shell.bubbles.Bubbles
import com.google.common.truth.Truth.assertThat
import java.util.Optional
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mock
+import org.mockito.Mockito.atLeastOnce
import org.mockito.Mockito.doNothing
import org.mockito.Mockito.never
import org.mockito.Mockito.spy
@@ -82,6 +85,7 @@ import org.mockito.Mockito.verifyZeroInteractions
import org.mockito.MockitoAnnotations
/** atest SystemUITests:NoteTaskControllerTest */
+@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
internal class NoteTaskControllerTest : SysuiTestCase() {
@@ -101,6 +105,8 @@ internal class NoteTaskControllerTest : SysuiTestCase() {
@Mock private lateinit var devicePolicyManager: DevicePolicyManager
@Mock private lateinit var secureSettings: SecureSettings
private val userTracker = FakeUserTracker()
+ private val testDispatcher = UnconfinedTestDispatcher()
+ private val testScope = TestScope(testDispatcher)
@Before
fun setUp() {
@@ -136,7 +142,6 @@ internal class NoteTaskControllerTest : SysuiTestCase() {
context = context,
resolver = resolver,
eventLogger = eventLogger,
- optionalBubbles = Optional.ofNullable(bubbles),
userManager = userManager,
keyguardManager = keyguardManager,
isEnabled = isEnabled,
@@ -146,6 +151,9 @@ internal class NoteTaskControllerTest : SysuiTestCase() {
shortcutManager = shortcutManager,
activityManager = activityManager,
secureSettings = secureSettings,
+ noteTaskBubblesController =
+ FakeNoteTaskBubbleController(context, testDispatcher, Optional.ofNullable(bubbles)),
+ applicationScope = testScope,
)
// region onBubbleExpandChanged
@@ -161,7 +169,7 @@ internal class NoteTaskControllerTest : SysuiTestCase() {
)
verify(eventLogger).logNoteTaskOpened(expectedInfo)
- verifyZeroInteractions(context, bubbles, keyguardManager, userManager)
+ verifyZeroInteractions(bubbles, keyguardManager, userManager)
}
@Test
@@ -176,7 +184,7 @@ internal class NoteTaskControllerTest : SysuiTestCase() {
)
verify(eventLogger).logNoteTaskClosed(expectedInfo)
- verifyZeroInteractions(context, bubbles, keyguardManager, userManager)
+ verifyZeroInteractions(bubbles, keyguardManager, userManager)
}
@Test
@@ -190,7 +198,7 @@ internal class NoteTaskControllerTest : SysuiTestCase() {
key = Bubble.getAppBubbleKeyForApp(expectedInfo.packageName, expectedInfo.user),
)
- verifyZeroInteractions(context, bubbles, keyguardManager, userManager, eventLogger)
+ verifyZeroInteractions(bubbles, keyguardManager, userManager, eventLogger)
}
@Test
@@ -204,7 +212,7 @@ internal class NoteTaskControllerTest : SysuiTestCase() {
key = Bubble.getAppBubbleKeyForApp(expectedInfo.packageName, expectedInfo.user),
)
- verifyZeroInteractions(context, bubbles, keyguardManager, userManager, eventLogger)
+ verifyZeroInteractions(bubbles, keyguardManager, userManager, eventLogger)
}
@Test
@@ -215,7 +223,7 @@ internal class NoteTaskControllerTest : SysuiTestCase() {
key = "any other key",
)
- verifyZeroInteractions(context, bubbles, keyguardManager, userManager, eventLogger)
+ verifyZeroInteractions(bubbles, keyguardManager, userManager, eventLogger)
}
@Test
@@ -226,7 +234,7 @@ internal class NoteTaskControllerTest : SysuiTestCase() {
key = Bubble.getAppBubbleKeyForApp(NOTE_TASK_INFO.packageName, NOTE_TASK_INFO.user),
)
- verifyZeroInteractions(context, bubbles, keyguardManager, userManager, eventLogger)
+ verifyZeroInteractions(bubbles, keyguardManager, userManager, eventLogger)
}
// endregion
@@ -352,7 +360,7 @@ internal class NoteTaskControllerTest : SysuiTestCase() {
createNoteTaskController().showNoteTask(entryPoint = expectedInfo.entryPoint!!)
// Context package name used to create bubble icon from drawable resource id
- verify(context).packageName
+ verify(context, atLeastOnce()).packageName
verifyNoteTaskOpenInBubbleInUser(userTracker.userHandle)
verifyZeroInteractions(eventLogger)
}
@@ -361,7 +369,7 @@ internal class NoteTaskControllerTest : SysuiTestCase() {
fun showNoteTask_bubblesIsNull_shouldDoNothing() {
createNoteTaskController(bubbles = null).showNoteTask(entryPoint = TAIL_BUTTON)
- verifyZeroInteractions(context, bubbles, eventLogger)
+ verifyZeroInteractions(bubbles, eventLogger)
}
@Test
@@ -373,14 +381,14 @@ internal class NoteTaskControllerTest : SysuiTestCase() {
noteTaskController.showNoteTask(entryPoint = TAIL_BUTTON)
verify(noteTaskController).showNoDefaultNotesAppToast()
- verifyZeroInteractions(context, bubbles, eventLogger)
+ verifyZeroInteractions(bubbles, eventLogger)
}
@Test
fun showNoteTask_flagDisabled_shouldDoNothing() {
createNoteTaskController(isEnabled = false).showNoteTask(entryPoint = TAIL_BUTTON)
- verifyZeroInteractions(context, bubbles, eventLogger)
+ verifyZeroInteractions(bubbles, eventLogger)
}
@Test
@@ -389,7 +397,7 @@ internal class NoteTaskControllerTest : SysuiTestCase() {
createNoteTaskController().showNoteTask(entryPoint = TAIL_BUTTON)
- verifyZeroInteractions(context, bubbles, eventLogger)
+ verifyZeroInteractions(bubbles, eventLogger)
}
@Test
@@ -509,7 +517,7 @@ internal class NoteTaskControllerTest : SysuiTestCase() {
createNoteTaskController().showNoteTask(entryPoint = QUICK_AFFORDANCE)
- verifyZeroInteractions(context, bubbles, eventLogger)
+ verifyZeroInteractions(bubbles, eventLogger)
}
@Test
@@ -525,7 +533,7 @@ internal class NoteTaskControllerTest : SysuiTestCase() {
createNoteTaskController().showNoteTask(entryPoint = QUICK_AFFORDANCE)
- verifyZeroInteractions(context, bubbles, eventLogger)
+ verifyZeroInteractions(bubbles, eventLogger)
}
@Test
@@ -637,7 +645,7 @@ internal class NoteTaskControllerTest : SysuiTestCase() {
createNoteTaskController(isEnabled = true).onRoleHoldersChanged("NOT_NOTES", user)
- verifyZeroInteractions(context)
+ verify(context, never()).startActivityAsUser(any(), any())
}
@Test
@@ -733,21 +741,6 @@ internal class NoteTaskControllerTest : SysuiTestCase() {
}
// endregion
- // startregion startNoteTaskProxyActivityForUser
- @Test
- fun startNoteTaskProxyActivityForUser_shouldStartLaunchNoteTaskProxyActivityWithExpectedUser() {
- val user0 = UserHandle.of(0)
- createNoteTaskController().startNoteTaskProxyActivityForUser(user0)
-
- val intentCaptor = argumentCaptor()
- verify(context).startActivityAsUser(intentCaptor.capture(), eq(user0))
- assertThat(intentCaptor.value).run {
- hasComponentClass(LaunchNoteTaskManagedProfileProxyActivity::class.java)
- hasFlags(FLAG_ACTIVITY_NEW_TASK)
- }
- }
- // endregion
-
// region getUserForHandlingNotesTaking
@Test
fun getUserForHandlingNotesTaking_cope_quickAffordance_shouldReturnWorkProfileUser() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivityTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivityTest.kt
index a0c376ff1a1ca..627c4a80e1ecf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivityTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivityTest.kt
@@ -17,9 +17,6 @@
package com.android.systemui.notetask.shortcut
import android.content.Intent
-import android.content.pm.UserInfo
-import android.os.UserHandle
-import android.os.UserManager
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import androidx.test.filters.SmallTest
@@ -29,17 +26,14 @@ import com.android.dx.mockito.inline.extended.ExtendedMockito.verify
import com.android.systemui.SysuiTestCase
import com.android.systemui.notetask.NoteTaskController
import com.android.systemui.notetask.NoteTaskEntryPoint
-import com.android.systemui.settings.FakeUserTracker
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.eq
-import com.android.systemui.util.mockito.whenever
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
-import org.mockito.Mockito.never
import org.mockito.MockitoAnnotations
@RunWith(AndroidTestingRunner::class)
@@ -48,8 +42,6 @@ import org.mockito.MockitoAnnotations
class LaunchNoteTaskActivityTest : SysuiTestCase() {
@Mock lateinit var noteTaskController: NoteTaskController
- @Mock lateinit var userManager: UserManager
- private val userTracker: FakeUserTracker = FakeUserTracker()
@Rule
@JvmField
@@ -60,8 +52,6 @@ class LaunchNoteTaskActivityTest : SysuiTestCase() {
override fun create(intent: Intent?) =
LaunchNoteTaskActivity(
controller = noteTaskController,
- userManager = userManager,
- userTracker = userTracker
)
},
/* initialTouchMode= */ false,
@@ -71,7 +61,6 @@ class LaunchNoteTaskActivityTest : SysuiTestCase() {
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
- whenever(userManager.isManagedProfile(eq(workProfileUser.id))).thenReturn(true)
}
@After
@@ -83,36 +72,7 @@ class LaunchNoteTaskActivityTest : SysuiTestCase() {
fun startActivityOnNonWorkProfileUser_shouldLaunchNoteTask() {
activityRule.launchActivity(/* startIntent= */ null)
- verify(noteTaskController).showNoteTask(eq(NoteTaskEntryPoint.WIDGET_PICKER_SHORTCUT))
- }
-
- @Test
- fun startActivityOnWorkProfileUser_shouldLaunchProxyActivity() {
- val mainUserHandle: UserHandle = mainUser.userHandle
- userTracker.set(listOf(mainUser, workProfileUser), selectedUserIndex = 1)
- whenever(userManager.isManagedProfile).thenReturn(true)
- whenever(userManager.mainUser).thenReturn(mainUserHandle)
-
- activityRule.launchActivity(/* startIntent= */ null)
-
- verify(noteTaskController).startNoteTaskProxyActivityForUser(eq(mainUserHandle))
- }
-
- @Test
- fun startActivityOnWorkProfileUser_noMainUser_shouldNotLaunch() {
- userTracker.set(listOf(mainUser, workProfileUser), selectedUserIndex = 1)
- whenever(userManager.isManagedProfile).thenReturn(true)
- whenever(userManager.mainUser).thenReturn(null)
-
- activityRule.launchActivity(/* startIntent= */ null)
-
- verify(noteTaskController, never()).showNoteTask(any())
- verify(noteTaskController, never()).startNoteTaskProxyActivityForUser(any())
- }
-
- private companion object {
- val mainUser = UserInfo(/* id= */ 0, /* name= */ "primary", /* flags= */ UserInfo.FLAG_MAIN)
- val workProfileUser =
- UserInfo(/* id= */ 10, /* name= */ "work", /* flags= */ UserInfo.FLAG_PROFILE)
+ verify(noteTaskController)
+ .showNoteTaskAsUser(eq(NoteTaskEntryPoint.WIDGET_PICKER_SHORTCUT), any())
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskManagedProfileProxyActivityTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskManagedProfileProxyActivityTest.kt
deleted file mode 100644
index 6347c3404348e..0000000000000
--- a/packages/SystemUI/tests/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskManagedProfileProxyActivityTest.kt
+++ /dev/null
@@ -1,111 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.notetask.shortcut
-
-import android.content.Intent
-import android.content.pm.UserInfo
-import android.os.UserHandle
-import android.os.UserManager
-import android.testing.AndroidTestingRunner
-import android.testing.TestableLooper
-import androidx.test.filters.SmallTest
-import androidx.test.rule.ActivityTestRule
-import androidx.test.runner.intercepting.SingleActivityFactory
-import com.android.dx.mockito.inline.extended.ExtendedMockito.never
-import com.android.dx.mockito.inline.extended.ExtendedMockito.verify
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.notetask.NoteTaskController
-import com.android.systemui.notetask.NoteTaskEntryPoint
-import com.android.systemui.settings.FakeUserTracker
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.eq
-import com.android.systemui.util.mockito.whenever
-import org.junit.After
-import org.junit.Before
-import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.Mock
-import org.mockito.MockitoAnnotations
-
-@RunWith(AndroidTestingRunner::class)
-@SmallTest
-@TestableLooper.RunWithLooper
-class LaunchNoteTaskManagedProfileProxyActivityTest : SysuiTestCase() {
-
- @Mock lateinit var noteTaskController: NoteTaskController
- @Mock lateinit var userManager: UserManager
- private val userTracker = FakeUserTracker()
-
- @Rule
- @JvmField
- val activityRule =
- ActivityTestRule(
- /* activityFactory= */ object :
- SingleActivityFactory(
- LaunchNoteTaskManagedProfileProxyActivity::class.java
- ) {
- override fun create(intent: Intent?) =
- LaunchNoteTaskManagedProfileProxyActivity(
- controller = noteTaskController,
- userManager = userManager,
- userTracker = userTracker
- )
- },
- /* initialTouchMode= */ false,
- /* launchActivity= */ false,
- )
-
- @Before
- fun setUp() {
- MockitoAnnotations.initMocks(this)
- whenever(userManager.isManagedProfile(eq(workProfileUser.id))).thenReturn(true)
- }
-
- @After
- fun tearDown() {
- activityRule.finishActivity()
- }
-
- @Test
- fun startActivity_noWorkProfileUser_shouldNotLaunchNoteTask() {
- userTracker.set(listOf(mainUser), selectedUserIndex = 0)
- activityRule.launchActivity(/* startIntent= */ null)
-
- verify(noteTaskController, never()).showNoteTaskAsUser(any(), any())
- }
-
- @Test
- fun startActivity_hasWorkProfileUser_shouldLaunchNoteTaskOnTheWorkProfileUser() {
- userTracker.set(mainAndWorkProfileUsers, mainAndWorkProfileUsers.indexOf(mainUser))
- activityRule.launchActivity(/* startIntent= */ null)
-
- val workProfileUserHandle: UserHandle = workProfileUser.userHandle
- verify(noteTaskController)
- .showNoteTaskAsUser(
- eq(NoteTaskEntryPoint.WIDGET_PICKER_SHORTCUT),
- eq(workProfileUserHandle)
- )
- }
-
- private companion object {
- val mainUser = UserInfo(/* id= */ 0, /* name= */ "primary", /* flags= */ UserInfo.FLAG_MAIN)
- val workProfileUser =
- UserInfo(/* id= */ 10, /* name= */ "work", /* flags= */ UserInfo.FLAG_PROFILE)
- val mainAndWorkProfileUsers = listOf(mainUser, workProfileUser)
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsServiceTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsServiceTest.java
index 67b1099c1e0a3..d8897e9048c32 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsServiceTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsServiceTest.java
@@ -16,14 +16,17 @@
package com.android.systemui.screenshot.appclips;
+import static android.content.Intent.CAPTURE_CONTENT_FOR_NOTE_BLOCKED_BY_ADMIN;
+import static android.content.Intent.CAPTURE_CONTENT_FOR_NOTE_FAILED;
+import static android.content.Intent.CAPTURE_CONTENT_FOR_NOTE_SUCCESS;
+import static android.content.Intent.CAPTURE_CONTENT_FOR_NOTE_WINDOW_MODE_UNSUPPORTED;
+
import static com.android.systemui.flags.Flags.SCREENSHOT_APP_CLIPS;
import static com.google.common.truth.Truth.assertThat;
-import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.admin.DevicePolicyManager;
@@ -31,8 +34,6 @@ import android.content.Context;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;
-import android.os.UserHandle;
-import android.os.UserManager;
import androidx.test.runner.AndroidJUnit4;
@@ -46,7 +47,6 @@ import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
-import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import java.util.Optional;
@@ -63,7 +63,6 @@ public final class AppClipsServiceTest extends SysuiTestCase {
@Mock private Optional mOptionalBubbles;
@Mock private Bubbles mBubbles;
@Mock private DevicePolicyManager mDevicePolicyManager;
- @Mock private UserManager mUserManager;
private AppClipsService mAppClipsService;
@@ -80,51 +79,84 @@ public final class AppClipsServiceTest extends SysuiTestCase {
.canLaunchCaptureContentActivityForNote(FAKE_TASK_ID)).isFalse();
}
+ @Test
+ public void flagOff_internal_shouldReturnFailed() throws RemoteException {
+ when(mFeatureFlags.isEnabled(SCREENSHOT_APP_CLIPS)).thenReturn(false);
+
+ assertThat(getInterfaceWithRealContext()
+ .canLaunchCaptureContentActivityForNoteInternal(FAKE_TASK_ID))
+ .isEqualTo(CAPTURE_CONTENT_FOR_NOTE_FAILED);
+ }
+
@Test
public void emptyBubbles_shouldReturnFalse() throws RemoteException {
- when(mFeatureFlags.isEnabled(SCREENSHOT_APP_CLIPS)).thenReturn(true);
- when(mOptionalBubbles.isEmpty()).thenReturn(true);
+ mockForEmptyBubbles();
assertThat(getInterfaceWithRealContext()
.canLaunchCaptureContentActivityForNote(FAKE_TASK_ID)).isFalse();
}
+ @Test
+ public void emptyBubbles_internal_shouldReturnFailed() throws RemoteException {
+ mockForEmptyBubbles();
+
+ assertThat(getInterfaceWithRealContext()
+ .canLaunchCaptureContentActivityForNoteInternal(FAKE_TASK_ID))
+ .isEqualTo(CAPTURE_CONTENT_FOR_NOTE_FAILED);
+ }
+
@Test
public void taskIdNotAppBubble_shouldReturnFalse() throws RemoteException {
- when(mFeatureFlags.isEnabled(SCREENSHOT_APP_CLIPS)).thenReturn(true);
- when(mOptionalBubbles.isEmpty()).thenReturn(false);
- when(mOptionalBubbles.get()).thenReturn(mBubbles);
- when(mBubbles.isAppBubbleTaskId(eq((FAKE_TASK_ID)))).thenReturn(false);
+ mockForTaskIdNotAppBubble();
assertThat(getInterfaceWithRealContext()
.canLaunchCaptureContentActivityForNote(FAKE_TASK_ID)).isFalse();
}
+ @Test
+ public void taskIdNotAppBubble_internal_shouldReturnWindowUnsupported() throws RemoteException {
+ mockForTaskIdNotAppBubble();
+
+ assertThat(getInterfaceWithRealContext()
+ .canLaunchCaptureContentActivityForNoteInternal(FAKE_TASK_ID))
+ .isEqualTo(CAPTURE_CONTENT_FOR_NOTE_WINDOW_MODE_UNSUPPORTED);
+ }
+
@Test
public void dpmScreenshotBlocked_shouldReturnFalse() throws RemoteException {
- when(mFeatureFlags.isEnabled(SCREENSHOT_APP_CLIPS)).thenReturn(true);
- when(mOptionalBubbles.isEmpty()).thenReturn(false);
- when(mOptionalBubbles.get()).thenReturn(mBubbles);
- when(mBubbles.isAppBubbleTaskId(eq((FAKE_TASK_ID)))).thenReturn(true);
- when(mDevicePolicyManager.getScreenCaptureDisabled(eq(null))).thenReturn(true);
+ mockForScreenshotBlocked();
assertThat(getInterfaceWithRealContext()
.canLaunchCaptureContentActivityForNote(FAKE_TASK_ID)).isFalse();
}
+ @Test
+ public void dpmScreenshotBlocked_internal_shouldReturnBlockedByAdmin() throws RemoteException {
+ mockForScreenshotBlocked();
+
+ assertThat(getInterfaceWithRealContext()
+ .canLaunchCaptureContentActivityForNoteInternal(FAKE_TASK_ID))
+ .isEqualTo(CAPTURE_CONTENT_FOR_NOTE_BLOCKED_BY_ADMIN);
+ }
+
@Test
public void configComponentNameNotValid_shouldReturnFalse() throws RemoteException {
- when(mMockContext.getString(anyInt())).thenReturn(EMPTY);
- when(mFeatureFlags.isEnabled(SCREENSHOT_APP_CLIPS)).thenReturn(true);
- when(mOptionalBubbles.isEmpty()).thenReturn(false);
- when(mOptionalBubbles.get()).thenReturn(mBubbles);
- when(mBubbles.isAppBubbleTaskId(eq((FAKE_TASK_ID)))).thenReturn(true);
- when(mDevicePolicyManager.getScreenCaptureDisabled(eq(null))).thenReturn(false);
+ mockForInvalidConfigComponentName();
assertThat(getInterfaceWithMockContext()
.canLaunchCaptureContentActivityForNote(FAKE_TASK_ID)).isFalse();
}
+ @Test
+ public void configComponentNameNotValid_internal_shouldReturnFailed() throws RemoteException {
+ mockForInvalidConfigComponentName();
+
+ assertThat(getInterfaceWithMockContext()
+ .canLaunchCaptureContentActivityForNoteInternal(FAKE_TASK_ID))
+ .isEqualTo(CAPTURE_CONTENT_FOR_NOTE_FAILED);
+ }
+
+
@Test
public void allPrerequisitesSatisfy_shouldReturnTrue() throws RemoteException {
mockToSatisfyAllPrerequisites();
@@ -134,28 +166,44 @@ public final class AppClipsServiceTest extends SysuiTestCase {
}
@Test
- public void isManagedProfile_shouldUseProxyConnection() throws RemoteException {
- when(mUserManager.isManagedProfile()).thenReturn(true);
- when(mUserManager.getMainUser()).thenReturn(UserHandle.SYSTEM);
- IAppClipsService service = getInterfaceWithRealContext();
- mAppClipsService.mProxyConnectorToMainProfile =
- Mockito.spy(mAppClipsService.mProxyConnectorToMainProfile);
+ public void allPrerequisitesSatisfy_internal_shouldReturnSuccess() throws RemoteException {
+ mockToSatisfyAllPrerequisites();
- service.canLaunchCaptureContentActivityForNote(FAKE_TASK_ID);
-
- verify(mAppClipsService.mProxyConnectorToMainProfile).postForResult(any());
+ assertThat(getInterfaceWithRealContext()
+ .canLaunchCaptureContentActivityForNoteInternal(FAKE_TASK_ID))
+ .isEqualTo(CAPTURE_CONTENT_FOR_NOTE_SUCCESS);
}
- @Test
- public void isManagedProfile_noMainUser_shouldReturnFalse() {
- when(mUserManager.isManagedProfile()).thenReturn(true);
- when(mUserManager.getMainUser()).thenReturn(null);
-
- getInterfaceWithRealContext();
-
- assertThat(mAppClipsService.mProxyConnectorToMainProfile).isNull();
+ private void mockForEmptyBubbles() {
+ when(mFeatureFlags.isEnabled(SCREENSHOT_APP_CLIPS)).thenReturn(true);
+ when(mOptionalBubbles.isEmpty()).thenReturn(true);
}
+ private void mockForTaskIdNotAppBubble() {
+ when(mFeatureFlags.isEnabled(SCREENSHOT_APP_CLIPS)).thenReturn(true);
+ when(mOptionalBubbles.isEmpty()).thenReturn(false);
+ when(mOptionalBubbles.get()).thenReturn(mBubbles);
+ when(mBubbles.isAppBubbleTaskId(eq((FAKE_TASK_ID)))).thenReturn(false);
+ }
+
+ private void mockForScreenshotBlocked() {
+ when(mFeatureFlags.isEnabled(SCREENSHOT_APP_CLIPS)).thenReturn(true);
+ when(mOptionalBubbles.isEmpty()).thenReturn(false);
+ when(mOptionalBubbles.get()).thenReturn(mBubbles);
+ when(mBubbles.isAppBubbleTaskId(eq((FAKE_TASK_ID)))).thenReturn(true);
+ when(mDevicePolicyManager.getScreenCaptureDisabled(eq(null))).thenReturn(true);
+ }
+
+ private void mockForInvalidConfigComponentName() {
+ when(mMockContext.getString(anyInt())).thenReturn(EMPTY);
+ when(mFeatureFlags.isEnabled(SCREENSHOT_APP_CLIPS)).thenReturn(true);
+ when(mOptionalBubbles.isEmpty()).thenReturn(false);
+ when(mOptionalBubbles.get()).thenReturn(mBubbles);
+ when(mBubbles.isAppBubbleTaskId(eq((FAKE_TASK_ID)))).thenReturn(true);
+ when(mDevicePolicyManager.getScreenCaptureDisabled(eq(null))).thenReturn(false);
+ }
+
+
private void mockToSatisfyAllPrerequisites() {
when(mFeatureFlags.isEnabled(SCREENSHOT_APP_CLIPS)).thenReturn(true);
when(mOptionalBubbles.isEmpty()).thenReturn(false);
@@ -166,13 +214,13 @@ public final class AppClipsServiceTest extends SysuiTestCase {
private IAppClipsService getInterfaceWithRealContext() {
mAppClipsService = new AppClipsService(getContext(), mFeatureFlags,
- mOptionalBubbles, mDevicePolicyManager, mUserManager);
+ mOptionalBubbles, mDevicePolicyManager);
return getInterfaceFromService(mAppClipsService);
}
private IAppClipsService getInterfaceWithMockContext() {
mAppClipsService = new AppClipsService(mMockContext, mFeatureFlags,
- mOptionalBubbles, mDevicePolicyManager, mUserManager);
+ mOptionalBubbles, mDevicePolicyManager);
return getInterfaceFromService(mAppClipsService);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivityTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivityTest.java
index e9007ff84f13e..7fad972d83fcd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivityTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivityTest.java
@@ -24,22 +24,19 @@ import static android.content.Intent.CAPTURE_CONTENT_FOR_NOTE_USER_CANCELED;
import static android.content.Intent.CAPTURE_CONTENT_FOR_NOTE_WINDOW_MODE_UNSUPPORTED;
import static android.content.Intent.EXTRA_CAPTURE_CONTENT_FOR_NOTE_STATUS_CODE;
-import static com.android.systemui.flags.Flags.SCREENSHOT_APP_CLIPS;
+import static com.android.internal.infra.AndroidFuture.completedFuture;
import static com.android.systemui.screenshot.appclips.AppClipsEvent.SCREENSHOT_FOR_NOTE_TRIGGERED;
import static com.android.systemui.screenshot.appclips.AppClipsTrampolineActivity.EXTRA_SCREENSHOT_URI;
-import static com.android.systemui.screenshot.appclips.AppClipsTrampolineActivity.EXTRA_USE_WP_USER;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assume.assumeFalse;
import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.Activity;
-import android.app.admin.DevicePolicyManager;
import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.ActivityInfo;
@@ -52,20 +49,22 @@ import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.UserHandle;
-import android.os.UserManager;
import android.testing.AndroidTestingRunner;
import androidx.test.rule.ActivityTestRule;
import androidx.test.runner.intercepting.SingleActivityFactory;
+import com.android.internal.infra.ServiceConnector;
import com.android.internal.logging.UiEventLogger;
+import com.android.internal.statusbar.IAppClipsService;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.broadcast.BroadcastSender;
+import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.notetask.NoteTaskController;
-import com.android.systemui.settings.UserTracker;
-import com.android.wm.shell.bubbles.Bubbles;
+
+import com.google.common.util.concurrent.MoreExecutors;
import org.junit.After;
import org.junit.Before;
@@ -75,8 +74,7 @@ import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
-import java.util.List;
-import java.util.Optional;
+import java.util.concurrent.Executor;
@RunWith(AndroidTestingRunner.class)
public final class AppClipsTrampolineActivityTest extends SysuiTestCase {
@@ -86,25 +84,19 @@ public final class AppClipsTrampolineActivityTest extends SysuiTestCase {
private static final int TEST_UID = 42;
private static final String TEST_CALLING_PACKAGE = "test-calling-package";
- @Mock
- private DevicePolicyManager mDevicePolicyManager;
- @Mock
- private FeatureFlags mFeatureFlags;
- @Mock
- private Optional mOptionalBubbles;
- @Mock
- private Bubbles mBubbles;
+ @Mock private ServiceConnector mServiceConnector;
@Mock
private NoteTaskController mNoteTaskController;
@Mock
private PackageManager mPackageManager;
@Mock
- private UserTracker mUserTracker;
- @Mock
private UiEventLogger mUiEventLogger;
@Mock
- private UserManager mUserManager;
-
+ private BroadcastSender mBroadcastSender;
+ @Background
+ private Executor mBgExecutor;
+ @Main
+ private Executor mMainExecutor;
@Main
private Handler mMainHandler;
@@ -114,9 +106,9 @@ public final class AppClipsTrampolineActivityTest extends SysuiTestCase {
new SingleActivityFactory<>(AppClipsTrampolineActivityTestable.class) {
@Override
protected AppClipsTrampolineActivityTestable create(Intent unUsed) {
- return new AppClipsTrampolineActivityTestable(mDevicePolicyManager,
- mFeatureFlags, mOptionalBubbles, mNoteTaskController, mPackageManager,
- mUserTracker, mUiEventLogger, mUserManager, mMainHandler);
+ return new AppClipsTrampolineActivityTestable(mServiceConnector,
+ mNoteTaskController, mPackageManager, mUiEventLogger, mBroadcastSender,
+ mBgExecutor, mMainExecutor, mMainHandler);
}
};
@@ -133,6 +125,8 @@ public final class AppClipsTrampolineActivityTest extends SysuiTestCase {
mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH));
MockitoAnnotations.initMocks(this);
+ mBgExecutor = MoreExecutors.directExecutor();
+ mMainExecutor = MoreExecutors.directExecutor();
mMainHandler = mContext.getMainThreadHandler();
mActivityIntent = new Intent(mContext, AppClipsTrampolineActivityTestable.class);
@@ -169,19 +163,9 @@ public final class AppClipsTrampolineActivityTest extends SysuiTestCase {
}
@Test
- public void flagOff_shouldFinishWithResultCancel() {
- when(mFeatureFlags.isEnabled(SCREENSHOT_APP_CLIPS)).thenReturn(false);
-
- mActivityRule.launchActivity(mActivityIntent);
-
- assertThat(mActivityRule.getActivityResult().getResultCode())
- .isEqualTo(Activity.RESULT_CANCELED);
- }
-
- @Test
- public void bubblesEmpty_shouldFinishWithFailed() {
- when(mFeatureFlags.isEnabled(SCREENSHOT_APP_CLIPS)).thenReturn(true);
- when(mOptionalBubbles.isEmpty()).thenReturn(true);
+ public void queryService_returnedFailed_shouldFinishWithFailed() {
+ when(mServiceConnector.postForResult(any()))
+ .thenReturn(completedFuture(CAPTURE_CONTENT_FOR_NOTE_FAILED));
mActivityRule.launchActivity(mActivityIntent);
@@ -189,14 +173,13 @@ public final class AppClipsTrampolineActivityTest extends SysuiTestCase {
assertThat(actualResult.getResultCode()).isEqualTo(Activity.RESULT_OK);
assertThat(getStatusCodeExtra(actualResult.getResultData()))
.isEqualTo(CAPTURE_CONTENT_FOR_NOTE_FAILED);
+ assertThat(mActivityRule.getActivity().isFinishing()).isTrue();
}
@Test
- public void taskIdNotAppBubble_shouldFinishWithWindowModeUnsupported() {
- when(mFeatureFlags.isEnabled(SCREENSHOT_APP_CLIPS)).thenReturn(true);
- when(mOptionalBubbles.isEmpty()).thenReturn(false);
- when(mOptionalBubbles.get()).thenReturn(mBubbles);
- when(mBubbles.isAppBubbleTaskId(anyInt())).thenReturn(false);
+ public void queryService_returnedWindowModeUnsupported_shouldFinishWithWindowModeUnsupported() {
+ when(mServiceConnector.postForResult(any()))
+ .thenReturn(completedFuture(CAPTURE_CONTENT_FOR_NOTE_WINDOW_MODE_UNSUPPORTED));
mActivityRule.launchActivity(mActivityIntent);
@@ -204,15 +187,13 @@ public final class AppClipsTrampolineActivityTest extends SysuiTestCase {
assertThat(actualResult.getResultCode()).isEqualTo(Activity.RESULT_OK);
assertThat(getStatusCodeExtra(actualResult.getResultData()))
.isEqualTo(CAPTURE_CONTENT_FOR_NOTE_WINDOW_MODE_UNSUPPORTED);
+ assertThat(mActivityRule.getActivity().isFinishing()).isTrue();
}
@Test
- public void dpmScreenshotBlocked_shouldFinishWithBlockedByAdmin() {
- when(mFeatureFlags.isEnabled(SCREENSHOT_APP_CLIPS)).thenReturn(true);
- when(mOptionalBubbles.isEmpty()).thenReturn(false);
- when(mOptionalBubbles.get()).thenReturn(mBubbles);
- when(mBubbles.isAppBubbleTaskId(anyInt())).thenReturn(true);
- when(mDevicePolicyManager.getScreenCaptureDisabled(eq(null))).thenReturn(true);
+ public void queryService_returnedScreenshotBlocked_shouldFinishWithBlockedByAdmin() {
+ when(mServiceConnector.postForResult(any()))
+ .thenReturn(completedFuture(CAPTURE_CONTENT_FOR_NOTE_BLOCKED_BY_ADMIN));
mActivityRule.launchActivity(mActivityIntent);
@@ -220,6 +201,7 @@ public final class AppClipsTrampolineActivityTest extends SysuiTestCase {
assertThat(actualResult.getResultCode()).isEqualTo(Activity.RESULT_OK);
assertThat(getStatusCodeExtra(actualResult.getResultData()))
.isEqualTo(CAPTURE_CONTENT_FOR_NOTE_BLOCKED_BY_ADMIN);
+ assertThat(mActivityRule.getActivity().isFinishing()).isTrue();
}
@Test
@@ -240,6 +222,7 @@ public final class AppClipsTrampolineActivityTest extends SysuiTestCase {
assertThat(actualResult.getResultCode()).isEqualTo(Activity.RESULT_OK);
assertThat(getStatusCodeExtra(actualResult.getResultData()))
.isEqualTo(CAPTURE_CONTENT_FOR_NOTE_USER_CANCELED);
+ assertThat(mActivityRule.getActivity().isFinishing()).isTrue();
}
@Test
@@ -261,6 +244,7 @@ public final class AppClipsTrampolineActivityTest extends SysuiTestCase {
assertThat(getStatusCodeExtra(actualResult.getResultData()))
.isEqualTo(CAPTURE_CONTENT_FOR_NOTE_SUCCESS);
assertThat(actualResult.getResultData().getData()).isEqualTo(TEST_URI);
+ assertThat(mActivityRule.getActivity().isFinishing()).isTrue();
}
@Test
@@ -274,48 +258,9 @@ public final class AppClipsTrampolineActivityTest extends SysuiTestCase {
verify(mUiEventLogger).log(SCREENSHOT_FOR_NOTE_TRIGGERED, TEST_UID, TEST_CALLING_PACKAGE);
}
- @Test
- public void startAppClipsActivity_throughWPUser_shouldStartMainUserActivity()
- throws NameNotFoundException {
- when(mUserManager.isManagedProfile()).thenReturn(true);
- when(mUserManager.getMainUser()).thenReturn(UserHandle.SYSTEM);
- mockToSatisfyAllPrerequisites();
-
- AppClipsTrampolineActivityTestable activity = mActivityRule.launchActivity(mActivityIntent);
- waitForIdleSync();
-
- Intent actualIntent = activity.mStartedIntent;
- assertThat(actualIntent.getComponent()).isEqualTo(
- new ComponentName(mContext, AppClipsTrampolineActivity.class));
- assertThat(actualIntent.getFlags()).isEqualTo(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
- assertThat(actualIntent.getBooleanExtra(EXTRA_USE_WP_USER, false)).isTrue();
- assertThat(activity.mStartingUser).isEqualTo(UserHandle.SYSTEM);
- }
-
- @Test
- public void startAppClipsActivity_throughWPUser_noMainUser_shouldFinishWithFailed()
- throws NameNotFoundException {
- when(mUserManager.isManagedProfile()).thenReturn(true);
- when(mUserManager.getMainUser()).thenReturn(null);
-
- mockToSatisfyAllPrerequisites();
-
- mActivityRule.launchActivity(mActivityIntent);
- waitForIdleSync();
-
- ActivityResult actualResult = mActivityRule.getActivityResult();
- assertThat(actualResult.getResultCode()).isEqualTo(Activity.RESULT_OK);
- assertThat(getStatusCodeExtra(actualResult.getResultData()))
- .isEqualTo(CAPTURE_CONTENT_FOR_NOTE_FAILED);
- }
-
private void mockToSatisfyAllPrerequisites() throws NameNotFoundException {
- when(mFeatureFlags.isEnabled(SCREENSHOT_APP_CLIPS)).thenReturn(true);
- when(mOptionalBubbles.isEmpty()).thenReturn(false);
- when(mOptionalBubbles.get()).thenReturn(mBubbles);
- when(mBubbles.isAppBubbleTaskId(anyInt())).thenReturn(true);
- when(mDevicePolicyManager.getScreenCaptureDisabled(eq(null))).thenReturn(false);
- when(mUserTracker.getUserProfiles()).thenReturn(List.of());
+ when(mServiceConnector.postForResult(any()))
+ .thenReturn(completedFuture(CAPTURE_CONTENT_FOR_NOTE_SUCCESS));
ApplicationInfo testApplicationInfo = new ApplicationInfo();
testApplicationInfo.uid = TEST_UID;
@@ -330,17 +275,14 @@ public final class AppClipsTrampolineActivityTest extends SysuiTestCase {
Intent mStartedIntent;
UserHandle mStartingUser;
- public AppClipsTrampolineActivityTestable(DevicePolicyManager devicePolicyManager,
- FeatureFlags flags,
- Optional optionalBubbles,
- NoteTaskController noteTaskController,
- PackageManager packageManager,
- UserTracker userTracker,
- UiEventLogger uiEventLogger,
- UserManager userManager,
+ public AppClipsTrampolineActivityTestable(
+ ServiceConnector serviceServiceConnector,
+ NoteTaskController noteTaskController, PackageManager packageManager,
+ UiEventLogger uiEventLogger, BroadcastSender broadcastSender,
+ @Background Executor bgExecutor, @Main Executor mainExecutor,
@Main Handler mainHandler) {
- super(devicePolicyManager, flags, optionalBubbles, noteTaskController, packageManager,
- userTracker, uiEventLogger, userManager, mainHandler);
+ super(serviceServiceConnector, noteTaskController, packageManager, uiEventLogger,
+ broadcastSender, bgExecutor, mainExecutor, mainHandler);
}
@Override