Merge "Fix share/edit actions for work profile." into tm-qpr-dev

This commit is contained in:
Matt Casey
2022-10-17 13:18:51 +00:00
committed by Android (Google) Code Review
12 changed files with 508 additions and 16 deletions

View File

@@ -402,6 +402,9 @@
android:permission="com.android.systemui.permission.SELF" android:permission="com.android.systemui.permission.SELF"
android:exported="false" /> android:exported="false" />
<service android:name=".screenshot.ScreenshotCrossProfileService"
android:permission="com.android.systemui.permission.SELF"
android:exported="false" />
<service android:name=".screenrecord.RecordingService" /> <service android:name=".screenrecord.RecordingService" />

View File

@@ -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)
}
}

View File

@@ -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) {}
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -16,9 +16,14 @@
package com.android.systemui.screenshot; package com.android.systemui.screenshot;
import com.android.systemui.screenshot.IOnDoneCallback;
/** Interface implemented by ScreenshotProxyService */ /** Interface implemented by ScreenshotProxyService */
interface IScreenshotProxy { interface IScreenshotProxy {
/** Is the notification shade currently exanded? */ /** Is the notification shade currently exanded? */
boolean isNotificationShadeExpanded(); boolean isNotificationShadeExpanded();
/** Attempts to dismiss the keyguard. */
void dismissKeyguard(IOnDoneCallback callback);
} }

View File

@@ -173,6 +173,7 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
mImageData.deleteAction = createDeleteAction(mContext, mContext.getResources(), uri); mImageData.deleteAction = createDeleteAction(mContext, mContext.getResources(), uri);
mImageData.quickShareAction = createQuickShareAction(mContext, mImageData.quickShareAction = createQuickShareAction(mContext,
mQuickShareData.quickShareAction, uri); mQuickShareData.quickShareAction, uri);
mImageData.subject = getSubjectString();
mParams.mActionsReadyListener.onActionsReady(mImageData); mParams.mActionsReadyListener.onActionsReady(mImageData);
if (DEBUG_CALLBACK) { if (DEBUG_CALLBACK) {
@@ -237,8 +238,6 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
// Create a share intent, this will always go through the chooser activity first // Create a share intent, this will always go through the chooser activity first
// which should not trigger auto-enter PiP // 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); Intent sharingIntent = new Intent(Intent.ACTION_SEND);
sharingIntent.setDataAndType(uri, "image/png"); sharingIntent.setDataAndType(uri, "image/png");
sharingIntent.putExtra(Intent.EXTRA_STREAM, uri); sharingIntent.putExtra(Intent.EXTRA_STREAM, uri);
@@ -248,7 +247,7 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
new String[]{ClipDescription.MIMETYPE_TEXT_PLAIN}), new String[]{ClipDescription.MIMETYPE_TEXT_PLAIN}),
new ClipData.Item(uri)); new ClipData.Item(uri));
sharingIntent.setClipData(clipdata); sharingIntent.setClipData(clipdata);
sharingIntent.putExtra(Intent.EXTRA_SUBJECT, subject); sharingIntent.putExtra(Intent.EXTRA_SUBJECT, getSubjectString());
sharingIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) sharingIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION); .addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
@@ -318,7 +317,7 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
// by setting the (otherwise unused) request code to the current user id. // by setting the (otherwise unused) request code to the current user id.
int requestCode = mContext.getUserId(); int requestCode = mContext.getUserId();
// Create a edit action // Create an edit action
PendingIntent editAction = PendingIntent.getBroadcastAsUser(context, requestCode, PendingIntent editAction = PendingIntent.getBroadcastAsUser(context, requestCode,
new Intent(context, ActionProxyReceiver.class) new Intent(context, ActionProxyReceiver.class)
.putExtra(ScreenshotController.EXTRA_ACTION_INTENT, pendingIntent) .putExtra(ScreenshotController.EXTRA_ACTION_INTENT, pendingIntent)
@@ -479,4 +478,9 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
mParams.mQuickShareActionsReadyListener.onActionsReady(mQuickShareData); mParams.mQuickShareActionsReadyListener.onActionsReady(mQuickShareData);
} }
} }
private String getSubjectString() {
String subjectDate = DateFormat.getDateTimeInstance().format(new Date(mImageTime));
return String.format(SCREENSHOT_SHARE_SUBJECT_TEMPLATE, subjectDate);
}
} }

