Merge "Fix share/edit actions for work profile." into tm-qpr-dev
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 2737ecf..b5145f9 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -402,6 +402,9 @@
android:permission="com.android.systemui.permission.SELF"
android:exported="false" />
+ <service android:name=".screenshot.ScreenshotCrossProfileService"
+ android:permission="com.android.systemui.permission.SELF"
+ android:exported="false" />
<service android:name=".screenrecord.RecordingService" />
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentCreator.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentCreator.kt
new file mode 100644
index 0000000..017e57f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentCreator.kt
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenshot
+
+import android.content.ClipData
+import android.content.ClipDescription
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.net.Uri
+import com.android.systemui.R
+
+object ActionIntentCreator {
+ /** @return a chooser intent to share the given URI with the optional provided subject. */
+ fun createShareIntent(uri: Uri, subject: String?): Intent {
+ // Create a share intent, this will always go through the chooser activity first
+ // which should not trigger auto-enter PiP
+ val sharingIntent =
+ Intent(Intent.ACTION_SEND).apply {
+ setDataAndType(uri, "image/png")
+ putExtra(Intent.EXTRA_STREAM, uri)
+
+ // Include URI in ClipData also, so that grantPermission picks it up.
+ // We don't use setData here because some apps interpret this as "to:".
+ clipData =
+ ClipData(
+ ClipDescription("content", arrayOf(ClipDescription.MIMETYPE_TEXT_PLAIN)),
+ ClipData.Item(uri)
+ )
+
+ putExtra(Intent.EXTRA_SUBJECT, subject)
+ addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
+ addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
+ }
+
+ return Intent.createChooser(sharingIntent, null)
+ .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
+ }
+
+ /**
+ * @return an ACTION_EDIT intent for the given URI, directed to config_screenshotEditor if
+ * available.
+ */
+ fun createEditIntent(uri: Uri, context: Context): Intent {
+ val editIntent = Intent(Intent.ACTION_EDIT)
+
+ context.getString(R.string.config_screenshotEditor)?.let {
+ if (it.isNotEmpty()) {
+ editIntent.component = ComponentName.unflattenFromString(it)
+ }
+ }
+
+ return editIntent
+ .setDataAndType(uri, "image/png")
+ .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
+ .addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt
new file mode 100644
index 0000000..5961635
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenshot
+
+import android.content.Context
+import android.content.Intent
+import android.os.Bundle
+import android.os.RemoteException
+import android.os.UserHandle
+import android.util.Log
+import android.view.Display
+import android.view.IRemoteAnimationFinishedCallback
+import android.view.IRemoteAnimationRunner
+import android.view.RemoteAnimationAdapter
+import android.view.RemoteAnimationTarget
+import android.view.WindowManager
+import android.view.WindowManagerGlobal
+import com.android.internal.infra.ServiceConnector
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import javax.inject.Inject
+import kotlinx.coroutines.CompletableDeferred
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+
+@SysUISingleton
+class ActionIntentExecutor
+@Inject
+constructor(
+ @Application private val applicationScope: CoroutineScope,
+ @Background private val bgDispatcher: CoroutineDispatcher,
+ private val context: Context,
+) {
+ /**
+ * Execute the given intent with startActivity while performing operations for screenshot action
+ * launching.
+ * - Dismiss the keyguard first
+ * - If the userId is not the current user, proxy to a service running as that user to execute
+ * - After startActivity, optionally override the pending app transition.
+ */
+ fun launchIntentAsync(
+ intent: Intent,
+ bundle: Bundle,
+ userId: Int,
+ overrideTransition: Boolean,
+ ) {
+ applicationScope.launch { launchIntent(intent, bundle, userId, overrideTransition) }
+ }
+
+ suspend fun launchIntent(
+ intent: Intent,
+ bundle: Bundle,
+ userId: Int,
+ overrideTransition: Boolean,
+ ) {
+ withContext(bgDispatcher) {
+ dismissKeyguard()
+
+ if (userId == UserHandle.myUserId()) {
+ context.startActivity(intent, bundle)
+ } else {
+ launchCrossProfileIntent(userId, intent, bundle)
+ }
+
+ if (overrideTransition) {
+ val runner = RemoteAnimationAdapter(SCREENSHOT_REMOTE_RUNNER, 0, 0)
+ try {
+ WindowManagerGlobal.getWindowManagerService()
+ .overridePendingAppTransitionRemote(runner, Display.DEFAULT_DISPLAY)
+ } catch (e: Exception) {
+ Log.e(TAG, "Error overriding screenshot app transition", e)
+ }
+ }
+ }
+ }
+
+ private val proxyConnector: ServiceConnector<IScreenshotProxy> =
+ ServiceConnector.Impl(
+ context,
+ Intent(context, ScreenshotProxyService::class.java),
+ Context.BIND_AUTO_CREATE or Context.BIND_WAIVE_PRIORITY or Context.BIND_NOT_VISIBLE,
+ context.userId,
+ IScreenshotProxy.Stub::asInterface,
+ )
+
+ private suspend fun dismissKeyguard() {
+ val completion = CompletableDeferred<Unit>()
+ val onDoneBinder =
+ object : IOnDoneCallback.Stub() {
+ override fun onDone(success: Boolean) {
+ completion.complete(Unit)
+ }
+ }
+ proxyConnector.post { it.dismissKeyguard(onDoneBinder) }
+ completion.await()
+ }
+
+ private fun getCrossProfileConnector(userId: Int): ServiceConnector<ICrossProfileService> =
+ ServiceConnector.Impl<ICrossProfileService>(
+ context,
+ Intent(context, ScreenshotCrossProfileService::class.java),
+ Context.BIND_AUTO_CREATE or Context.BIND_WAIVE_PRIORITY or Context.BIND_NOT_VISIBLE,
+ userId,
+ ICrossProfileService.Stub::asInterface,
+ )
+
+ private suspend fun launchCrossProfileIntent(userId: Int, intent: Intent, bundle: Bundle) {
+ val connector = getCrossProfileConnector(userId)
+ val completion = CompletableDeferred<Unit>()
+ connector.post {
+ it.launchIntent(intent, bundle)
+ completion.complete(Unit)
+ }
+ completion.await()
+ }
+}
+
+private const val TAG: String = "ActionIntentExecutor"
+private const val SCREENSHOT_SHARE_SUBJECT_TEMPLATE = "Screenshot (%s)"
+
+/**
+ * This is effectively a no-op, but we need something non-null to pass in, in order to successfully
+ * override the pending activity entrance animation.
+ */
+private val SCREENSHOT_REMOTE_RUNNER: IRemoteAnimationRunner.Stub =
+ object : IRemoteAnimationRunner.Stub() {
+ override fun onAnimationStart(
+ @WindowManager.TransitionOldType transit: Int,
+ apps: Array<RemoteAnimationTarget>,
+ wallpapers: Array<RemoteAnimationTarget>,
+ nonApps: Array<RemoteAnimationTarget>,
+ finishedCallback: IRemoteAnimationFinishedCallback,
+ ) {
+ try {
+ finishedCallback.onAnimationFinished()
+ } catch (e: RemoteException) {
+ Log.e(TAG, "Error finishing screenshot remote animation", e)
+ }
+ }
+
+ override fun onAnimationCancelled(isKeyguardOccluded: Boolean) {}
+ }
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ICrossProfileService.aidl b/packages/SystemUI/src/com/android/systemui/screenshot/ICrossProfileService.aidl
new file mode 100644
index 0000000..da83472
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ICrossProfileService.aidl
@@ -0,0 +1,27 @@
+/**
+ * Copyright (c) 2009, 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.screenshot;
+
+import android.app.PendingIntent;
+import android.content.Intent;
+import android.os.Bundle;
+
+/** Interface implemented by ScreenshotCrossProfileService */
+interface ICrossProfileService {
+
+ void launchIntent(in Intent intent, in Bundle bundle);
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/IOnDoneCallback.aidl b/packages/SystemUI/src/com/android/systemui/screenshot/IOnDoneCallback.aidl
new file mode 100644
index 0000000..e15030f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/IOnDoneCallback.aidl
@@ -0,0 +1,21 @@
+/**
+ * Copyright (c) 2022, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenshot;
+
+interface IOnDoneCallback {
+ void onDone(boolean success);
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/IScreenshotProxy.aidl b/packages/SystemUI/src/com/android/systemui/screenshot/IScreenshotProxy.aidl
index f7c4dad..d2e3fbd 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/IScreenshotProxy.aidl
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/IScreenshotProxy.aidl
@@ -16,9 +16,14 @@
package com.android.systemui.screenshot;
+import com.android.systemui.screenshot.IOnDoneCallback;
+
/** Interface implemented by ScreenshotProxyService */
interface IScreenshotProxy {
/** Is the notification shade currently exanded? */
boolean isNotificationShadeExpanded();
-}
\ No newline at end of file
+
+ /** Attempts to dismiss the keyguard. */
+ void dismissKeyguard(IOnDoneCallback callback);
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
index 077ad35..7143ba2 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
@@ -173,6 +173,7 @@
mImageData.deleteAction = createDeleteAction(mContext, mContext.getResources(), uri);
mImageData.quickShareAction = createQuickShareAction(mContext,
mQuickShareData.quickShareAction, uri);
+ mImageData.subject = getSubjectString();
mParams.mActionsReadyListener.onActionsReady(mImageData);
if (DEBUG_CALLBACK) {
@@ -237,8 +238,6 @@
// Create a share intent, this will always go through the chooser activity first
// which should not trigger auto-enter PiP
- String subjectDate = DateFormat.getDateTimeInstance().format(new Date(mImageTime));
- String subject = String.format(SCREENSHOT_SHARE_SUBJECT_TEMPLATE, subjectDate);
Intent sharingIntent = new Intent(Intent.ACTION_SEND);
sharingIntent.setDataAndType(uri, "image/png");
sharingIntent.putExtra(Intent.EXTRA_STREAM, uri);
@@ -248,7 +247,7 @@
new String[]{ClipDescription.MIMETYPE_TEXT_PLAIN}),
new ClipData.Item(uri));
sharingIntent.setClipData(clipdata);
- sharingIntent.putExtra(Intent.EXTRA_SUBJECT, subject);
+ sharingIntent.putExtra(Intent.EXTRA_SUBJECT, getSubjectString());
sharingIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
@@ -318,7 +317,7 @@
// by setting the (otherwise unused) request code to the current user id.
int requestCode = mContext.getUserId();
- // Create a edit action
+ // Create an edit action
PendingIntent editAction = PendingIntent.getBroadcastAsUser(context, requestCode,
new Intent(context, ActionProxyReceiver.class)
.putExtra(ScreenshotController.EXTRA_ACTION_INTENT, pendingIntent)
@@ -479,4 +478,9 @@
mParams.mQuickShareActionsReadyListener.onActionsReady(mQuickShareData);
}
}
+
+ private String getSubjectString() {
+ String subjectDate = DateFormat.getDateTimeInstance().format(new Date(mImageTime));
+ return String.format(SCREENSHOT_SHARE_SUBJECT_TEMPLATE, subjectDate);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index 6d5121a..231e415 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -174,7 +174,7 @@
public List<Notification.Action> smartActions;
public Notification.Action quickShareAction;
public UserHandle owner;
-
+ public String subject; // Title for sharing
/**
* POD for shared element transition.
@@ -195,6 +195,7 @@
deleteAction = null;
smartActions = null;
quickShareAction = null;
+ subject = null;
}
}
@@ -273,6 +274,7 @@
private final ScreenshotNotificationSmartActionsProvider
mScreenshotNotificationSmartActionsProvider;
private final TimeoutHandler mScreenshotHandler;
+ private final ActionIntentExecutor mActionExecutor;
private ScreenshotView mScreenshotView;
private Bitmap mScreenBitmap;
@@ -310,7 +312,8 @@
ActivityManager activityManager,
TimeoutHandler timeoutHandler,
BroadcastSender broadcastSender,
- ScreenshotNotificationSmartActionsProvider screenshotNotificationSmartActionsProvider
+ ScreenshotNotificationSmartActionsProvider screenshotNotificationSmartActionsProvider,
+ ActionIntentExecutor actionExecutor
) {
mScreenshotSmartActions = screenshotSmartActions;
mNotificationsController = screenshotNotificationsController;
@@ -340,6 +343,7 @@
mContext = (WindowContext) displayContext.createWindowContext(TYPE_SCREENSHOT, null);
mWindowManager = mContext.getSystemService(WindowManager.class);
mFlags = flags;
+ mActionExecutor = actionExecutor;
mAccessibilityManager = AccessibilityManager.getInstance(mContext);
@@ -486,7 +490,7 @@
// TODO(159460485): Remove this when focus is handled properly in the system
setWindowFocusable(false);
}
- });
+ }, mActionExecutor, mFlags);
mScreenshotView.setDefaultTimeoutMillis(mScreenshotHandler.getDefaultTimeoutMillis());
mScreenshotView.setOnKeyListener((v, keyCode, event) -> {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotCrossProfileService.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotCrossProfileService.kt
new file mode 100644
index 0000000..2e6c756
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotCrossProfileService.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenshot
+
+import android.app.Service
+import android.content.Intent
+import android.os.Bundle
+import android.os.IBinder
+import android.util.Log
+
+/**
+ * If a screenshot is saved to the work profile, any intents that grant access to the screenshot
+ * must come from a service running as the work profile user. This service is meant to be started as
+ * the desired user and just startActivity for the given intent.
+ */
+class ScreenshotCrossProfileService : Service() {
+
+ private val mBinder: IBinder =
+ object : ICrossProfileService.Stub() {
+ override fun launchIntent(intent: Intent, bundle: Bundle) {
+ startActivity(intent, bundle)
+ }
+ }
+
+ override fun onBind(intent: Intent): IBinder? {
+ Log.d(TAG, "onBind: $intent")
+ return mBinder
+ }
+
+ companion object {
+ const val TAG = "ScreenshotProxyService"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotProxyService.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotProxyService.kt
index 793085a..c41e2bc 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotProxyService.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotProxyService.kt
@@ -20,13 +20,16 @@
import android.os.IBinder
import android.util.Log
import com.android.systemui.shade.ShadeExpansionStateManager
+import com.android.systemui.statusbar.phone.CentralSurfaces
+import java.util.Optional
import javax.inject.Inject
/**
* Provides state from the main SystemUI process on behalf of the Screenshot process.
*/
internal class ScreenshotProxyService @Inject constructor(
- private val mExpansionMgr: ShadeExpansionStateManager
+ private val mExpansionMgr: ShadeExpansionStateManager,
+ private val mCentralSurfacesOptional: Optional<CentralSurfaces>,
) : Service() {
private val mBinder: IBinder = object : IScreenshotProxy.Stub() {
@@ -38,6 +41,20 @@
Log.d(TAG, "isNotificationShadeExpanded(): $expanded")
return expanded
}
+
+ override fun dismissKeyguard(callback: IOnDoneCallback) {
+ if (mCentralSurfacesOptional.isPresent) {
+ mCentralSurfacesOptional.get().executeRunnableDismissingKeyguard(
+ Runnable {
+ callback.onDone(true)
+ }, null,
+ true /* dismissShade */, true /* afterKeyguardGone */,
+ true /* deferred */
+ )
+ } else {
+ callback.onDone(false)
+ }
+ }
}
override fun onBind(intent: Intent): IBinder? {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
index be41a6b..26cbcbf 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
@@ -87,6 +87,8 @@
import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.logging.UiEventLogger;
import com.android.systemui.R;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
import com.android.systemui.screenshot.ScreenshotController.SavedImageData.ActionTransition;
import com.android.systemui.shared.system.InputChannelCompat;
import com.android.systemui.shared.system.InputMonitorCompat;
@@ -168,6 +170,8 @@
private final InteractionJankMonitor mInteractionJankMonitor;
private long mDefaultTimeoutOfTimeoutHandler;
+ private ActionIntentExecutor mActionExecutor;
+ private FeatureFlags mFlags;
private enum PendingInteraction {
PREVIEW,
@@ -422,9 +426,12 @@
* Note: must be called before any other (non-constructor) method or null pointer exceptions
* may occur.
*/
- void init(UiEventLogger uiEventLogger, ScreenshotViewCallback callbacks) {
+ void init(UiEventLogger uiEventLogger, ScreenshotViewCallback callbacks,
+ ActionIntentExecutor actionExecutor, FeatureFlags flags) {
mUiEventLogger = uiEventLogger;
mCallbacks = callbacks;
+ mActionExecutor = actionExecutor;
+ mFlags = flags;
}
void setScreenshot(Bitmap bitmap, Insets screenInsets) {
@@ -759,18 +766,37 @@
void setChipIntents(ScreenshotController.SavedImageData imageData) {
mShareChip.setOnClickListener(v -> {
mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SHARE_TAPPED, 0, mPackageName);
- startSharedTransition(
- imageData.shareTransition.get());
+ if (mFlags.isEnabled(Flags.SCREENSHOT_WORK_PROFILE_POLICY)) {
+ mActionExecutor.launchIntentAsync(ActionIntentCreator.INSTANCE.createShareIntent(
+ imageData.uri, imageData.subject),
+ imageData.shareTransition.get().bundle,
+ imageData.owner.getIdentifier(), false);
+ } else {
+ startSharedTransition(imageData.shareTransition.get());
+ }
});
mEditChip.setOnClickListener(v -> {
mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_EDIT_TAPPED, 0, mPackageName);
- startSharedTransition(
- imageData.editTransition.get());
+ if (mFlags.isEnabled(Flags.SCREENSHOT_WORK_PROFILE_POLICY)) {
+ mActionExecutor.launchIntentAsync(
+ ActionIntentCreator.INSTANCE.createEditIntent(imageData.uri, mContext),
+ imageData.editTransition.get().bundle,
+ imageData.owner.getIdentifier(), true);
+ } else {
+ startSharedTransition(imageData.editTransition.get());
+ }
});
mScreenshotPreview.setOnClickListener(v -> {
mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_PREVIEW_TAPPED, 0, mPackageName);
- startSharedTransition(
- imageData.editTransition.get());
+ if (mFlags.isEnabled(Flags.SCREENSHOT_WORK_PROFILE_POLICY)) {
+ mActionExecutor.launchIntentAsync(
+ ActionIntentCreator.INSTANCE.createEditIntent(imageData.uri, mContext),
+ imageData.editTransition.get().bundle,
+ imageData.owner.getIdentifier(), true);
+ } else {
+ startSharedTransition(
+ imageData.editTransition.get());
+ }
});
if (mQuickShareChip != null) {
mQuickShareChip.setPendingIntent(imageData.quickShareAction.actionIntent,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionIntentCreatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionIntentCreatorTest.kt
new file mode 100644
index 0000000..b6a595b
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionIntentCreatorTest.kt
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenshot
+
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.net.Uri
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.mockito.Mockito.`when` as whenever
+
+@SmallTest
+class ActionIntentCreatorTest : SysuiTestCase() {
+
+ @Test
+ fun testCreateShareIntent() {
+ val uri = Uri.parse("content://fake")
+ val subject = "Example subject"
+
+ val output = ActionIntentCreator.createShareIntent(uri, subject)
+
+ assertThat(output.action).isEqualTo(Intent.ACTION_CHOOSER)
+ assertFlagsSet(
+ Intent.FLAG_ACTIVITY_NEW_TASK or
+ Intent.FLAG_ACTIVITY_CLEAR_TASK or
+ Intent.FLAG_GRANT_READ_URI_PERMISSION,
+ output.flags
+ )
+
+ val wrappedIntent = output.getParcelableExtra(Intent.EXTRA_INTENT, Intent::class.java)
+ assertThat(wrappedIntent?.action).isEqualTo(Intent.ACTION_SEND)
+ assertThat(wrappedIntent?.data).isEqualTo(uri)
+ assertThat(wrappedIntent?.type).isEqualTo("image/png")
+ assertThat(wrappedIntent?.getStringExtra(Intent.EXTRA_SUBJECT)).isEqualTo(subject)
+ assertThat(wrappedIntent?.getParcelableExtra(Intent.EXTRA_STREAM, Uri::class.java))
+ .isEqualTo(uri)
+ }
+
+ @Test
+ fun testCreateShareIntent_noSubject() {
+ val uri = Uri.parse("content://fake")
+ val output = ActionIntentCreator.createShareIntent(uri, null)
+ val wrappedIntent = output.getParcelableExtra(Intent.EXTRA_INTENT, Intent::class.java)
+ assertThat(wrappedIntent?.getStringExtra(Intent.EXTRA_SUBJECT)).isNull()
+ }
+
+ @Test
+ fun testCreateEditIntent() {
+ val uri = Uri.parse("content://fake")
+ val context = mock<Context>()
+
+ val output = ActionIntentCreator.createEditIntent(uri, context)
+
+ assertThat(output.action).isEqualTo(Intent.ACTION_EDIT)
+ assertThat(output.data).isEqualTo(uri)
+ assertThat(output.type).isEqualTo("image/png")
+ assertThat(output.component).isNull()
+ val expectedFlags =
+ Intent.FLAG_GRANT_READ_URI_PERMISSION or
+ Intent.FLAG_GRANT_WRITE_URI_PERMISSION or
+ Intent.FLAG_ACTIVITY_NEW_TASK or
+ Intent.FLAG_ACTIVITY_CLEAR_TASK
+ assertFlagsSet(expectedFlags, output.flags)
+ }
+
+ @Test
+ fun testCreateEditIntent_withEditor() {
+ val uri = Uri.parse("content://fake")
+ val context = mock<Context>()
+ var component = ComponentName("com.android.foo", "com.android.foo.Something")
+
+ whenever(context.getString(eq(R.string.config_screenshotEditor)))
+ .thenReturn(component.flattenToString())
+
+ val output = ActionIntentCreator.createEditIntent(uri, context)
+
+ assertThat(output.component).isEqualTo(component)
+ }
+
+ private fun assertFlagsSet(expected: Int, observed: Int) {
+ assertThat(observed and expected).isEqualTo(expected)
+ }
+}