Loading packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionsProvider.kt +48 −88 Original line number Diff line number Diff line Loading @@ -23,21 +23,17 @@ import android.app.PendingIntent import android.app.assist.AssistContent import android.content.Context import android.content.Intent import android.os.Process import android.os.UserHandle import android.provider.DeviceConfig import android.util.Log import android.util.Pair import androidx.appcompat.content.res.AppCompatResources import com.android.app.tracing.coroutines.launch import com.android.internal.config.sysui.SystemUiDeviceConfigFlags import com.android.internal.logging.UiEventLogger import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.log.DebugLogger.debugLog import com.android.systemui.res.R import com.android.systemui.screenshot.ActionIntentCreator.createEdit import com.android.systemui.screenshot.ActionIntentCreator.createShareWithSubject import com.android.systemui.screenshot.ScreenshotController.SavedImageData import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_EDIT_TAPPED import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_PREVIEW_TAPPED import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_SHARE_TAPPED Loading @@ -47,8 +43,6 @@ import com.android.systemui.screenshot.ui.viewmodel.ScreenshotViewModel import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import java.text.DateFormat import java.util.Date import kotlinx.coroutines.CoroutineScope /** Loading @@ -56,7 +50,7 @@ import kotlinx.coroutines.CoroutineScope * implementation. */ interface ScreenshotActionsProvider { fun setCompletedScreenshot(result: SavedImageData) fun setCompletedScreenshot(result: ScreenshotSavedResult) fun isPendingSharedTransition(): Boolean fun onAssistContentAvailable(assistContent: AssistContent) {} Loading Loading @@ -85,8 +79,8 @@ constructor( @Assisted val windowTransition: () -> Pair<ActivityOptions, ExitTransitionCoordinator>, @Assisted val requestDismissal: () -> Unit, ) : ScreenshotActionsProvider { private var pendingAction: ((SavedImageData) -> Unit)? = null private var result: SavedImageData? = null private var pendingAction: ((ScreenshotSavedResult) -> Unit)? = null private var result: ScreenshotSavedResult? = null private var isPendingSharedTransition = false init { Loading @@ -94,7 +88,7 @@ constructor( debugLog(LogConfig.DEBUG_ACTIONS) { "Preview tapped" } uiEventLogger.log(SCREENSHOT_PREVIEW_TAPPED, 0, request.packageNameString) onDeferrableActionTapped { result -> startSharedTransition(createEdit(result.uri, context), true) startSharedTransition(createEdit(result.uri, context), result.user, true) } } viewModel.addAction( Loading @@ -106,7 +100,7 @@ constructor( debugLog(LogConfig.DEBUG_ACTIONS) { "Edit tapped" } uiEventLogger.log(SCREENSHOT_EDIT_TAPPED, 0, request.packageNameString) onDeferrableActionTapped { result -> startSharedTransition(createEdit(result.uri, context), true) startSharedTransition(createEdit(result.uri, context), result.user, true) } } ) Loading @@ -119,18 +113,21 @@ constructor( debugLog(LogConfig.DEBUG_ACTIONS) { "Share tapped" } uiEventLogger.log(SCREENSHOT_SHARE_TAPPED, 0, request.packageNameString) onDeferrableActionTapped { result -> startSharedTransition(createShareWithSubject(result.uri, result.subject), false) startSharedTransition( createShareWithSubject(result.uri, result.subject), result.user, false ) } } ) if (smartActionsEnabled(request.userHandle ?: Process.myUserHandle())) { smartActionsProvider.requestQuickShare(request, requestId) { quickShare -> if (!quickShare.actionIntent.isImmutable) { viewModel.addAction( ActionButtonViewModel( quickShare.getIcon().loadDrawable(context), quickShare.title, quickShare.title, quickShare.title ) { debugLog(LogConfig.DEBUG_ACTIONS) { "Quickshare tapped" } onDeferrableActionTapped { result -> Loading @@ -141,12 +138,7 @@ constructor( ) sendPendingIntent( smartActionsProvider .wrapIntent( quickShare, result.uri, result.subject, requestId ) .wrapIntent(quickShare, result.uri, result.subject, requestId) .actionIntent ) } Loading @@ -157,54 +149,38 @@ constructor( } } } } override fun setCompletedScreenshot(result: SavedImageData) { override fun setCompletedScreenshot(result: ScreenshotSavedResult) { if (this.result != null) { Log.e(TAG, "Got a second completed screenshot for existing request!") return } if (result.uri == null || result.owner == null || result.imageTime == null) { Log.e(TAG, "Invalid result provided!") return } if (result.subject == null) { result.subject = getSubjectString(result.imageTime) } this.result = result pendingAction?.invoke(result) if (smartActionsEnabled(result.owner)) { smartActionsProvider.requestSmartActions(request, requestId, result) { smartActions -> viewModel.addActions( smartActions.map { ActionButtonViewModel( it.getIcon().loadDrawable(context), it.title, it.title, ) { ActionButtonViewModel(it.getIcon().loadDrawable(context), it.title, it.title) { sendPendingIntent(it.actionIntent) } } ) } } } override fun isPendingSharedTransition(): Boolean { return isPendingSharedTransition } private fun onDeferrableActionTapped(onResult: (SavedImageData) -> Unit) { private fun onDeferrableActionTapped(onResult: (ScreenshotSavedResult) -> Unit) { result?.let { onResult.invoke(it) } ?: run { pendingAction = onResult } } private fun startSharedTransition(intent: Intent, overrideTransition: Boolean) { val user = result?.owner ?: run { Log.wtf(TAG, "User handle not provided in screenshot result! Result: $result") return } private fun startSharedTransition( intent: Intent, user: UserHandle, overrideTransition: Boolean ) { isPendingSharedTransition = true applicationScope.launch("$TAG#launchIntentAsync") { actionExecutor.launchIntent(intent, windowTransition.invoke(), user, overrideTransition) Loading @@ -225,21 +201,6 @@ constructor( } } private fun smartActionsEnabled(user: UserHandle): Boolean { val savingToOtherUser = user != Process.myUserHandle() return !savingToOtherUser && DeviceConfig.getBoolean( DeviceConfig.NAMESPACE_SYSTEMUI, SystemUiDeviceConfigFlags.ENABLE_SCREENSHOT_NOTIFICATION_SMART_ACTIONS, true ) } private fun getSubjectString(imageTime: Long): String { val subjectDate = DateFormat.getDateTimeInstance().format(Date(imageTime)) return String.format(SCREENSHOT_SHARE_SUBJECT_TEMPLATE, subjectDate) } @AssistedFactory interface Factory : ScreenshotActionsProvider.Factory { override fun create( Loading @@ -252,6 +213,5 @@ constructor( companion object { private const val TAG = "ScreenshotActionsProvider" private const val SCREENSHOT_SHARE_SUBJECT_TEMPLATE = "Screenshot (%s)" } } packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java +3 −7 Original line number Diff line number Diff line Loading @@ -942,7 +942,7 @@ public class ScreenshotController { private void saveScreenshotInBackground( ScreenshotData screenshot, UUID requestId, Consumer<Uri> finisher) { ListenableFuture<ImageExporter.Result> future = mImageExporter.export(mBgExecutor, requestId, screenshot.getBitmap(), screenshot.getUserHandle(), mDisplayId); requestId, screenshot.getBitmap(), screenshot.getUserOrDefault(), mDisplayId); future.addListener(() -> { try { ImageExporter.Result result = future.get(); Loading @@ -950,12 +950,8 @@ public class ScreenshotController { logScreenshotResultStatus(result.uri, screenshot.getUserHandle()); mScreenshotHandler.resetTimeout(); if (result.uri != null) { final SavedImageData savedImageData = new SavedImageData(); savedImageData.uri = result.uri; savedImageData.owner = screenshot.getUserHandle(); savedImageData.imageTime = result.timestamp; mActionsProvider.setCompletedScreenshot(savedImageData); mViewProxy.setChipIntents(savedImageData); mActionsProvider.setCompletedScreenshot(new ScreenshotSavedResult( result.uri, screenshot.getUserOrDefault(), result.timestamp)); } if (DEBUG_CALLBACK) { Log.d(TAG, "finished background processing, Calling (Consumer<Uri>) " Loading packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotData.kt +5 −0 Original line number Diff line number Diff line Loading @@ -5,6 +5,7 @@ import android.graphics.Bitmap import android.graphics.Insets import android.graphics.Rect import android.net.Uri import android.os.Process import android.os.UserHandle import android.view.Display import android.view.WindowManager.ScreenshotSource Loading @@ -31,6 +32,10 @@ data class ScreenshotData( val packageNameString: String get() = if (topComponent == null) "" else topComponent!!.packageName fun getUserOrDefault(): UserHandle { return userHandle ?: Process.myUserHandle() } companion object { @JvmStatic fun fromRequest(request: ScreenshotRequest, displayId: Int = Display.DEFAULT_DISPLAY) = Loading packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSavedResult.kt 0 → 100644 +39 −0 Original line number Diff line number Diff line /* * Copyright (C) 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.systemui.screenshot import android.net.Uri import android.os.UserHandle import java.text.DateFormat import java.util.Date /** * Represents a saved screenshot, with the uri and user it was saved to as well as the time it was * saved. */ data class ScreenshotSavedResult(val uri: Uri, val user: UserHandle, val imageTime: Long) { val subject: String init { val subjectDate = DateFormat.getDateTimeInstance().format(Date(imageTime)) subject = String.format(SCREENSHOT_SHARE_SUBJECT_TEMPLATE, subjectDate) } companion object { private const val SCREENSHOT_SHARE_SUBJECT_TEMPLATE = "Screenshot (%s)" } } packages/SystemUI/src/com/android/systemui/screenshot/SmartActionsProvider.kt +13 −8 Original line number Diff line number Diff line Loading @@ -65,10 +65,9 @@ constructor( onAction: (Notification.Action) -> Unit ) { val bitmap = data.bitmap ?: return val user = data.userHandle ?: return val component = data.topComponent ?: ComponentName("", "") requestQuickShareAction(id, bitmap, component, user) { quickShareAction -> onAction(quickShareAction) requestQuickShareAction(id, bitmap, component, data.getUserOrDefault()) { quickShare -> onAction(quickShare) } } Loading @@ -83,14 +82,19 @@ constructor( fun requestSmartActions( data: ScreenshotData, id: String, result: ScreenshotController.SavedImageData, result: ScreenshotSavedResult, onActions: (List<Notification.Action>) -> Unit ) { val bitmap = data.bitmap ?: return val user = data.userHandle ?: return val uri = result.uri ?: return val component = data.topComponent ?: ComponentName("", "") requestSmartActions(id, bitmap, component, user, uri, REGULAR_SMART_ACTIONS) { actions -> requestSmartActions( id, bitmap, component, data.getUserOrDefault(), result.uri, REGULAR_SMART_ACTIONS ) { actions -> onActions(actions) } } Loading Loading @@ -197,7 +201,7 @@ constructor( onActions(listOf()) return } var smartActionsFuture: CompletableFuture<List<Notification.Action>> val smartActionsFuture: CompletableFuture<List<Notification.Action>> val startTimeMs = SystemClock.uptimeMillis() try { smartActionsFuture = Loading Loading @@ -266,6 +270,7 @@ constructor( Log.e(TAG, "Error in notifyScreenshotOp: ", e) } } private fun isSmartActionsEnabled(user: UserHandle): Boolean { // Smart actions don't yet work for cross-user saves. val savingToOtherUser = user !== Process.myUserHandle() Loading Loading
packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionsProvider.kt +48 −88 Original line number Diff line number Diff line Loading @@ -23,21 +23,17 @@ import android.app.PendingIntent import android.app.assist.AssistContent import android.content.Context import android.content.Intent import android.os.Process import android.os.UserHandle import android.provider.DeviceConfig import android.util.Log import android.util.Pair import androidx.appcompat.content.res.AppCompatResources import com.android.app.tracing.coroutines.launch import com.android.internal.config.sysui.SystemUiDeviceConfigFlags import com.android.internal.logging.UiEventLogger import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.log.DebugLogger.debugLog import com.android.systemui.res.R import com.android.systemui.screenshot.ActionIntentCreator.createEdit import com.android.systemui.screenshot.ActionIntentCreator.createShareWithSubject import com.android.systemui.screenshot.ScreenshotController.SavedImageData import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_EDIT_TAPPED import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_PREVIEW_TAPPED import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_SHARE_TAPPED Loading @@ -47,8 +43,6 @@ import com.android.systemui.screenshot.ui.viewmodel.ScreenshotViewModel import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import java.text.DateFormat import java.util.Date import kotlinx.coroutines.CoroutineScope /** Loading @@ -56,7 +50,7 @@ import kotlinx.coroutines.CoroutineScope * implementation. */ interface ScreenshotActionsProvider { fun setCompletedScreenshot(result: SavedImageData) fun setCompletedScreenshot(result: ScreenshotSavedResult) fun isPendingSharedTransition(): Boolean fun onAssistContentAvailable(assistContent: AssistContent) {} Loading Loading @@ -85,8 +79,8 @@ constructor( @Assisted val windowTransition: () -> Pair<ActivityOptions, ExitTransitionCoordinator>, @Assisted val requestDismissal: () -> Unit, ) : ScreenshotActionsProvider { private var pendingAction: ((SavedImageData) -> Unit)? = null private var result: SavedImageData? = null private var pendingAction: ((ScreenshotSavedResult) -> Unit)? = null private var result: ScreenshotSavedResult? = null private var isPendingSharedTransition = false init { Loading @@ -94,7 +88,7 @@ constructor( debugLog(LogConfig.DEBUG_ACTIONS) { "Preview tapped" } uiEventLogger.log(SCREENSHOT_PREVIEW_TAPPED, 0, request.packageNameString) onDeferrableActionTapped { result -> startSharedTransition(createEdit(result.uri, context), true) startSharedTransition(createEdit(result.uri, context), result.user, true) } } viewModel.addAction( Loading @@ -106,7 +100,7 @@ constructor( debugLog(LogConfig.DEBUG_ACTIONS) { "Edit tapped" } uiEventLogger.log(SCREENSHOT_EDIT_TAPPED, 0, request.packageNameString) onDeferrableActionTapped { result -> startSharedTransition(createEdit(result.uri, context), true) startSharedTransition(createEdit(result.uri, context), result.user, true) } } ) Loading @@ -119,18 +113,21 @@ constructor( debugLog(LogConfig.DEBUG_ACTIONS) { "Share tapped" } uiEventLogger.log(SCREENSHOT_SHARE_TAPPED, 0, request.packageNameString) onDeferrableActionTapped { result -> startSharedTransition(createShareWithSubject(result.uri, result.subject), false) startSharedTransition( createShareWithSubject(result.uri, result.subject), result.user, false ) } } ) if (smartActionsEnabled(request.userHandle ?: Process.myUserHandle())) { smartActionsProvider.requestQuickShare(request, requestId) { quickShare -> if (!quickShare.actionIntent.isImmutable) { viewModel.addAction( ActionButtonViewModel( quickShare.getIcon().loadDrawable(context), quickShare.title, quickShare.title, quickShare.title ) { debugLog(LogConfig.DEBUG_ACTIONS) { "Quickshare tapped" } onDeferrableActionTapped { result -> Loading @@ -141,12 +138,7 @@ constructor( ) sendPendingIntent( smartActionsProvider .wrapIntent( quickShare, result.uri, result.subject, requestId ) .wrapIntent(quickShare, result.uri, result.subject, requestId) .actionIntent ) } Loading @@ -157,54 +149,38 @@ constructor( } } } } override fun setCompletedScreenshot(result: SavedImageData) { override fun setCompletedScreenshot(result: ScreenshotSavedResult) { if (this.result != null) { Log.e(TAG, "Got a second completed screenshot for existing request!") return } if (result.uri == null || result.owner == null || result.imageTime == null) { Log.e(TAG, "Invalid result provided!") return } if (result.subject == null) { result.subject = getSubjectString(result.imageTime) } this.result = result pendingAction?.invoke(result) if (smartActionsEnabled(result.owner)) { smartActionsProvider.requestSmartActions(request, requestId, result) { smartActions -> viewModel.addActions( smartActions.map { ActionButtonViewModel( it.getIcon().loadDrawable(context), it.title, it.title, ) { ActionButtonViewModel(it.getIcon().loadDrawable(context), it.title, it.title) { sendPendingIntent(it.actionIntent) } } ) } } } override fun isPendingSharedTransition(): Boolean { return isPendingSharedTransition } private fun onDeferrableActionTapped(onResult: (SavedImageData) -> Unit) { private fun onDeferrableActionTapped(onResult: (ScreenshotSavedResult) -> Unit) { result?.let { onResult.invoke(it) } ?: run { pendingAction = onResult } } private fun startSharedTransition(intent: Intent, overrideTransition: Boolean) { val user = result?.owner ?: run { Log.wtf(TAG, "User handle not provided in screenshot result! Result: $result") return } private fun startSharedTransition( intent: Intent, user: UserHandle, overrideTransition: Boolean ) { isPendingSharedTransition = true applicationScope.launch("$TAG#launchIntentAsync") { actionExecutor.launchIntent(intent, windowTransition.invoke(), user, overrideTransition) Loading @@ -225,21 +201,6 @@ constructor( } } private fun smartActionsEnabled(user: UserHandle): Boolean { val savingToOtherUser = user != Process.myUserHandle() return !savingToOtherUser && DeviceConfig.getBoolean( DeviceConfig.NAMESPACE_SYSTEMUI, SystemUiDeviceConfigFlags.ENABLE_SCREENSHOT_NOTIFICATION_SMART_ACTIONS, true ) } private fun getSubjectString(imageTime: Long): String { val subjectDate = DateFormat.getDateTimeInstance().format(Date(imageTime)) return String.format(SCREENSHOT_SHARE_SUBJECT_TEMPLATE, subjectDate) } @AssistedFactory interface Factory : ScreenshotActionsProvider.Factory { override fun create( Loading @@ -252,6 +213,5 @@ constructor( companion object { private const val TAG = "ScreenshotActionsProvider" private const val SCREENSHOT_SHARE_SUBJECT_TEMPLATE = "Screenshot (%s)" } }
packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java +3 −7 Original line number Diff line number Diff line Loading @@ -942,7 +942,7 @@ public class ScreenshotController { private void saveScreenshotInBackground( ScreenshotData screenshot, UUID requestId, Consumer<Uri> finisher) { ListenableFuture<ImageExporter.Result> future = mImageExporter.export(mBgExecutor, requestId, screenshot.getBitmap(), screenshot.getUserHandle(), mDisplayId); requestId, screenshot.getBitmap(), screenshot.getUserOrDefault(), mDisplayId); future.addListener(() -> { try { ImageExporter.Result result = future.get(); Loading @@ -950,12 +950,8 @@ public class ScreenshotController { logScreenshotResultStatus(result.uri, screenshot.getUserHandle()); mScreenshotHandler.resetTimeout(); if (result.uri != null) { final SavedImageData savedImageData = new SavedImageData(); savedImageData.uri = result.uri; savedImageData.owner = screenshot.getUserHandle(); savedImageData.imageTime = result.timestamp; mActionsProvider.setCompletedScreenshot(savedImageData); mViewProxy.setChipIntents(savedImageData); mActionsProvider.setCompletedScreenshot(new ScreenshotSavedResult( result.uri, screenshot.getUserOrDefault(), result.timestamp)); } if (DEBUG_CALLBACK) { Log.d(TAG, "finished background processing, Calling (Consumer<Uri>) " Loading
packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotData.kt +5 −0 Original line number Diff line number Diff line Loading @@ -5,6 +5,7 @@ import android.graphics.Bitmap import android.graphics.Insets import android.graphics.Rect import android.net.Uri import android.os.Process import android.os.UserHandle import android.view.Display import android.view.WindowManager.ScreenshotSource Loading @@ -31,6 +32,10 @@ data class ScreenshotData( val packageNameString: String get() = if (topComponent == null) "" else topComponent!!.packageName fun getUserOrDefault(): UserHandle { return userHandle ?: Process.myUserHandle() } companion object { @JvmStatic fun fromRequest(request: ScreenshotRequest, displayId: Int = Display.DEFAULT_DISPLAY) = Loading
packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSavedResult.kt 0 → 100644 +39 −0 Original line number Diff line number Diff line /* * Copyright (C) 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.systemui.screenshot import android.net.Uri import android.os.UserHandle import java.text.DateFormat import java.util.Date /** * Represents a saved screenshot, with the uri and user it was saved to as well as the time it was * saved. */ data class ScreenshotSavedResult(val uri: Uri, val user: UserHandle, val imageTime: Long) { val subject: String init { val subjectDate = DateFormat.getDateTimeInstance().format(Date(imageTime)) subject = String.format(SCREENSHOT_SHARE_SUBJECT_TEMPLATE, subjectDate) } companion object { private const val SCREENSHOT_SHARE_SUBJECT_TEMPLATE = "Screenshot (%s)" } }
packages/SystemUI/src/com/android/systemui/screenshot/SmartActionsProvider.kt +13 −8 Original line number Diff line number Diff line Loading @@ -65,10 +65,9 @@ constructor( onAction: (Notification.Action) -> Unit ) { val bitmap = data.bitmap ?: return val user = data.userHandle ?: return val component = data.topComponent ?: ComponentName("", "") requestQuickShareAction(id, bitmap, component, user) { quickShareAction -> onAction(quickShareAction) requestQuickShareAction(id, bitmap, component, data.getUserOrDefault()) { quickShare -> onAction(quickShare) } } Loading @@ -83,14 +82,19 @@ constructor( fun requestSmartActions( data: ScreenshotData, id: String, result: ScreenshotController.SavedImageData, result: ScreenshotSavedResult, onActions: (List<Notification.Action>) -> Unit ) { val bitmap = data.bitmap ?: return val user = data.userHandle ?: return val uri = result.uri ?: return val component = data.topComponent ?: ComponentName("", "") requestSmartActions(id, bitmap, component, user, uri, REGULAR_SMART_ACTIONS) { actions -> requestSmartActions( id, bitmap, component, data.getUserOrDefault(), result.uri, REGULAR_SMART_ACTIONS ) { actions -> onActions(actions) } } Loading Loading @@ -197,7 +201,7 @@ constructor( onActions(listOf()) return } var smartActionsFuture: CompletableFuture<List<Notification.Action>> val smartActionsFuture: CompletableFuture<List<Notification.Action>> val startTimeMs = SystemClock.uptimeMillis() try { smartActionsFuture = Loading Loading @@ -266,6 +270,7 @@ constructor( Log.e(TAG, "Error in notifyScreenshotOp: ", e) } } private fun isSmartActionsEnabled(user: UserHandle): Boolean { // Smart actions don't yet work for cross-user saves. val savingToOtherUser = user !== Process.myUserHandle() Loading