View File

@@ -174,7 +174,7 @@ public class ScreenshotController {
public List<Notification.Action> smartActions; public List<Notification.Action> smartActions;
public Notification.Action quickShareAction; public Notification.Action quickShareAction;
public UserHandle owner; public UserHandle owner;
public String subject; // Title for sharing
/** /**
* POD for shared element transition. * POD for shared element transition.
@@ -195,6 +195,7 @@ public class ScreenshotController {
deleteAction = null; deleteAction = null;
smartActions = null; smartActions = null;
quickShareAction = null; quickShareAction = null;
subject = null;
} }
} }
@@ -273,6 +274,7 @@ public class ScreenshotController {
private final ScreenshotNotificationSmartActionsProvider private final ScreenshotNotificationSmartActionsProvider
mScreenshotNotificationSmartActionsProvider; mScreenshotNotificationSmartActionsProvider;
private final TimeoutHandler mScreenshotHandler; private final TimeoutHandler mScreenshotHandler;
private final ActionIntentExecutor mActionExecutor;
private ScreenshotView mScreenshotView; private ScreenshotView mScreenshotView;
private Bitmap mScreenBitmap; private Bitmap mScreenBitmap;
@@ -310,7 +312,8 @@ public class ScreenshotController {
ActivityManager activityManager, ActivityManager activityManager,
TimeoutHandler timeoutHandler, TimeoutHandler timeoutHandler,
BroadcastSender broadcastSender, BroadcastSender broadcastSender,
ScreenshotNotificationSmartActionsProvider screenshotNotificationSmartActionsProvider ScreenshotNotificationSmartActionsProvider screenshotNotificationSmartActionsProvider,
ActionIntentExecutor actionExecutor
) { ) {
mScreenshotSmartActions = screenshotSmartActions; mScreenshotSmartActions = screenshotSmartActions;
mNotificationsController = screenshotNotificationsController; mNotificationsController = screenshotNotificationsController;
@@ -340,6 +343,7 @@ public class ScreenshotController {
mContext = (WindowContext) displayContext.createWindowContext(TYPE_SCREENSHOT, null); mContext = (WindowContext) displayContext.createWindowContext(TYPE_SCREENSHOT, null);
mWindowManager = mContext.getSystemService(WindowManager.class); mWindowManager = mContext.getSystemService(WindowManager.class);
mFlags = flags; mFlags = flags;
mActionExecutor = actionExecutor;
mAccessibilityManager = AccessibilityManager.getInstance(mContext); mAccessibilityManager = AccessibilityManager.getInstance(mContext);
@@ -486,7 +490,7 @@ public class ScreenshotController {
// TODO(159460485): Remove this when focus is handled properly in the system // TODO(159460485): Remove this when focus is handled properly in the system
setWindowFocusable(false); setWindowFocusable(false);
} }
}); }, mActionExecutor, mFlags);
mScreenshotView.setDefaultTimeoutMillis(mScreenshotHandler.getDefaultTimeoutMillis()); mScreenshotView.setDefaultTimeoutMillis(mScreenshotHandler.getDefaultTimeoutMillis());
mScreenshotView.setOnKeyListener((v, keyCode, event) -> { mScreenshotView.setOnKeyListener((v, keyCode, event) -> {

View File

@@ -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"
}
}

View File

@@ -20,13 +20,16 @@ import android.content.Intent
import android.os.IBinder import android.os.IBinder
import android.util.Log import android.util.Log
import com.android.systemui.shade.ShadeExpansionStateManager import com.android.systemui.shade.ShadeExpansionStateManager
import com.android.systemui.statusbar.phone.CentralSurfaces
import java.util.Optional
import javax.inject.Inject import javax.inject.Inject
/** /**
* Provides state from the main SystemUI process on behalf of the Screenshot process. * Provides state from the main SystemUI process on behalf of the Screenshot process.
*/ */
internal class ScreenshotProxyService @Inject constructor( internal class ScreenshotProxyService @Inject constructor(
private val mExpansionMgr: ShadeExpansionStateManager private val mExpansionMgr: ShadeExpansionStateManager,
private val mCentralSurfacesOptional: Optional<CentralSurfaces>,
) : Service() { ) : Service() {
private val mBinder: IBinder = object : IScreenshotProxy.Stub() { private val mBinder: IBinder = object : IScreenshotProxy.Stub() {
@@ -38,6 +41,20 @@ internal class ScreenshotProxyService @Inject constructor(
Log.d(TAG, "isNotificationShadeExpanded(): $expanded") Log.d(TAG, "isNotificationShadeExpanded(): $expanded")
return 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? { override fun onBind(intent: Intent): IBinder? {

View File

@@ -87,6 +87,8 @@ import androidx.constraintlayout.widget.ConstraintLayout;
import com.android.internal.jank.InteractionJankMonitor; import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.logging.UiEventLogger; import com.android.internal.logging.UiEventLogger;
import com.android.systemui.R; 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.screenshot.ScreenshotController.SavedImageData.ActionTransition;
import com.android.systemui.shared.system.InputChannelCompat; import com.android.systemui.shared.system.InputChannelCompat;
import com.android.systemui.shared.system.InputMonitorCompat; import com.android.systemui.shared.system.InputMonitorCompat;
@@ -168,6 +170,8 @@ public class ScreenshotView extends FrameLayout implements
private final InteractionJankMonitor mInteractionJankMonitor; private final InteractionJankMonitor mInteractionJankMonitor;
private long mDefaultTimeoutOfTimeoutHandler; private long mDefaultTimeoutOfTimeoutHandler;
private ActionIntentExecutor mActionExecutor;
private FeatureFlags mFlags;
private enum PendingInteraction { private enum PendingInteraction {
PREVIEW, PREVIEW,
@@ -422,9 +426,12 @@ public class ScreenshotView extends FrameLayout implements
* Note: must be called before any other (non-constructor) method or null pointer exceptions * Note: must be called before any other (non-constructor) method or null pointer exceptions
* may occur. * may occur.
*/ */
void init(UiEventLogger uiEventLogger, ScreenshotViewCallback callbacks) { void init(UiEventLogger uiEventLogger, ScreenshotViewCallback callbacks,
ActionIntentExecutor actionExecutor, FeatureFlags flags) {
mUiEventLogger = uiEventLogger; mUiEventLogger = uiEventLogger;
mCallbacks = callbacks; mCallbacks = callbacks;
mActionExecutor = actionExecutor;
mFlags = flags;
} }
void setScreenshot(Bitmap bitmap, Insets screenInsets) { void setScreenshot(Bitmap bitmap, Insets screenInsets) {
@@ -759,18 +766,37 @@ public class ScreenshotView extends FrameLayout implements
void setChipIntents(ScreenshotController.SavedImageData imageData) { void setChipIntents(ScreenshotController.SavedImageData imageData) {
mShareChip.setOnClickListener(v -> { mShareChip.setOnClickListener(v -> {
mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SHARE_TAPPED, 0, mPackageName); mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SHARE_TAPPED, 0, mPackageName);
startSharedTransition( if (mFlags.isEnabled(Flags.SCREENSHOT_WORK_PROFILE_POLICY)) {
imageData.shareTransition.get()); 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 -> { mEditChip.setOnClickListener(v -> {
mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_EDIT_TAPPED, 0, mPackageName); mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_EDIT_TAPPED, 0, mPackageName);
startSharedTransition( if (mFlags.isEnabled(Flags.SCREENSHOT_WORK_PROFILE_POLICY)) {
imageData.editTransition.get()); mActionExecutor.launchIntentAsync(
ActionIntentCreator.INSTANCE.createEditIntent(imageData.uri, mContext),
imageData.editTransition.get().bundle,
imageData.owner.getIdentifier(), true);
} else {
startSharedTransition(imageData.editTransition.get());
}
}); });
mScreenshotPreview.setOnClickListener(v -> { mScreenshotPreview.setOnClickListener(v -> {
mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_PREVIEW_TAPPED, 0, mPackageName); mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_PREVIEW_TAPPED, 0, mPackageName);
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( startSharedTransition(
imageData.editTransition.get()); imageData.editTransition.get());
}
}); });
if (mQuickShareChip != null) { if (mQuickShareChip != null) {
mQuickShareChip.setPendingIntent(imageData.quickShareAction.actionIntent, mQuickShareChip.setPendingIntent(imageData.quickShareAction.actionIntent,

View File

@@ -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)
}
}