Merge "Fix share/edit actions for work profile." into tm-qpr-dev am: 693d0a4131 am: d0117ee2cb
Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/20141373 Change-Id: If0fa332ef27b7079dca1191c3811a92e020cc93e Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
@@ -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" />
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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) {}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
/** Attempts to dismiss the keyguard. */
|
||||
void dismissKeyguard(IOnDoneCallback callback);
|
||||
}
|
||||
|
||||
@@ -173,6 +173,7 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
|
||||
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 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
|
||||
|
||||
// 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 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
|
||||
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 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
|
||||
// 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 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
|
||||
mParams.mQuickShareActionsReadyListener.onActionsReady(mQuickShareData);
|
||||
}
|
||||
}
|
||||
|
||||
private String getSubjectString() {
|
||||
String subjectDate = DateFormat.getDateTimeInstance().format(new Date(mImageTime));
|
||||
return String.format(SCREENSHOT_SHARE_SUBJECT_TEMPLATE, subjectDate);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -174,7 +174,7 @@ public class ScreenshotController {
|
||||
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 @@ public class ScreenshotController {
|
||||
deleteAction = null;
|
||||
smartActions = null;
|
||||
quickShareAction = null;
|
||||
subject = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -273,6 +274,7 @@ public class ScreenshotController {
|
||||
private final ScreenshotNotificationSmartActionsProvider
|
||||
mScreenshotNotificationSmartActionsProvider;
|
||||
private final TimeoutHandler mScreenshotHandler;
|
||||
private final ActionIntentExecutor mActionExecutor;
|
||||
|
||||
private ScreenshotView mScreenshotView;
|
||||
private Bitmap mScreenBitmap;
|
||||
@@ -310,7 +312,8 @@ public class ScreenshotController {
|
||||
ActivityManager activityManager,
|
||||
TimeoutHandler timeoutHandler,
|
||||
BroadcastSender broadcastSender,
|
||||
ScreenshotNotificationSmartActionsProvider screenshotNotificationSmartActionsProvider
|
||||
ScreenshotNotificationSmartActionsProvider screenshotNotificationSmartActionsProvider,
|
||||
ActionIntentExecutor actionExecutor
|
||||
) {
|
||||
mScreenshotSmartActions = screenshotSmartActions;
|
||||
mNotificationsController = screenshotNotificationsController;
|
||||
@@ -340,6 +343,7 @@ public class ScreenshotController {
|
||||
mContext = (WindowContext) displayContext.createWindowContext(TYPE_SCREENSHOT, null);
|
||||
mWindowManager = mContext.getSystemService(WindowManager.class);
|
||||
mFlags = flags;
|
||||
mActionExecutor = actionExecutor;
|
||||
|
||||
mAccessibilityManager = AccessibilityManager.getInstance(mContext);
|
||||
|
||||
@@ -486,7 +490,7 @@ public class ScreenshotController {
|
||||
// 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) -> {
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -20,13 +20,16 @@ import android.content.Intent
|
||||
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 @@ internal class ScreenshotProxyService @Inject constructor(
|
||||
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? {
|
||||
|
||||
@@ -87,6 +87,8 @@ import androidx.constraintlayout.widget.ConstraintLayout;
|
||||
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 @@ public class ScreenshotView extends FrameLayout implements
|
||||
|
||||
private final InteractionJankMonitor mInteractionJankMonitor;
|
||||
private long mDefaultTimeoutOfTimeoutHandler;
|
||||
private ActionIntentExecutor mActionExecutor;
|
||||
private FeatureFlags mFlags;
|
||||
|
||||
private enum PendingInteraction {
|
||||
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
|
||||
* 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 @@ public class ScreenshotView extends FrameLayout implements
|
||||
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,
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user