Merge "Support BP retry face auth on SFPS acquired" into 24D1-dev
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 48ddd30..81a1a2d 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -12956,7 +12956,7 @@
field public static final String SERVICE_INTERFACE = "android.service.ondeviceintelligence.OnDeviceIntelligenceService";
}
- public static class OnDeviceIntelligenceService.OnDeviceIntelligenceServiceException extends java.lang.Exception {
+ public abstract static class OnDeviceIntelligenceService.OnDeviceIntelligenceServiceException extends java.lang.Exception {
ctor public OnDeviceIntelligenceService.OnDeviceIntelligenceServiceException(int);
ctor public OnDeviceIntelligenceService.OnDeviceIntelligenceServiceException(int, @NonNull String);
method public int getErrorCode();
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 03fa841..8439833 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -10919,7 +10919,7 @@
"biometric_debug_enabled";
/**
- * Whether or not virtual sensors are enabled.
+ * Whether or not both fingerprint and face virtual sensors are enabled.
* @hide
*/
@TestApi
@@ -10927,6 +10927,22 @@
public static final String BIOMETRIC_VIRTUAL_ENABLED = "biometric_virtual_enabled";
/**
+ * Whether or not fingerprint virtual sensors are enabled.
+ * @hide
+ */
+ @FlaggedApi("com.android.server.biometrics.face_vhal_feature")
+ public static final String BIOMETRIC_FINGERPRINT_VIRTUAL_ENABLED =
+ "biometric_fingerprint_virtual_enabled";
+
+ /**
+ * Whether or not face virtual sensors are enabled.
+ * @hide
+ */
+ @FlaggedApi("com.android.server.biometrics.face_vhal_feature")
+ public static final String BIOMETRIC_FACE_VIRTUAL_ENABLED =
+ "biometric_face_virtual_enabled";
+
+ /**
* Whether or not biometric is allowed on Keyguard.
* @hide
*/
diff --git a/core/java/com/android/internal/jank/Cuj.java b/core/java/com/android/internal/jank/Cuj.java
index 3662d69..d2a533c 100644
--- a/core/java/com/android/internal/jank/Cuj.java
+++ b/core/java/com/android/internal/jank/Cuj.java
@@ -124,10 +124,13 @@
public static final int CUJ_BACK_PANEL_ARROW = 88;
public static final int CUJ_LAUNCHER_CLOSE_ALL_APPS_BACK = 89;
public static final int CUJ_LAUNCHER_SEARCH_QSB_WEB_SEARCH = 90;
+ public static final int CUJ_LAUNCHER_LAUNCH_APP_PAIR_FROM_WORKSPACE = 91;
+ public static final int CUJ_LAUNCHER_LAUNCH_APP_PAIR_FROM_TASKBAR = 92;
+ public static final int CUJ_LAUNCHER_SAVE_APP_PAIR = 93;
// When adding a CUJ, update this and make sure to also update CUJ_TO_STATSD_INTERACTION_TYPE.
@VisibleForTesting
- static final int LAST_CUJ = CUJ_LAUNCHER_SEARCH_QSB_WEB_SEARCH;
+ static final int LAST_CUJ = CUJ_LAUNCHER_SAVE_APP_PAIR;
/** @hide */
@IntDef({
@@ -212,6 +215,9 @@
CUJ_BACK_PANEL_ARROW,
CUJ_LAUNCHER_CLOSE_ALL_APPS_BACK,
CUJ_LAUNCHER_SEARCH_QSB_WEB_SEARCH,
+ CUJ_LAUNCHER_LAUNCH_APP_PAIR_FROM_WORKSPACE,
+ CUJ_LAUNCHER_LAUNCH_APP_PAIR_FROM_TASKBAR,
+ CUJ_LAUNCHER_SAVE_APP_PAIR
})
@Retention(RetentionPolicy.SOURCE)
public @interface CujType {
@@ -306,6 +312,9 @@
CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_BACK_PANEL_ARROW] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__BACK_PANEL_ARROW;
CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_CLOSE_ALL_APPS_BACK] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_CLOSE_ALL_APPS_BACK;
CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_SEARCH_QSB_WEB_SEARCH] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_SEARCH_QSB_WEB_SEARCH;
+ CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_LAUNCH_APP_PAIR_FROM_WORKSPACE] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_LAUNCH_APP_PAIR_FROM_WORKSPACE;
+ CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_LAUNCH_APP_PAIR_FROM_TASKBAR] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_LAUNCH_APP_PAIR_FROM_TASKBAR;
+ CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_SAVE_APP_PAIR] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_SAVE_APP_PAIR;
}
private Cuj() {
@@ -484,6 +493,12 @@
return "LAUNCHER_CLOSE_ALL_APPS_BACK";
case CUJ_LAUNCHER_SEARCH_QSB_WEB_SEARCH:
return "LAUNCHER_SEARCH_QSB_WEB_SEARCH";
+ case CUJ_LAUNCHER_LAUNCH_APP_PAIR_FROM_WORKSPACE:
+ return "LAUNCHER_LAUNCH_APP_PAIR_FROM_WORKSPACE";
+ case CUJ_LAUNCHER_LAUNCH_APP_PAIR_FROM_TASKBAR:
+ return "LAUNCHER_LAUNCH_APP_PAIR_FROM_TASKBAR";
+ case CUJ_LAUNCHER_SAVE_APP_PAIR:
+ return "LAUNCHER_SAVE_APP_PAIR";
}
return "UNKNOWN";
}
diff --git a/core/java/com/android/internal/jank/InteractionJankMonitor.java b/core/java/com/android/internal/jank/InteractionJankMonitor.java
index 0ec8b74..a288fb7 100644
--- a/core/java/com/android/internal/jank/InteractionJankMonitor.java
+++ b/core/java/com/android/internal/jank/InteractionJankMonitor.java
@@ -165,6 +165,9 @@
@Deprecated public static final int CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY = Cuj.CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY;
@Deprecated public static final int CUJ_PREDICTIVE_BACK_CROSS_TASK = Cuj.CUJ_PREDICTIVE_BACK_CROSS_TASK;
@Deprecated public static final int CUJ_PREDICTIVE_BACK_HOME = Cuj.CUJ_PREDICTIVE_BACK_HOME;
+ @Deprecated public static final int CUJ_LAUNCHER_LAUNCH_APP_PAIR_FROM_WORKSPACE = Cuj.CUJ_LAUNCHER_LAUNCH_APP_PAIR_FROM_WORKSPACE;
+ @Deprecated public static final int CUJ_LAUNCHER_LAUNCH_APP_PAIR_FROM_TASKBAR = Cuj.CUJ_LAUNCHER_LAUNCH_APP_PAIR_FROM_TASKBAR;
+ @Deprecated public static final int CUJ_LAUNCHER_SAVE_APP_PAIR = Cuj.CUJ_LAUNCHER_SAVE_APP_PAIR;
private static class InstanceHolder {
public static final InteractionJankMonitor INSTANCE =
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java
index c26604a..7c2ba45 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java
@@ -293,7 +293,13 @@
@Override
public void onFoldStateChanged(boolean isFolded) {
if (isFolded) {
+ // Reset unfold animation finished flag on folding, so it could be used next time
+ // when we unfold the device as an indication that animation hasn't finished yet
mAnimationFinished = false;
+
+ // If we are currently animating unfold animation we should finish it because
+ // the animation might not start and finish as the device was folded
+ finishTransitionIfNeeded();
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldTransitionHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldTransitionHandlerTest.java
index c5e229f..acc0bce 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldTransitionHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldTransitionHandlerTest.java
@@ -298,6 +298,32 @@
}
@Test
+ public void fold_animationInProgress_finishesTransition() {
+ TransitionRequestInfo requestInfo = createUnfoldTransitionRequestInfo();
+ TransitionFinishCallback finishCallback = mock(TransitionFinishCallback.class);
+
+ // Unfold
+ mShellUnfoldProgressProvider.onFoldStateChanged(/* isFolded= */ false);
+ mUnfoldTransitionHandler.handleRequest(mTransition, requestInfo);
+ mUnfoldTransitionHandler.startAnimation(
+ mTransition,
+ mock(TransitionInfo.class),
+ mock(SurfaceControl.Transaction.class),
+ mock(SurfaceControl.Transaction.class),
+ finishCallback
+ );
+
+ // Start animation but don't finish it
+ mShellUnfoldProgressProvider.onStateChangeStarted();
+ mShellUnfoldProgressProvider.onStateChangeProgress(0.5f);
+
+ // Fold
+ mShellUnfoldProgressProvider.onFoldStateChanged(/* isFolded= */ true);
+
+ verify(finishCallback).onTransitionFinished(any());
+ }
+
+ @Test
public void mergeAnimation_eatsDisplayOnlyTransitions() {
TransitionRequestInfo requestInfo = createUnfoldTransitionRequestInfo();
mUnfoldTransitionHandler.handleRequest(mTransition, requestInfo);
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java b/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java
index 221ca4f..4763c0b 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java
@@ -244,8 +244,7 @@
}
private static boolean isArchivingEnabled() {
- return android.content.pm.Flags.archiving()
- || SystemProperties.getBoolean("pm.archiving.enabled", false);
+ return android.content.pm.Flags.archiving();
}
private boolean isCloneProfile(UserHandle userHandle) {
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt
index a395266..e1e1ee5 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt
@@ -151,7 +151,7 @@
}
private fun isArchivingEnabled(featureFlags: FeatureFlags) =
- featureFlags.archiving() || SystemProperties.getBoolean("pm.archiving.enabled", false)
+ featureFlags.archiving()
override fun showSystemPredicate(
userIdFlow: Flow<Int>,
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 28cdc6d..4f9e11a 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -848,6 +848,8 @@
Settings.Secure.BIOMETRIC_APP_ENABLED,
Settings.Secure.BIOMETRIC_KEYGUARD_ENABLED,
Settings.Secure.BIOMETRIC_VIRTUAL_ENABLED,
+ Settings.Secure.BIOMETRIC_FINGERPRINT_VIRTUAL_ENABLED,
+ Settings.Secure.BIOMETRIC_FACE_VIRTUAL_ENABLED,
Settings.Secure.BLUETOOTH_ADDR_VALID,
Settings.Secure.BLUETOOTH_ADDRESS,
Settings.Secure.BLUETOOTH_NAME,
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index cc2e84c..1129c63 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -326,6 +326,7 @@
"androidx.test.uiautomator_uiautomator",
"androidx.core_core-animation-testing",
"mockito-target-extended-minus-junit4",
+ "mockito-kotlin2",
"androidx.test.ext.junit",
"androidx.test.ext.truth",
"kotlin-test",
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index dd1fc23..8e73379 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -343,6 +343,16 @@
}
flag {
+ name: "activity_transition_use_largest_window"
+ namespace: "systemui"
+ description: "Target largest opening window during activity transitions."
+ bug: "323294573"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "centralized_status_bar_height_fix"
namespace: "systemui"
description: "Refactors shade header and keyguard status bar to read status bar dimens from a"
diff --git a/packages/SystemUI/animation/Android.bp b/packages/SystemUI/animation/Android.bp
index 99b7c36..2268d16 100644
--- a/packages/SystemUI/animation/Android.bp
+++ b/packages/SystemUI/animation/Android.bp
@@ -44,6 +44,7 @@
"androidx.core_core-animation-nodeps",
"androidx.core_core-ktx",
"androidx.annotation_annotation",
+ "com_android_systemui_flags_lib",
"SystemUIShaderLib",
"WindowManager-Shell-shared",
"animationlib",
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt
index 1b99e19..ea1cb34 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt
@@ -43,6 +43,7 @@
import com.android.app.animation.Interpolators
import com.android.internal.annotations.VisibleForTesting
import com.android.internal.policy.ScreenDecorationsUtils
+import com.android.systemui.Flags.activityTransitionUseLargestWindow
import kotlin.math.roundToInt
private const val TAG = "ActivityTransitionAnimator"
@@ -648,11 +649,27 @@
var candidate: RemoteAnimationTarget? = null
for (it in apps) {
if (it.mode == RemoteAnimationTarget.MODE_OPENING) {
- if (!it.hasAnimatingParent) {
- return it
- }
- if (candidate == null) {
- candidate = it
+ if (activityTransitionUseLargestWindow()) {
+ if (
+ candidate == null ||
+ !it.hasAnimatingParent && candidate.hasAnimatingParent
+ ) {
+ candidate = it
+ continue
+ }
+ if (
+ !it.hasAnimatingParent &&
+ it.screenSpaceBounds.hasGreaterAreaThan(candidate.screenSpaceBounds)
+ ) {
+ candidate = it
+ }
+ } else {
+ if (!it.hasAnimatingParent) {
+ return it
+ }
+ if (candidate == null) {
+ candidate = it
+ }
}
}
}
@@ -960,5 +977,9 @@
e.printStackTrace()
}
}
+
+ private fun Rect.hasGreaterAreaThan(other: Rect): Boolean {
+ return (this.width() * this.height()) > (other.width() * other.height())
+ }
}
}
diff --git a/packages/SystemUI/res/layout/screenshot_shelf.xml b/packages/SystemUI/res/layout/screenshot_shelf.xml
index c988b4a..eeb64bd8 100644
--- a/packages/SystemUI/res/layout/screenshot_shelf.xml
+++ b/packages/SystemUI/res/layout/screenshot_shelf.xml
@@ -51,15 +51,7 @@
<LinearLayout
android:id="@+id/screenshot_actions"
android:layout_width="wrap_content"
- android:layout_height="wrap_content">
- <include layout="@layout/overlay_action_chip"
- android:id="@+id/screenshot_share_chip"/>
- <include layout="@layout/overlay_action_chip"
- android:id="@+id/screenshot_edit_chip"/>
- <include layout="@layout/overlay_action_chip"
- android:id="@+id/screenshot_scroll_chip"
- android:visibility="gone" />
- </LinearLayout>
+ android:layout_height="wrap_content" />
</HorizontalScrollView>
<View
android:id="@+id/screenshot_preview_border"
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ActionExecutor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ActionExecutor.kt
new file mode 100644
index 0000000..caa67df
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ActionExecutor.kt
@@ -0,0 +1,113 @@
+/*
+ * 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.app.ActivityOptions
+import android.app.BroadcastOptions
+import android.app.ExitTransitionCoordinator
+import android.app.ExitTransitionCoordinator.ExitTransitionCallbacks
+import android.app.PendingIntent
+import android.content.Intent
+import android.os.UserHandle
+import android.util.Log
+import android.util.Pair
+import android.view.View
+import android.view.Window
+import com.android.app.tracing.coroutines.launch
+import com.android.internal.app.ChooserActivity
+import com.android.systemui.dagger.qualifiers.Application
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import kotlinx.coroutines.CoroutineScope
+
+class ActionExecutor
+@AssistedInject
+constructor(
+ private val intentExecutor: ActionIntentExecutor,
+ @Application private val applicationScope: CoroutineScope,
+ @Assisted val window: Window,
+ @Assisted val transitionView: View,
+ @Assisted val onDismiss: (() -> Unit)
+) {
+
+ var isPendingSharedTransition = false
+ private set
+
+ fun startSharedTransition(intent: Intent, user: UserHandle, overrideTransition: Boolean) {
+ isPendingSharedTransition = true
+ val windowTransition = createWindowTransition()
+ applicationScope.launch("$TAG#launchIntentAsync") {
+ intentExecutor.launchIntent(
+ intent,
+ user,
+ overrideTransition,
+ windowTransition.first,
+ windowTransition.second
+ )
+ }
+ }
+
+ fun sendPendingIntent(pendingIntent: PendingIntent) {
+ try {
+ val options = BroadcastOptions.makeBasic()
+ options.setInteractive(true)
+ options.setPendingIntentBackgroundActivityStartMode(
+ ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
+ )
+ pendingIntent.send(options.toBundle())
+ onDismiss.invoke()
+ } catch (e: PendingIntent.CanceledException) {
+ Log.e(TAG, "Intent cancelled", e)
+ }
+ }
+
+ /**
+ * Supplies the necessary bits for the shared element transition to share sheet. Note that once
+ * called, the action intent to share must be sent immediately after.
+ */
+ private fun createWindowTransition(): Pair<ActivityOptions, ExitTransitionCoordinator> {
+ val callbacks: ExitTransitionCallbacks =
+ object : ExitTransitionCallbacks {
+ override fun isReturnTransitionAllowed(): Boolean {
+ return false
+ }
+
+ override fun hideSharedElements() {
+ isPendingSharedTransition = false
+ onDismiss.invoke()
+ }
+
+ override fun onFinish() {}
+ }
+ return ActivityOptions.startSharedElementAnimation(
+ window,
+ callbacks,
+ null,
+ Pair.create(transitionView, ChooserActivity.FIRST_IMAGE_PREVIEW_TRANSITION_NAME)
+ )
+ }
+
+ @AssistedFactory
+ interface Factory {
+ fun create(window: Window, transitionView: View, onDismiss: (() -> Unit)): ActionExecutor
+ }
+
+ companion object {
+ private const val TAG = "ActionExecutor"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentCreator.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentCreator.kt
index 8e9769ab..a0cef52 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentCreator.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentCreator.kt
@@ -23,7 +23,9 @@
import android.content.Context
import android.content.Intent
import android.net.Uri
+import android.os.UserHandle
import com.android.systemui.res.R
+import com.android.systemui.screenshot.scroll.LongScreenshotActivity
object ActionIntentCreator {
/** @return a chooser intent to share the given URI. */
@@ -89,6 +91,14 @@
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
}
+ /** @return an Intent to start the LongScreenshotActivity */
+ fun createLongScreenshotIntent(owner: UserHandle, context: Context): Intent {
+ return Intent(context, LongScreenshotActivity::class.java)
+ .putExtra(LongScreenshotActivity.EXTRA_SCREENSHOT_USER_HANDLE, owner)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
+ }
+
private const val EXTRA_EDIT_SOURCE = "edit_source"
private const val EDIT_SOURCE_SCREENSHOT = "screenshot"
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt
index 1f9853b..4eca51d 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt
@@ -25,7 +25,6 @@
import android.os.RemoteException
import android.os.UserHandle
import android.util.Log
-import android.util.Pair
import android.view.IRemoteAnimationFinishedCallback
import android.view.IRemoteAnimationRunner
import android.view.RemoteAnimationAdapter
@@ -67,20 +66,22 @@
*/
fun launchIntentAsync(
intent: Intent,
- transition: Pair<ActivityOptions, ExitTransitionCoordinator>?,
user: UserHandle,
overrideTransition: Boolean,
+ options: ActivityOptions?,
+ transitionCoordinator: ExitTransitionCoordinator?,
) {
applicationScope.launch("$TAG#launchIntentAsync") {
- launchIntent(intent, transition, user, overrideTransition)
+ launchIntent(intent, user, overrideTransition, options, transitionCoordinator)
}
}
suspend fun launchIntent(
intent: Intent,
- transition: Pair<ActivityOptions, ExitTransitionCoordinator>?,
user: UserHandle,
overrideTransition: Boolean,
+ options: ActivityOptions?,
+ transitionCoordinator: ExitTransitionCoordinator?,
) {
if (screenshotActionDismissSystemWindows()) {
keyguardController.dismiss()
@@ -90,14 +91,12 @@
} else {
dismissKeyguard()
}
- transition?.second?.startExit()
+ transitionCoordinator?.startExit()
if (user == myUserHandle()) {
- withContext(mainDispatcher) {
- context.startActivity(intent, transition?.first?.toBundle())
- }
+ withContext(mainDispatcher) { context.startActivity(intent, options?.toBundle()) }
} else {
- launchCrossProfileIntent(user, intent, transition?.first?.toBundle())
+ launchCrossProfileIntent(user, intent, options?.toBundle())
}
if (overrideTransition) {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotViewProxy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotViewProxy.kt
index a1481f6..4cf18fb 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotViewProxy.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotViewProxy.kt
@@ -34,9 +34,9 @@
import com.android.internal.logging.UiEventLogger
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.res.R
-import com.android.systemui.screenshot.scroll.ScrollCaptureController
import com.android.systemui.screenshot.LogConfig.DEBUG_DISMISS
import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_DISMISSED_OTHER
+import com.android.systemui.screenshot.scroll.ScrollCaptureController
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
@@ -113,7 +113,7 @@
override fun setChipIntents(imageData: ScreenshotController.SavedImageData) =
view.setChipIntents(imageData)
- override fun requestDismissal(event: ScreenshotEvent) {
+ override fun requestDismissal(event: ScreenshotEvent?) {
if (DEBUG_DISMISS) {
Log.d(TAG, "screenshot dismissal requested")
}
@@ -124,7 +124,7 @@
}
return
}
- logger.log(event, 0, packageName)
+ event?.let { logger.log(event, 0, packageName) }
view.animateDismissal()
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
index bbf7ed5..4914409 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
@@ -165,6 +165,7 @@
mQuickShareData.quickShareAction, mScreenshotId, uri, mImageTime, image,
mParams.owner);
mImageData.subject = getSubjectString(mImageTime);
+ mImageData.imageTime = mImageTime;
mParams.mActionsReadyListener.onActionsReady(mImageData);
if (DEBUG_CALLBACK) {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionsProvider.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionsProvider.kt
index 97acccd..f69021f 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionsProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionsProvider.kt
@@ -16,78 +16,180 @@
package com.android.systemui.screenshot
+import android.app.assist.AssistContent
import android.content.Context
-import android.content.Intent
-import android.graphics.drawable.Drawable
-import android.net.Uri
-import android.os.UserHandle
+import android.util.Log
import androidx.appcompat.content.res.AppCompatResources
+import com.android.internal.logging.UiEventLogger
+import com.android.systemui.log.DebugLogger.debugLog
import com.android.systemui.res.R
-import javax.inject.Inject
+import com.android.systemui.screenshot.ActionIntentCreator.createEdit
+import com.android.systemui.screenshot.ActionIntentCreator.createShareWithSubject
+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
+import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_SMART_ACTION_TAPPED
+import com.android.systemui.screenshot.ui.viewmodel.ActionButtonViewModel
+import com.android.systemui.screenshot.ui.viewmodel.ScreenshotViewModel
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
/**
* Provides actions for screenshots. This class can be overridden by a vendor-specific SysUI
* implementation.
*/
interface ScreenshotActionsProvider {
- data class ScreenshotAction(
- val icon: Drawable? = null,
- val text: String? = null,
- val description: String,
- val overrideTransition: Boolean = false,
- val retrieveIntent: (Uri) -> Intent
- )
+ fun onScrollChipReady(onClick: Runnable)
+ fun setCompletedScreenshot(result: ScreenshotSavedResult)
- interface ScreenshotActionsCallback {
- fun setPreviewAction(overrideTransition: Boolean = false, retrieveIntent: (Uri) -> Intent)
- fun addAction(action: ScreenshotAction) = addActions(listOf(action))
- fun addActions(actions: List<ScreenshotAction>)
- }
+ fun onAssistContentAvailable(assistContent: AssistContent) {}
interface Factory {
fun create(
- context: Context,
- user: UserHandle?,
- callback: ScreenshotActionsCallback
+ request: ScreenshotData,
+ requestId: String,
+ actionExecutor: ActionExecutor,
): ScreenshotActionsProvider
}
}
-class DefaultScreenshotActionsProvider(
+class DefaultScreenshotActionsProvider
+@AssistedInject
+constructor(
private val context: Context,
- private val user: UserHandle?,
- private val callback: ScreenshotActionsProvider.ScreenshotActionsCallback
+ private val viewModel: ScreenshotViewModel,
+ private val smartActionsProvider: SmartActionsProvider,
+ private val uiEventLogger: UiEventLogger,
+ @Assisted val request: ScreenshotData,
+ @Assisted val requestId: String,
+ @Assisted val actionExecutor: ActionExecutor,
) : ScreenshotActionsProvider {
+ private var pendingAction: ((ScreenshotSavedResult) -> Unit)? = null
+ private var result: ScreenshotSavedResult? = null
+
init {
- callback.setPreviewAction(true) { ActionIntentCreator.createEdit(it, context) }
- val editAction =
- ScreenshotActionsProvider.ScreenshotAction(
+ viewModel.setPreviewAction {
+ debugLog(LogConfig.DEBUG_ACTIONS) { "Preview tapped" }
+ uiEventLogger.log(SCREENSHOT_PREVIEW_TAPPED, 0, request.packageNameString)
+ onDeferrableActionTapped { result ->
+ actionExecutor.startSharedTransition(
+ createEdit(result.uri, context),
+ result.user,
+ true
+ )
+ }
+ }
+ viewModel.addAction(
+ ActionButtonViewModel(
AppCompatResources.getDrawable(context, R.drawable.ic_screenshot_edit),
context.resources.getString(R.string.screenshot_edit_label),
context.resources.getString(R.string.screenshot_edit_description),
- true
- ) { uri ->
- ActionIntentCreator.createEdit(uri, context)
+ ) {
+ debugLog(LogConfig.DEBUG_ACTIONS) { "Edit tapped" }
+ uiEventLogger.log(SCREENSHOT_EDIT_TAPPED, 0, request.packageNameString)
+ onDeferrableActionTapped { result ->
+ actionExecutor.startSharedTransition(
+ createEdit(result.uri, context),
+ result.user,
+ true
+ )
+ }
}
- val shareAction =
- ScreenshotActionsProvider.ScreenshotAction(
+ )
+ viewModel.addAction(
+ ActionButtonViewModel(
AppCompatResources.getDrawable(context, R.drawable.ic_screenshot_share),
context.resources.getString(R.string.screenshot_share_label),
context.resources.getString(R.string.screenshot_share_description),
- false
- ) { uri ->
- ActionIntentCreator.createShare(uri)
+ ) {
+ debugLog(LogConfig.DEBUG_ACTIONS) { "Share tapped" }
+ uiEventLogger.log(SCREENSHOT_SHARE_TAPPED, 0, request.packageNameString)
+ onDeferrableActionTapped { result ->
+ actionExecutor.startSharedTransition(
+ createShareWithSubject(result.uri, result.subject),
+ result.user,
+ false
+ )
+ }
}
- callback.addActions(listOf(editAction, shareAction))
+ )
+ smartActionsProvider.requestQuickShare(request, requestId) { quickShare ->
+ if (!quickShare.actionIntent.isImmutable) {
+ viewModel.addAction(
+ ActionButtonViewModel(
+ quickShare.getIcon().loadDrawable(context),
+ quickShare.title,
+ quickShare.title
+ ) {
+ debugLog(LogConfig.DEBUG_ACTIONS) { "Quickshare tapped" }
+ onDeferrableActionTapped { result ->
+ uiEventLogger.log(
+ SCREENSHOT_SMART_ACTION_TAPPED,
+ 0,
+ request.packageNameString
+ )
+ val pendingIntentWithUri =
+ smartActionsProvider.wrapIntent(
+ quickShare,
+ result.uri,
+ result.subject,
+ requestId
+ )
+ actionExecutor.sendPendingIntent(pendingIntentWithUri)
+ }
+ }
+ )
+ } else {
+ Log.w(TAG, "Received immutable quick share pending intent; ignoring")
+ }
+ }
}
- class Factory @Inject constructor() : ScreenshotActionsProvider.Factory {
- override fun create(
- context: Context,
- user: UserHandle?,
- callback: ScreenshotActionsProvider.ScreenshotActionsCallback
- ): ScreenshotActionsProvider {
- return DefaultScreenshotActionsProvider(context, user, callback)
+ override fun onScrollChipReady(onClick: Runnable) {
+ viewModel.addAction(
+ ActionButtonViewModel(
+ AppCompatResources.getDrawable(context, R.drawable.ic_screenshot_scroll),
+ context.resources.getString(R.string.screenshot_scroll_label),
+ context.resources.getString(R.string.screenshot_scroll_label),
+ ) {
+ onClick.run()
+ }
+ )
+ }
+
+ override fun setCompletedScreenshot(result: ScreenshotSavedResult) {
+ if (this.result != null) {
+ Log.e(TAG, "Got a second completed screenshot for existing request!")
+ return
}
+ this.result = result
+ pendingAction?.invoke(result)
+ smartActionsProvider.requestSmartActions(request, requestId, result) { smartActions ->
+ viewModel.addActions(
+ smartActions.map {
+ ActionButtonViewModel(it.getIcon().loadDrawable(context), it.title, it.title) {
+ actionExecutor.sendPendingIntent(it.actionIntent)
+ }
+ }
+ )
+ }
+ }
+
+ private fun onDeferrableActionTapped(onResult: (ScreenshotSavedResult) -> Unit) {
+ result?.let { onResult.invoke(it) } ?: run { pendingAction = onResult }
+ }
+
+ @AssistedFactory
+ interface Factory : ScreenshotActionsProvider.Factory {
+ override fun create(
+ request: ScreenshotData,
+ requestId: String,
+ actionExecutor: ActionExecutor,
+ ): DefaultScreenshotActionsProvider
+ }
+
+ companion object {
+ private const val TAG = "ScreenshotActionsProvider"
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index 597e773..f43882c 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -29,14 +29,11 @@
import static com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_DISMISSED_OTHER;
import static com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_INTERACTION_TIMEOUT;
-import static java.util.Objects.requireNonNull;
-
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.annotation.MainThread;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.app.ActivityManager;
import android.app.ActivityOptions;
import android.app.ExitTransitionCoordinator;
import android.app.ICompatCameraControlCallback;
@@ -53,7 +50,6 @@
import android.hardware.display.DisplayManager;
import android.net.Uri;
import android.os.Process;
-import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
@@ -61,18 +57,12 @@
import android.util.Log;
import android.util.Pair;
import android.view.Display;
-import android.view.IRemoteAnimationFinishedCallback;
-import android.view.IRemoteAnimationRunner;
-import android.view.RemoteAnimationAdapter;
-import android.view.RemoteAnimationTarget;
import android.view.ScrollCaptureResponse;
import android.view.View;
import android.view.ViewRootImpl;
import android.view.ViewTreeObserver;
import android.view.WindowInsets;
import android.view.WindowManager;
-import android.view.WindowManagerGlobal;
-import android.view.accessibility.AccessibilityManager;
import android.widget.Toast;
import android.window.WindowContext;
@@ -80,16 +70,14 @@
import com.android.internal.logging.UiEventLogger;
import com.android.internal.policy.PhoneWindow;
import com.android.settingslib.applications.InterestingConfigChanges;
+import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.broadcast.BroadcastSender;
import com.android.systemui.clipboardoverlay.ClipboardOverlayController;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.res.R;
import com.android.systemui.screenshot.TakeScreenshotService.RequestCallback;
-import com.android.systemui.screenshot.scroll.LongScreenshotActivity;
-import com.android.systemui.screenshot.scroll.LongScreenshotData;
-import com.android.systemui.screenshot.scroll.ScrollCaptureClient;
-import com.android.systemui.screenshot.scroll.ScrollCaptureController;
+import com.android.systemui.screenshot.scroll.ScrollCaptureExecutor;
import com.android.systemui.util.Assert;
import com.google.common.util.concurrent.ListenableFuture;
@@ -98,13 +86,13 @@
import dagger.assisted.AssistedFactory;
import dagger.assisted.AssistedInject;
+import kotlin.Unit;
+
import java.util.List;
-import java.util.concurrent.CancellationException;
-import java.util.concurrent.ExecutionException;
+import java.util.UUID;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
-import java.util.concurrent.Future;
import java.util.function.Consumer;
import javax.inject.Provider;
@@ -116,34 +104,6 @@
public class ScreenshotController {
private static final String TAG = logTag(ScreenshotController.class);
- private ScrollCaptureResponse mLastScrollCaptureResponse;
- private ListenableFuture<ScrollCaptureResponse> mLastScrollCaptureRequest;
-
- /**
- * 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.
- */
- static final IRemoteAnimationRunner.Stub SCREENSHOT_REMOTE_RUNNER =
- new IRemoteAnimationRunner.Stub() {
- @Override
- public void onAnimationStart(
- @WindowManager.TransitionOldType int transit,
- RemoteAnimationTarget[] apps,
- RemoteAnimationTarget[] wallpapers,
- RemoteAnimationTarget[] nonApps,
- final IRemoteAnimationFinishedCallback finishedCallback) {
- try {
- finishedCallback.onAnimationFinished();
- } catch (RemoteException e) {
- Log.e(TAG, "Error finishing screenshot remote animation", e);
- }
- }
-
- @Override
- public void onAnimationCancelled() {
- }
- };
-
/**
* POD used in the AsyncTask which saves an image in the background.
*/
@@ -169,6 +129,7 @@
public Notification.Action quickShareAction;
public UserHandle owner;
public String subject; // Title for sharing
+ public Long imageTime; // Time at which screenshot was saved
/**
* Used to reset the return data on error
@@ -178,6 +139,7 @@
smartActions = null;
quickShareAction = null;
subject = null;
+ imageTime = null;
}
}
@@ -216,17 +178,10 @@
// ScreenshotNotificationSmartActionsProvider.
static final String EXTRA_ACTION_TYPE = "android:screenshot_action_type";
static final String EXTRA_ID = "android:screenshot_id";
- static final String ACTION_TYPE_DELETE = "Delete";
- static final String ACTION_TYPE_SHARE = "Share";
- static final String ACTION_TYPE_EDIT = "Edit";
static final String EXTRA_SMART_ACTIONS_ENABLED = "android:smart_actions_enabled";
- static final String EXTRA_OVERRIDE_TRANSITION = "android:screenshot_override_transition";
static final String EXTRA_ACTION_INTENT = "android:screenshot_action_intent";
static final String EXTRA_ACTION_INTENT_FILLIN = "android:screenshot_action_intent_fillin";
- static final String SCREENSHOT_URI_ID = "android:screenshot_uri_id";
- static final String EXTRA_CANCEL_NOTIFICATION = "android:screenshot_cancel_notification";
- static final String EXTRA_DISALLOW_ENTER_PIP = "android:screenshot_disallow_enter_pip";
// From WizardManagerHelper.java
private static final String SETTINGS_SECURE_USER_SETUP_COMPLETE = "user_setup_complete";
@@ -245,23 +200,21 @@
private final Executor mMainExecutor;
private final ExecutorService mBgExecutor;
private final BroadcastSender mBroadcastSender;
+ private final BroadcastDispatcher mBroadcastDispatcher;
+ private final ActionExecutor mActionExecutor;
private final WindowManager mWindowManager;
private final WindowManager.LayoutParams mWindowLayoutParams;
- private final AccessibilityManager mAccessibilityManager;
@Nullable
private final ScreenshotSoundController mScreenshotSoundController;
- private final ScrollCaptureClient mScrollCaptureClient;
private final PhoneWindow mWindow;
private final DisplayManager mDisplayManager;
private final int mDisplayId;
- private final ScrollCaptureController mScrollCaptureController;
- private final LongScreenshotData mLongScreenshotHolder;
- private final boolean mIsLowRamDevice;
+ private final ScrollCaptureExecutor mScrollCaptureExecutor;
private final ScreenshotNotificationSmartActionsProvider
mScreenshotNotificationSmartActionsProvider;
private final TimeoutHandler mScreenshotHandler;
- private final ActionIntentExecutor mActionExecutor;
+ private final ActionIntentExecutor mActionIntentExecutor;
private final UserManager mUserManager;
private final AssistContentRequester mAssistContentRequester;
@@ -270,13 +223,11 @@
private SaveImageInBackgroundTask mSaveInBgTask;
private boolean mScreenshotTakenInPortrait;
private boolean mBlockAttach;
-
- private ScreenshotActionsProvider mActionsProvider;
-
private Animator mScreenshotAnimation;
private RequestCallback mCurrentRequestCallback;
+ private ScreenshotActionsProvider mActionsProvider;
private String mPackageName = "";
- private BroadcastReceiver mCopyBroadcastReceiver;
+ private final BroadcastReceiver mCopyBroadcastReceiver;
// When false, the screenshot is taken without showing the ui. Note that this only applies to
// external displays, as on the default one the UI should **always** be shown.
@@ -298,23 +249,24 @@
@AssistedInject
ScreenshotController(
Context context,
+ DisplayManager displayManager,
+ WindowManager windowManager,
FeatureFlags flags,
ScreenshotViewProxy.Factory viewProxyFactory,
ScreenshotActionsProvider.Factory actionsProviderFactory,
ScreenshotSmartActions screenshotSmartActions,
ScreenshotNotificationsController.Factory screenshotNotificationsControllerFactory,
- ScrollCaptureClient scrollCaptureClient,
UiEventLogger uiEventLogger,
ImageExporter imageExporter,
ImageCapture imageCapture,
@Main Executor mainExecutor,
- ScrollCaptureController scrollCaptureController,
- LongScreenshotData longScreenshotHolder,
- ActivityManager activityManager,
+ ScrollCaptureExecutor scrollCaptureExecutor,
TimeoutHandler timeoutHandler,
BroadcastSender broadcastSender,
+ BroadcastDispatcher broadcastDispatcher,
ScreenshotNotificationSmartActionsProvider screenshotNotificationSmartActionsProvider,
- ActionIntentExecutor actionExecutor,
+ ActionIntentExecutor actionIntentExecutor,
+ ActionExecutor.Factory actionExecutorFactory,
UserManager userManager,
AssistContentRequester assistContentRequester,
MessageContainerController messageContainerController,
@@ -323,36 +275,34 @@
@Assisted boolean showUIOnExternalDisplay
) {
mScreenshotSmartActions = screenshotSmartActions;
+ mActionsProviderFactory = actionsProviderFactory;
mNotificationsController = screenshotNotificationsControllerFactory.create(displayId);
- mScrollCaptureClient = scrollCaptureClient;
mUiEventLogger = uiEventLogger;
mImageExporter = imageExporter;
mImageCapture = imageCapture;
mMainExecutor = mainExecutor;
- mScrollCaptureController = scrollCaptureController;
- mLongScreenshotHolder = longScreenshotHolder;
- mIsLowRamDevice = activityManager.isLowRamDevice();
+ mScrollCaptureExecutor = scrollCaptureExecutor;
mScreenshotNotificationSmartActionsProvider = screenshotNotificationSmartActionsProvider;
mBgExecutor = Executors.newSingleThreadExecutor();
mBroadcastSender = broadcastSender;
+ mBroadcastDispatcher = broadcastDispatcher;
mScreenshotHandler = timeoutHandler;
mScreenshotHandler.setDefaultTimeoutMillis(SCREENSHOT_CORNER_DEFAULT_TIMEOUT_MILLIS);
mDisplayId = displayId;
- mDisplayManager = requireNonNull(context.getSystemService(DisplayManager.class));
+ mDisplayManager = displayManager;
+ mWindowManager = windowManager;
final Context displayContext = context.createDisplayContext(getDisplay());
mContext = (WindowContext) displayContext.createWindowContext(TYPE_SCREENSHOT, null);
- mWindowManager = mContext.getSystemService(WindowManager.class);
mFlags = flags;
- mActionExecutor = actionExecutor;
+ mActionIntentExecutor = actionIntentExecutor;
mUserManager = userManager;
mMessageContainerController = messageContainerController;
mAssistContentRequester = assistContentRequester;
mViewProxy = viewProxyFactory.getProxy(mContext, mDisplayId);
- mActionsProviderFactory = actionsProviderFactory;
mScreenshotHandler.setOnTimeoutRunnable(() -> {
if (DEBUG_UI) {
@@ -361,8 +311,6 @@
mViewProxy.requestDismissal(SCREENSHOT_INTERACTION_TIMEOUT);
});
- mAccessibilityManager = AccessibilityManager.getInstance(mContext);
-
// Setup the window that we are going to use
mWindowLayoutParams = FloatingWindowUtil.getFloatingWindowParams();
mWindowLayoutParams.setTitle("ScreenshotAnimation");
@@ -373,6 +321,12 @@
mConfigChanges.applyNewConfig(context.getResources());
reloadAssets();
+ mActionExecutor = actionExecutorFactory.create(mWindow, mViewProxy.getScreenshotPreview(),
+ () -> {
+ requestDismissal(null);
+ return Unit.INSTANCE;
+ });
+
// Sound is only reproduced from the controller of the default display.
if (displayId == Display.DEFAULT_DISPLAY) {
mScreenshotSoundController = screenshotSoundController.get();
@@ -388,9 +342,9 @@
}
}
};
- mContext.registerReceiver(mCopyBroadcastReceiver, new IntentFilter(
- ClipboardOverlayController.COPY_OVERLAY_ACTION),
- ClipboardOverlayController.SELF_PERMISSION, null, Context.RECEIVER_NOT_EXPORTED);
+ mBroadcastDispatcher.registerReceiver(mCopyBroadcastReceiver, new IntentFilter(
+ ClipboardOverlayController.COPY_OVERLAY_ACTION), null, null,
+ Context.RECEIVER_NOT_EXPORTED, ClipboardOverlayController.SELF_PERMISSION);
mShowUIOnExternalDisplay = showUIOnExternalDisplay;
}
@@ -448,8 +402,23 @@
return;
}
- saveScreenshotInWorkerThread(screenshot.getUserHandle(), finisher,
- this::showUiOnActionsReady, this::showUiOnQuickShareActionReady);
+ if (screenshotShelfUi()) {
+ final UUID requestId = UUID.randomUUID();
+ final String screenshotId = String.format("Screenshot_%s", requestId);
+ mActionsProvider = mActionsProviderFactory.create(
+ screenshot, screenshotId, mActionExecutor);
+ saveScreenshotInBackground(screenshot, requestId, finisher);
+
+ if (screenshot.getTaskId() >= 0) {
+ mAssistContentRequester.requestAssistContent(screenshot.getTaskId(),
+ assistContent -> {
+ mActionsProvider.onAssistContentAvailable(assistContent);
+ });
+ }
+ } else {
+ saveScreenshotInWorkerThread(screenshot.getUserHandle(), finisher,
+ this::showUiOnActionsReady, this::showUiOnQuickShareActionReady);
+ }
// The window is focusable by default
setWindowFocusable(true);
@@ -484,7 +453,9 @@
// ignore system bar insets for the purpose of window layout
mWindow.getDecorView().setOnApplyWindowInsetsListener(
(v, insets) -> WindowInsets.CONSUMED);
- mScreenshotHandler.cancelTimeout(); // restarted after animation
+ if (!screenshotShelfUi()) {
+ mScreenshotHandler.cancelTimeout(); // restarted after animation
+ }
}
private boolean shouldShowUi() {
@@ -504,11 +475,6 @@
mViewProxy.reset();
- if (screenshotShelfUi()) {
- mActionsProvider = mActionsProviderFactory.create(mContext, screenshot.getUserHandle(),
- ((ScreenshotActionsProvider.ScreenshotActionsCallback) mViewProxy));
- }
-
if (mViewProxy.isAttachedToWindow()) {
// if we didn't already dismiss for another reason
if (!mViewProxy.isDismissing()) {
@@ -536,7 +502,11 @@
}
boolean isPendingSharedTransition() {
- return mViewProxy.isPendingSharedTransition();
+ if (screenshotShelfUi()) {
+ return mActionExecutor.isPendingSharedTransition();
+ } else {
+ return mViewProxy.isPendingSharedTransition();
+ }
}
// Any cleanup needed when the service is being destroyed.
@@ -555,7 +525,7 @@
* Release the constructed window context.
*/
private void releaseContext() {
- mContext.unregisterReceiver(mCopyBroadcastReceiver);
+ mBroadcastDispatcher.unregisterReceiver(mCopyBroadcastReceiver);
mContext.release();
}
@@ -584,8 +554,9 @@
@Override
public void onAction(Intent intent, UserHandle owner, boolean overrideTransition) {
- mActionExecutor.launchIntentAsync(
- intent, createWindowTransition(), owner, overrideTransition);
+ Pair<ActivityOptions, ExitTransitionCoordinator> exit = createWindowTransition();
+ mActionIntentExecutor.launchIntentAsync(
+ intent, owner, overrideTransition, exit.first, exit.second);
}
@Override
@@ -603,7 +574,7 @@
if (DEBUG_WINDOW) {
Log.d(TAG, "setContentView: " + mViewProxy.getView());
}
- setContentView(mViewProxy.getView());
+ mWindow.setContentView(mViewProxy.getView());
}
private void enqueueScrollCaptureRequest(UserHandle owner) {
@@ -622,9 +593,8 @@
mViewProxy.hideScrollChip();
// Delay scroll capture eval a bit to allow the underlying activity
// to set up in the new orientation.
- mScreenshotHandler.postDelayed(() -> {
- requestScrollCapture(owner);
- }, 150);
+ mScreenshotHandler.postDelayed(
+ () -> requestScrollCapture(owner), 150);
mViewProxy.updateInsets(
mWindowManager.getCurrentWindowMetrics().getWindowInsets());
// Screenshot animation calculations won't be valid anymore,
@@ -647,120 +617,51 @@
}
private void requestScrollCapture(UserHandle owner) {
- if (!allowLongScreenshots()) {
- Log.d(TAG, "Long screenshots not supported on this device");
+ mScrollCaptureExecutor.requestScrollCapture(
+ mDisplayId,
+ mWindow.getDecorView().getWindowToken(),
+ (response) -> {
+ mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_IMPRESSION,
+ 0, response.getPackageName());
+ if (screenshotShelfUi() && mActionsProvider != null) {
+ mActionsProvider.onScrollChipReady(
+ () -> onScrollButtonClicked(owner, response));
+ } else {
+ mViewProxy.showScrollChip(response.getPackageName(),
+ () -> onScrollButtonClicked(owner, response));
+ }
+ return Unit.INSTANCE;
+ }
+ );
+ }
+
+ private void onScrollButtonClicked(UserHandle owner, ScrollCaptureResponse response) {
+ if (DEBUG_INPUT) {
+ Log.d(TAG, "scroll chip tapped");
+ }
+ mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_REQUESTED, 0,
+ response.getPackageName());
+ Bitmap newScreenshot = mImageCapture.captureDisplay(mDisplayId, getFullScreenRect());
+ if (newScreenshot == null) {
+ Log.e(TAG, "Failed to capture current screenshot for scroll transition!");
return;
}
- mScrollCaptureClient.setHostWindowToken(mWindow.getDecorView().getWindowToken());
- if (mLastScrollCaptureRequest != null) {
- mLastScrollCaptureRequest.cancel(true);
- }
- final ListenableFuture<ScrollCaptureResponse> future = mScrollCaptureClient.request(
- mDisplayId);
- mLastScrollCaptureRequest = future;
- mLastScrollCaptureRequest.addListener(() ->
- onScrollCaptureResponseReady(future, owner), mMainExecutor);
+ // delay starting scroll capture to make sure scrim is up before the app moves
+ mViewProxy.prepareScrollingTransition(response, mScreenBitmap, newScreenshot,
+ mScreenshotTakenInPortrait, () -> executeBatchScrollCapture(response, owner));
}
- private void onScrollCaptureResponseReady(Future<ScrollCaptureResponse> responseFuture,
- UserHandle owner) {
- try {
- if (mLastScrollCaptureResponse != null) {
- mLastScrollCaptureResponse.close();
- mLastScrollCaptureResponse = null;
- }
- if (responseFuture.isCancelled()) {
- return;
- }
- mLastScrollCaptureResponse = responseFuture.get();
- if (!mLastScrollCaptureResponse.isConnected()) {
- // No connection means that the target window wasn't found
- // or that it cannot support scroll capture.
- Log.d(TAG, "ScrollCapture: " + mLastScrollCaptureResponse.getDescription() + " ["
- + mLastScrollCaptureResponse.getWindowTitle() + "]");
- return;
- }
- Log.d(TAG, "ScrollCapture: connected to window ["
- + mLastScrollCaptureResponse.getWindowTitle() + "]");
+ private void executeBatchScrollCapture(ScrollCaptureResponse response, UserHandle owner) {
+ mScrollCaptureExecutor.executeBatchScrollCapture(response,
+ () -> {
+ final Intent intent = ActionIntentCreator.INSTANCE.createLongScreenshotIntent(
+ owner, mContext);
+ mActionIntentExecutor.launchIntentAsync(intent, owner, true,
+ ActivityOptions.makeCustomAnimation(mContext, 0, 0), null);
- final ScrollCaptureResponse response = mLastScrollCaptureResponse;
- mViewProxy.showScrollChip(response.getPackageName(), /* onClick */ () -> {
- DisplayMetrics displayMetrics = new DisplayMetrics();
- getDisplay().getRealMetrics(displayMetrics);
- Bitmap newScreenshot = mImageCapture.captureDisplay(mDisplayId,
- new Rect(0, 0, displayMetrics.widthPixels, displayMetrics.heightPixels));
-
- if (newScreenshot != null) {
- // delay starting scroll capture to make sure scrim is up before the app moves
- mViewProxy.prepareScrollingTransition(
- response, mScreenBitmap, newScreenshot, mScreenshotTakenInPortrait,
- () -> runBatchScrollCapture(response, owner));
- } else {
- Log.wtf(TAG, "failed to capture current screenshot for scroll transition");
- }
- });
- } catch (InterruptedException | ExecutionException e) {
- Log.e(TAG, "requestScrollCapture failed", e);
- }
- }
-
- ListenableFuture<ScrollCaptureController.LongScreenshot> mLongScreenshotFuture;
-
- private void runBatchScrollCapture(ScrollCaptureResponse response, UserHandle owner) {
- // Clear the reference to prevent close() in dismissScreenshot
- mLastScrollCaptureResponse = null;
-
- if (mLongScreenshotFuture != null) {
- mLongScreenshotFuture.cancel(true);
- }
- mLongScreenshotFuture = mScrollCaptureController.run(response);
- mLongScreenshotFuture.addListener(() -> {
- ScrollCaptureController.LongScreenshot longScreenshot;
- try {
- longScreenshot = mLongScreenshotFuture.get();
- } catch (CancellationException e) {
- Log.e(TAG, "Long screenshot cancelled");
- return;
- } catch (InterruptedException | ExecutionException e) {
- Log.e(TAG, "Exception", e);
- mViewProxy.restoreNonScrollingUi();
- return;
- }
-
- if (longScreenshot.getHeight() == 0) {
- mViewProxy.restoreNonScrollingUi();
- return;
- }
-
- mLongScreenshotHolder.setLongScreenshot(longScreenshot);
- mLongScreenshotHolder.setTransitionDestinationCallback(
- (transitionDestination, onTransitionEnd) -> {
- mViewProxy.startLongScreenshotTransition(
- transitionDestination, onTransitionEnd,
- longScreenshot);
- // TODO: Do this via ActionIntentExecutor instead.
- mContext.closeSystemDialogs();
- }
- );
-
- final Intent intent = new Intent(mContext, LongScreenshotActivity.class);
- intent.putExtra(LongScreenshotActivity.EXTRA_SCREENSHOT_USER_HANDLE,
- owner);
- intent.setFlags(
- Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
-
- mContext.startActivity(intent,
- ActivityOptions.makeCustomAnimation(mContext, 0, 0).toBundle());
- RemoteAnimationAdapter runner = new RemoteAnimationAdapter(
- SCREENSHOT_REMOTE_RUNNER, 0, 0);
- try {
- WindowManagerGlobal.getWindowManagerService()
- .overridePendingAppTransitionRemote(runner,
- mDisplayId);
- } catch (Exception e) {
- Log.e(TAG, "Error overriding screenshot app transition", e);
- }
- }, mMainExecutor);
+ },
+ mViewProxy::restoreNonScrollingUi,
+ mViewProxy::startLongScreenshotTransition);
}
private void withWindowAttached(Runnable action) {
@@ -785,10 +686,6 @@
}
}
- private void setContentView(View contentView) {
- mWindow.setContentView(contentView);
- }
-
@MainThread
private void attachWindow() {
View decorView = mWindow.getDecorView();
@@ -900,28 +797,16 @@
public void onFinish() {
}
};
- Pair<ActivityOptions, ExitTransitionCoordinator> transition =
- ActivityOptions.startSharedElementAnimation(mWindow, callbacks, null,
- Pair.create(mViewProxy.getScreenshotPreview(),
- ChooserActivity.FIRST_IMAGE_PREVIEW_TRANSITION_NAME));
- return transition;
+ return ActivityOptions.startSharedElementAnimation(mWindow, callbacks, null,
+ Pair.create(mViewProxy.getScreenshotPreview(),
+ ChooserActivity.FIRST_IMAGE_PREVIEW_TRANSITION_NAME));
}
/** Reset screenshot view and then call onCompleteRunnable */
private void finishDismiss() {
Log.d(TAG, "finishDismiss");
- if (mLastScrollCaptureRequest != null) {
- mLastScrollCaptureRequest.cancel(true);
- mLastScrollCaptureRequest = null;
- }
- if (mLastScrollCaptureResponse != null) {
- mLastScrollCaptureResponse.close();
- mLastScrollCaptureResponse = null;
- }
- if (mLongScreenshotFuture != null) {
- mLongScreenshotFuture.cancel(true);
- }
+ mScrollCaptureExecutor.close();
if (mCurrentRequestCallback != null) {
mCurrentRequestCallback.onFinish();
mCurrentRequestCallback = null;
@@ -931,6 +816,35 @@
mScreenshotHandler.cancelTimeout();
}
+ private void saveScreenshotInBackground(
+ ScreenshotData screenshot, UUID requestId, Consumer<Uri> finisher) {
+ ListenableFuture<ImageExporter.Result> future = mImageExporter.export(mBgExecutor,
+ requestId, screenshot.getBitmap(), screenshot.getUserOrDefault(), mDisplayId);
+ future.addListener(() -> {
+ try {
+ ImageExporter.Result result = future.get();
+ Log.d(TAG, "Saved screenshot: " + result);
+ logScreenshotResultStatus(result.uri, screenshot.getUserHandle());
+ mScreenshotHandler.resetTimeout();
+ if (result.uri != null) {
+ mActionsProvider.setCompletedScreenshot(new ScreenshotSavedResult(
+ result.uri, screenshot.getUserOrDefault(), result.timestamp));
+ }
+ if (DEBUG_CALLBACK) {
+ Log.d(TAG, "finished background processing, Calling (Consumer<Uri>) "
+ + "finisher.accept(\"" + result.uri + "\"");
+ }
+ finisher.accept(result.uri);
+ } catch (Exception e) {
+ Log.d(TAG, "Failed to store screenshot", e);
+ if (DEBUG_CALLBACK) {
+ Log.d(TAG, "Calling (Consumer<Uri>) finisher.accept(null)");
+ }
+ finisher.accept(null);
+ }
+ }, mMainExecutor);
+ }
+
/**
* Creates a new worker thread and saves the screenshot to the media store.
*/
@@ -966,13 +880,12 @@
*/
private void showUiOnActionsReady(ScreenshotController.SavedImageData imageData) {
logSuccessOnActionsReady(imageData);
- if (DEBUG_UI) {
- Log.d(TAG, "Showing UI actions");
- }
-
mScreenshotHandler.resetTimeout();
if (imageData.uri != null) {
+ if (DEBUG_UI) {
+ Log.d(TAG, "Showing UI actions");
+ }
if (!imageData.owner.equals(Process.myUserHandle())) {
Log.d(TAG, "Screenshot saved to user " + imageData.owner + " as "
+ imageData.uri);
@@ -1020,20 +933,27 @@
/**
* Logs success/failure of the screenshot saving task, and shows an error if it failed.
*/
- private void logSuccessOnActionsReady(ScreenshotController.SavedImageData imageData) {
- if (imageData.uri == null) {
+ private void logScreenshotResultStatus(Uri uri, UserHandle owner) {
+ if (uri == null) {
mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_NOT_SAVED, 0, mPackageName);
mNotificationsController.notifyScreenshotError(
R.string.screenshot_failed_to_save_text);
} else {
mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SAVED, 0, mPackageName);
- if (mUserManager.isManagedProfile(imageData.owner.getIdentifier())) {
+ if (mUserManager.isManagedProfile(owner.getIdentifier())) {
mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SAVED_TO_WORK_PROFILE, 0,
mPackageName);
}
}
}
+ /**
+ * Logs success/failure of the screenshot saving task, and shows an error if it failed.
+ */
+ private void logSuccessOnActionsReady(ScreenshotController.SavedImageData imageData) {
+ logScreenshotResultStatus(imageData.uri, imageData.owner);
+ }
+
private boolean isUserSetupComplete(UserHandle owner) {
return Settings.Secure.getInt(mContext.createContextAsUser(owner, 0)
.getContentResolver(), SETTINGS_SECURE_USER_SETUP_COMPLETE, 0) == 1;
@@ -1070,10 +990,6 @@
return mDisplayManager.getDisplay(mDisplayId);
}
- private boolean allowLongScreenshots() {
- return !mIsLowRamDevice;
- }
-
private Rect getFullScreenRect() {
DisplayMetrics displayMetrics = new DisplayMetrics();
getDisplay().getRealMetrics(displayMetrics);
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotData.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotData.kt
index 92e933a..4fdd90b 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotData.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotData.kt
@@ -5,6 +5,7 @@
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
@@ -31,6 +32,10 @@
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) =
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSavedResult.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSavedResult.kt
new file mode 100644
index 0000000..5b6e7ac
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSavedResult.kt
@@ -0,0 +1,39 @@
+/*
+ * 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)"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotShelfViewProxy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotShelfViewProxy.kt
index 88bca95..6b9332b 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotShelfViewProxy.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotShelfViewProxy.kt
@@ -20,10 +20,8 @@
import android.animation.AnimatorListenerAdapter
import android.app.Notification
import android.content.Context
-import android.content.Intent
import android.graphics.Bitmap
import android.graphics.Rect
-import android.net.Uri
import android.view.KeyEvent
import android.view.LayoutInflater
import android.view.ScrollCaptureResponse
@@ -35,7 +33,6 @@
import com.android.internal.logging.UiEventLogger
import com.android.systemui.log.DebugLogger.debugLog
import com.android.systemui.res.R
-import com.android.systemui.screenshot.LogConfig.DEBUG_ACTIONS
import com.android.systemui.screenshot.LogConfig.DEBUG_DISMISS
import com.android.systemui.screenshot.LogConfig.DEBUG_INPUT
import com.android.systemui.screenshot.LogConfig.DEBUG_WINDOW
@@ -45,7 +42,6 @@
import com.android.systemui.screenshot.ui.ScreenshotAnimationController
import com.android.systemui.screenshot.ui.ScreenshotShelfView
import com.android.systemui.screenshot.ui.binder.ScreenshotShelfViewBinder
-import com.android.systemui.screenshot.ui.viewmodel.ActionButtonViewModel
import com.android.systemui.screenshot.ui.viewmodel.ScreenshotViewModel
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
@@ -59,7 +55,7 @@
private val viewModel: ScreenshotViewModel,
@Assisted private val context: Context,
@Assisted private val displayId: Int
-) : ScreenshotViewProxy, ScreenshotActionsProvider.ScreenshotActionsCallback {
+) : ScreenshotViewProxy {
override val view: ScreenshotShelfView =
LayoutInflater.from(context).inflate(R.layout.screenshot_shelf, null) as ScreenshotShelfView
override val screenshotPreview: View
@@ -77,8 +73,6 @@
override var isPendingSharedTransition = false
private val animationController = ScreenshotAnimationController(view)
- private var imageData: SavedImageData? = null
- private var runOnImageDataAcquired: ((SavedImageData) -> Unit)? = null
init {
ScreenshotShelfViewBinder.bind(view, viewModel, LayoutInflater.from(context))
@@ -91,9 +85,7 @@
override fun reset() {
animationController.cancel()
isPendingSharedTransition = false
- imageData = null
viewModel.reset()
- runOnImageDataAcquired = null
}
override fun updateInsets(insets: WindowInsets) {}
override fun updateOrientation(insets: WindowInsets) {}
@@ -104,12 +96,9 @@
override fun addQuickShareChip(quickShareAction: Notification.Action) {}
- override fun setChipIntents(data: SavedImageData) {
- imageData = data
- runOnImageDataAcquired?.invoke(data)
- }
+ override fun setChipIntents(imageData: SavedImageData) {}
- override fun requestDismissal(event: ScreenshotEvent) {
+ override fun requestDismissal(event: ScreenshotEvent?) {
debugLog(DEBUG_DISMISS) { "screenshot dismissal requested: $event" }
// If we're already animating out, don't restart the animation
@@ -117,7 +106,7 @@
debugLog(DEBUG_DISMISS) { "Already dismissing, ignoring duplicate command $event" }
return
}
- logger.log(event, 0, packageName)
+ event?.let { logger.log(it, 0, packageName) }
val animator = animationController.getExitAnimation()
animator.addListener(
object : AnimatorListenerAdapter() {
@@ -143,13 +132,18 @@
newScreenshot: Bitmap,
screenshotTakenInPortrait: Boolean,
onTransitionPrepared: Runnable,
- ) {}
+ ) {
+ onTransitionPrepared.run()
+ }
override fun startLongScreenshotTransition(
transitionDestination: Rect,
onTransitionEnd: Runnable,
longScreenshot: ScrollCaptureController.LongScreenshot
- ) {}
+ ) {
+ onTransitionEnd.run()
+ callbacks?.onDismiss()
+ }
override fun restoreNonScrollingUi() {}
@@ -219,41 +213,4 @@
interface Factory : ScreenshotViewProxy.Factory {
override fun getProxy(context: Context, displayId: Int): ScreenshotShelfViewProxy
}
-
- override fun setPreviewAction(overrideTransition: Boolean, retrieveIntent: (Uri) -> Intent) {
- viewModel.setPreviewAction {
- imageData?.let {
- val intent = retrieveIntent(it.uri)
- debugLog(DEBUG_ACTIONS) { "Preview tapped: $intent" }
- isPendingSharedTransition = true
- callbacks?.onAction(intent, it.owner, overrideTransition)
- }
- }
- }
-
- override fun addActions(actions: List<ScreenshotActionsProvider.ScreenshotAction>) {
- viewModel.addActions(
- actions.map { action ->
- ActionButtonViewModel(action.icon, action.text, action.description) {
- val actionRunnable =
- getActionRunnable(action.retrieveIntent, action.overrideTransition)
- imageData?.let { actionRunnable(it) }
- ?: run { runOnImageDataAcquired = actionRunnable }
- }
- }
- )
- }
-
- private fun getActionRunnable(
- retrieveIntent: (Uri) -> Intent,
- overrideTransition: Boolean
- ): (SavedImageData) -> Unit {
- val onClick: (SavedImageData) -> Unit = {
- val intent = retrieveIntent(it.uri)
- debugLog(DEBUG_ACTIONS) { "Action tapped: $intent" }
- isPendingSharedTransition = true
- callbacks!!.onAction(intent, it.owner, overrideTransition)
- }
- return onClick
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
index 65e8457..59e38a8 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
@@ -259,16 +259,8 @@
if (DEBUG_SCROLL) {
Log.d(TAG, "Showing Scroll option");
}
- mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_IMPRESSION, 0, packageName);
mScrollChip.setVisibility(VISIBLE);
- mScrollChip.setOnClickListener((v) -> {
- if (DEBUG_INPUT) {
- Log.d(TAG, "scroll chip tapped");
- }
- mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_REQUESTED, 0,
- packageName);
- onClick.run();
- });
+ mScrollChip.setOnClickListener((v) -> onClick.run());
}
@Override // ViewTreeObserver.OnComputeInternalInsetsListener
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotViewProxy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotViewProxy.kt
index 6be32a9..a4069d1 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotViewProxy.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotViewProxy.kt
@@ -46,7 +46,7 @@
fun createScreenshotDropInAnimation(screenRect: Rect, showFlash: Boolean): Animator
fun addQuickShareChip(quickShareAction: Notification.Action)
fun setChipIntents(imageData: ScreenshotController.SavedImageData)
- fun requestDismissal(event: ScreenshotEvent)
+ fun requestDismissal(event: ScreenshotEvent?)
fun showScrollChip(packageName: String, onClick: Runnable)
fun hideScrollChip()
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/SmartActionsProvider.kt b/packages/SystemUI/src/com/android/systemui/screenshot/SmartActionsProvider.kt
new file mode 100644
index 0000000..a895b30
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/SmartActionsProvider.kt
@@ -0,0 +1,285 @@
+/*
+ * 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.app.Notification
+import android.app.PendingIntent
+import android.content.ClipData
+import android.content.ClipDescription
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.graphics.Bitmap
+import android.net.Uri
+import android.os.Bundle
+import android.os.Process
+import android.os.SystemClock
+import android.os.UserHandle
+import android.provider.DeviceConfig
+import android.util.Log
+import com.android.internal.config.sysui.SystemUiDeviceConfigFlags
+import com.android.systemui.log.DebugLogger.debugLog
+import com.android.systemui.screenshot.LogConfig.DEBUG_ACTIONS
+import com.android.systemui.screenshot.ScreenshotNotificationSmartActionsProvider.ScreenshotSmartActionType.QUICK_SHARE_ACTION
+import com.android.systemui.screenshot.ScreenshotNotificationSmartActionsProvider.ScreenshotSmartActionType.REGULAR_SMART_ACTIONS
+import java.util.concurrent.CompletableFuture
+import java.util.concurrent.TimeUnit
+import java.util.concurrent.TimeoutException
+import javax.inject.Inject
+import kotlin.random.Random
+
+/**
+ * Handle requesting smart/quickshare actions from the provider and executing an action when the
+ * action futures complete.
+ */
+class SmartActionsProvider
+@Inject
+constructor(
+ private val context: Context,
+ private val smartActions: ScreenshotNotificationSmartActionsProvider,
+) {
+ /**
+ * Requests quick share action for a given screenshot.
+ *
+ * @param data the ScreenshotData request
+ * @param id the request id for the screenshot
+ * @param onAction callback to run when quick share action is returned
+ */
+ fun requestQuickShare(
+ data: ScreenshotData,
+ id: String,
+ onAction: (Notification.Action) -> Unit
+ ) {
+ val bitmap = data.bitmap ?: return
+ val component = data.topComponent ?: ComponentName("", "")
+ requestQuickShareAction(id, bitmap, component, data.getUserOrDefault()) { quickShare ->
+ onAction(quickShare)
+ }
+ }
+
+ /**
+ * Requests smart actions for a given screenshot.
+ *
+ * @param data the ScreenshotData request
+ * @param id the request id for the screenshot
+ * @param result the data for the saved image
+ * @param onActions callback to run when actions are returned
+ */
+ fun requestSmartActions(
+ data: ScreenshotData,
+ id: String,
+ result: ScreenshotSavedResult,
+ onActions: (List<Notification.Action>) -> Unit
+ ) {
+ val bitmap = data.bitmap ?: return
+ val component = data.topComponent ?: ComponentName("", "")
+ requestSmartActions(
+ id,
+ bitmap,
+ component,
+ data.getUserOrDefault(),
+ result.uri,
+ REGULAR_SMART_ACTIONS
+ ) { actions ->
+ onActions(actions)
+ }
+ }
+
+ /**
+ * Wraps the given quick share action in a broadcast intent.
+ *
+ * @param quickShare the quick share action to wrap
+ * @param uri the URI of the saved screenshot
+ * @param subject the subject/title for the screenshot
+ * @param id the request ID of the screenshot
+ * @return the pending intent with correct URI
+ */
+ fun wrapIntent(
+ quickShare: Notification.Action,
+ uri: Uri,
+ subject: String,
+ id: String
+ ): PendingIntent {
+ val wrappedIntent: Intent =
+ Intent(context, SmartActionsReceiver::class.java)
+ .putExtra(ScreenshotController.EXTRA_ACTION_INTENT, quickShare.actionIntent)
+ .putExtra(
+ ScreenshotController.EXTRA_ACTION_INTENT_FILLIN,
+ createFillInIntent(uri, subject)
+ )
+ .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)
+ val extras: Bundle = quickShare.extras
+ val actionType =
+ extras.getString(
+ ScreenshotNotificationSmartActionsProvider.ACTION_TYPE,
+ ScreenshotNotificationSmartActionsProvider.DEFAULT_ACTION_TYPE
+ )
+ // We only query for quick share actions when smart actions are enabled, so we can assert
+ // that it's true here.
+ wrappedIntent
+ .putExtra(ScreenshotController.EXTRA_ACTION_TYPE, actionType)
+ .putExtra(ScreenshotController.EXTRA_ID, id)
+ .putExtra(ScreenshotController.EXTRA_SMART_ACTIONS_ENABLED, true)
+ return PendingIntent.getBroadcast(
+ context,
+ Random.nextInt(),
+ wrappedIntent,
+ PendingIntent.FLAG_CANCEL_CURRENT or PendingIntent.FLAG_IMMUTABLE
+ )
+ }
+
+ private fun createFillInIntent(uri: Uri, subject: String): Intent {
+ val fillIn = Intent()
+ fillIn.setType("image/png")
+ fillIn.putExtra(Intent.EXTRA_STREAM, uri)
+ fillIn.putExtra(Intent.EXTRA_SUBJECT, subject)
+ // Include URI in ClipData also, so that grantPermission picks it up.
+ // We don't use setData here because some apps interpret this as "to:".
+ val clipData =
+ ClipData(ClipDescription("content", arrayOf("image/png")), ClipData.Item(uri))
+ fillIn.clipData = clipData
+ fillIn.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
+ return fillIn
+ }
+
+ private fun requestQuickShareAction(
+ id: String,
+ image: Bitmap,
+ component: ComponentName,
+ user: UserHandle,
+ timeoutMs: Long = 500,
+ onAction: (Notification.Action) -> Unit
+ ) {
+ requestSmartActions(id, image, component, user, null, QUICK_SHARE_ACTION, timeoutMs) {
+ it.firstOrNull()?.let { action -> onAction(action) }
+ }
+ }
+
+ private fun requestSmartActions(
+ id: String,
+ image: Bitmap,
+ component: ComponentName,
+ user: UserHandle,
+ uri: Uri?,
+ actionType: ScreenshotNotificationSmartActionsProvider.ScreenshotSmartActionType,
+ timeoutMs: Long = 500,
+ onActions: (List<Notification.Action>) -> Unit
+ ) {
+ val enabled = isSmartActionsEnabled(user)
+ debugLog(DEBUG_ACTIONS) {
+ ("getSmartActionsFuture id=$id, uri=$uri, provider=$smartActions, " +
+ "actionType=$actionType, smartActionsEnabled=$enabled, userHandle=$user")
+ }
+ if (!enabled) {
+ debugLog(DEBUG_ACTIONS) { "Screenshot Intelligence not enabled, returning empty list" }
+ onActions(listOf())
+ return
+ }
+ if (image.config != Bitmap.Config.HARDWARE) {
+ debugLog(DEBUG_ACTIONS) {
+ "Bitmap expected: Hardware, Bitmap found: ${image.config}. Returning empty list."
+ }
+ onActions(listOf())
+ return
+ }
+ val smartActionsFuture: CompletableFuture<List<Notification.Action>>
+ val startTimeMs = SystemClock.uptimeMillis()
+ try {
+ smartActionsFuture =
+ smartActions.getActions(id, uri, image, component, actionType, user)
+ } catch (e: Throwable) {
+ val waitTimeMs = SystemClock.uptimeMillis() - startTimeMs
+ debugLog(DEBUG_ACTIONS, error = e) {
+ "Failed to get future for screenshot notification smart actions."
+ }
+ notifyScreenshotOp(
+ id,
+ ScreenshotNotificationSmartActionsProvider.ScreenshotOp.REQUEST_SMART_ACTIONS,
+ ScreenshotNotificationSmartActionsProvider.ScreenshotOpStatus.ERROR,
+ waitTimeMs
+ )
+ onActions(listOf())
+ return
+ }
+ try {
+ val actions = smartActionsFuture.get(timeoutMs, TimeUnit.MILLISECONDS)
+ val waitTimeMs = SystemClock.uptimeMillis() - startTimeMs
+ debugLog(DEBUG_ACTIONS) {
+ ("Got ${actions.size} smart actions. Wait time: $waitTimeMs ms, " +
+ "actionType=$actionType")
+ }
+ notifyScreenshotOp(
+ id,
+ ScreenshotNotificationSmartActionsProvider.ScreenshotOp.WAIT_FOR_SMART_ACTIONS,
+ ScreenshotNotificationSmartActionsProvider.ScreenshotOpStatus.SUCCESS,
+ waitTimeMs
+ )
+ onActions(actions)
+ } catch (e: Throwable) {
+ val waitTimeMs = SystemClock.uptimeMillis() - startTimeMs
+ debugLog(DEBUG_ACTIONS, error = e) {
+ "Error getting smart actions. Wait time: $waitTimeMs ms, actionType=$actionType"
+ }
+ val status =
+ if (e is TimeoutException) {
+ ScreenshotNotificationSmartActionsProvider.ScreenshotOpStatus.TIMEOUT
+ } else {
+ ScreenshotNotificationSmartActionsProvider.ScreenshotOpStatus.ERROR
+ }
+ notifyScreenshotOp(
+ id,
+ ScreenshotNotificationSmartActionsProvider.ScreenshotOp.WAIT_FOR_SMART_ACTIONS,
+ status,
+ waitTimeMs
+ )
+ onActions(listOf())
+ }
+ }
+
+ private fun notifyScreenshotOp(
+ screenshotId: String,
+ op: ScreenshotNotificationSmartActionsProvider.ScreenshotOp,
+ status: ScreenshotNotificationSmartActionsProvider.ScreenshotOpStatus,
+ durationMs: Long
+ ) {
+ debugLog(DEBUG_ACTIONS) {
+ "$smartActions notifyOp: $op id=$screenshotId, status=$status, durationMs=$durationMs"
+ }
+ try {
+ smartActions.notifyOp(screenshotId, op, status, durationMs)
+ } catch (e: Throwable) {
+ 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()
+ val actionsEnabled =
+ DeviceConfig.getBoolean(
+ DeviceConfig.NAMESPACE_SYSTEMUI,
+ SystemUiDeviceConfigFlags.ENABLE_SCREENSHOT_NOTIFICATION_SMART_ACTIONS,
+ true
+ )
+ return !savingToOtherUser && actionsEnabled
+ }
+
+ companion object {
+ private const val TAG = "SmartActionsProvider"
+ private const val SCREENSHOT_SHARE_SUBJECT_TEMPLATE = "Screenshot (%s)"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/scroll/LongScreenshotActivity.java b/packages/SystemUI/src/com/android/systemui/screenshot/scroll/LongScreenshotActivity.java
index 1e1a577..706ac9c 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/scroll/LongScreenshotActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/scroll/LongScreenshotActivity.java
@@ -335,8 +335,8 @@
// TODO: Fix transition for work profile. Omitting it in the meantime.
mActionExecutor.launchIntentAsync(
ActionIntentCreator.INSTANCE.createEdit(uri, this),
- null,
- mScreenshotUserHandle, false);
+ mScreenshotUserHandle, false,
+ /* activityOptions */ null, /* transitionCoordinator */ null);
} else {
String editorPackage = getString(R.string.config_screenshotEditor);
Intent intent = new Intent(Intent.ACTION_EDIT);
@@ -363,7 +363,8 @@
private void doShare(Uri uri) {
Intent shareIntent = ActionIntentCreator.INSTANCE.createShare(uri);
- mActionExecutor.launchIntentAsync(shareIntent, null, mScreenshotUserHandle, false);
+ mActionExecutor.launchIntentAsync(shareIntent, mScreenshotUserHandle, false,
+ /* activityOptions */ null, /* transitionCoordinator */ null);
}
private void onClicked(View v) {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/scroll/ScrollCaptureExecutor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/scroll/ScrollCaptureExecutor.kt
new file mode 100644
index 0000000..6c4ee3e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/scroll/ScrollCaptureExecutor.kt
@@ -0,0 +1,165 @@
+/*
+ * 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.scroll
+
+import android.app.ActivityManager
+import android.graphics.Rect
+import android.os.IBinder
+import android.util.Log
+import android.view.ScrollCaptureResponse
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.screenshot.scroll.ScrollCaptureController.LongScreenshot
+import com.google.common.util.concurrent.ListenableFuture
+import java.util.concurrent.ExecutionException
+import java.util.concurrent.Executor
+import java.util.concurrent.Future
+import javax.inject.Inject
+
+class ScrollCaptureExecutor
+@Inject
+constructor(
+ activityManager: ActivityManager,
+ private val scrollCaptureClient: ScrollCaptureClient,
+ private val scrollCaptureController: ScrollCaptureController,
+ private val longScreenshotHolder: LongScreenshotData,
+ @Main private val mainExecutor: Executor
+) {
+ private val isLowRamDevice = activityManager.isLowRamDevice
+ private var lastScrollCaptureRequest: ListenableFuture<ScrollCaptureResponse>? = null
+ private var lastScrollCaptureResponse: ScrollCaptureResponse? = null
+ private var longScreenshotFuture: ListenableFuture<LongScreenshot>? = null
+
+ fun requestScrollCapture(
+ displayId: Int,
+ token: IBinder,
+ callback: (ScrollCaptureResponse) -> Unit
+ ) {
+ if (!allowLongScreenshots()) {
+ Log.d(TAG, "Long screenshots not supported on this device")
+ return
+ }
+ scrollCaptureClient.setHostWindowToken(token)
+ lastScrollCaptureRequest?.cancel(true)
+ val scrollRequest =
+ scrollCaptureClient.request(displayId).apply {
+ addListener(
+ { onScrollCaptureResponseReady(this)?.let { callback.invoke(it) } },
+ mainExecutor
+ )
+ }
+ lastScrollCaptureRequest = scrollRequest
+ }
+
+ fun interface ScrollTransitionReady {
+ fun onTransitionReady(
+ destRect: Rect,
+ onTransitionEnd: Runnable,
+ longScreenshot: LongScreenshot
+ )
+ }
+
+ fun executeBatchScrollCapture(
+ response: ScrollCaptureResponse,
+ onCaptureComplete: Runnable,
+ onFailure: Runnable,
+ transition: ScrollTransitionReady,
+ ) {
+ // Clear the reference to prevent close() on reset
+ lastScrollCaptureResponse = null
+ longScreenshotFuture?.cancel(true)
+ longScreenshotFuture =
+ scrollCaptureController.run(response).apply {
+ addListener(
+ {
+ getLongScreenshotChecked(this, onFailure)?.let {
+ longScreenshotHolder.setLongScreenshot(it)
+ longScreenshotHolder.setTransitionDestinationCallback {
+ destinationRect: Rect,
+ onTransitionEnd: Runnable ->
+ transition.onTransitionReady(destinationRect, onTransitionEnd, it)
+ }
+ onCaptureComplete.run()
+ }
+ },
+ mainExecutor
+ )
+ }
+ }
+
+ fun close() {
+ lastScrollCaptureRequest?.cancel(true)
+ lastScrollCaptureRequest = null
+ lastScrollCaptureResponse?.close()
+ lastScrollCaptureResponse = null
+ longScreenshotFuture?.cancel(true)
+ }
+
+ private fun getLongScreenshotChecked(
+ future: ListenableFuture<LongScreenshot>,
+ onFailure: Runnable
+ ): LongScreenshot? {
+ var longScreenshot: LongScreenshot? = null
+ runCatching { longScreenshot = future.get() }
+ .onFailure {
+ Log.e(TAG, "Caught exception", it)
+ onFailure.run()
+ return null
+ }
+ if (longScreenshot?.height != 0) {
+ return longScreenshot
+ }
+ onFailure.run()
+ return null
+ }
+
+ private fun onScrollCaptureResponseReady(
+ responseFuture: Future<ScrollCaptureResponse>
+ ): ScrollCaptureResponse? {
+ try {
+ lastScrollCaptureResponse?.close()
+ lastScrollCaptureResponse = null
+ if (responseFuture.isCancelled) {
+ return null
+ }
+ val captureResponse = responseFuture.get().apply { lastScrollCaptureResponse = this }
+ if (!captureResponse.isConnected) {
+ // No connection means that the target window wasn't found
+ // or that it cannot support scroll capture.
+ Log.d(
+ TAG,
+ "ScrollCapture: ${captureResponse.description} [${captureResponse.windowTitle}]"
+ )
+ return null
+ }
+ Log.d(TAG, "ScrollCapture: connected to window [${captureResponse.windowTitle}]")
+ return captureResponse
+ } catch (e: InterruptedException) {
+ Log.e(TAG, "requestScrollCapture interrupted", e)
+ } catch (e: ExecutionException) {
+ Log.e(TAG, "requestScrollCapture failed", e)
+ }
+ return null
+ }
+
+ private fun allowLongScreenshots(): Boolean {
+ return !isLowRamDevice
+ }
+
+ private companion object {
+ private const val TAG = "ScrollCaptureExecutor"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ui/binder/ActionButtonViewBinder.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ui/binder/ActionButtonViewBinder.kt
index c7fe3f6..a6374ae 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ui/binder/ActionButtonViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ui/binder/ActionButtonViewBinder.kt
@@ -36,6 +36,7 @@
} else {
view.setOnClickListener(null)
}
+ view.tag = viewModel.id
view.contentDescription = viewModel.description
view.visibility = View.VISIBLE
view.alpha = 1f
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ui/binder/ScreenshotShelfViewBinder.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ui/binder/ScreenshotShelfViewBinder.kt
index d878200..32e9296 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ui/binder/ScreenshotShelfViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ui/binder/ScreenshotShelfViewBinder.kt
@@ -60,7 +60,7 @@
}
launch {
viewModel.previewAction.collect { onClick ->
- previewView.setOnClickListener { onClick?.run() }
+ previewView.setOnClickListener { onClick?.invoke() }
}
}
launch {
@@ -70,21 +70,36 @@
.requireViewById<View>(R.id.actions_container_background)
.visibility = View.VISIBLE
}
- val viewPool = actionsContainer.children.toList()
- actionsContainer.removeAllViews()
- val actionButtons =
- List(actions.size) {
- viewPool.getOrElse(it) {
+
+ // Remove any buttons not in the new list, then do another pass to add
+ // any new actions and update any that are already there.
+ // This assumes that actions can never change order and that each action
+ // ID is unique.
+ val newIds = actions.map { it.id }
+
+ for (view in actionsContainer.children.toList()) {
+ if (view.tag !in newIds) {
+ actionsContainer.removeView(view)
+ }
+ }
+
+ for ((index, action) in actions.withIndex()) {
+ val currentView: View? = actionsContainer.getChildAt(index)
+ if (action.id == currentView?.tag) {
+ // Same ID, update the display
+ ActionButtonViewBinder.bind(currentView, action)
+ } else {
+ // Different ID. Removals have already happened so this must
+ // mean that the new action must be inserted here.
+ val actionButton =
layoutInflater.inflate(
R.layout.overlay_action_chip,
actionsContainer,
false
)
- }
+ actionsContainer.addView(actionButton, index)
+ ActionButtonViewBinder.bind(actionButton, action)
}
- actionButtons.zip(actions).forEach {
- actionsContainer.addView(it.first)
- ActionButtonViewBinder.bind(it.first, it.second)
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ui/viewmodel/ActionButtonViewModel.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ui/viewmodel/ActionButtonViewModel.kt
index 05bfed1..97b24c1 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ui/viewmodel/ActionButtonViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ui/viewmodel/ActionButtonViewModel.kt
@@ -22,5 +22,13 @@
val icon: Drawable?,
val name: CharSequence?,
val description: CharSequence,
- val onClicked: (() -> Unit)?
-)
+ val onClicked: (() -> Unit)?,
+) {
+ val id: Int = getId()
+
+ companion object {
+ private var nextId = 0
+
+ private fun getId() = nextId.also { nextId += 1 }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ui/viewmodel/ScreenshotViewModel.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ui/viewmodel/ScreenshotViewModel.kt
index dc61d1e..ddfa69b 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ui/viewmodel/ScreenshotViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ui/viewmodel/ScreenshotViewModel.kt
@@ -24,8 +24,8 @@
class ScreenshotViewModel(private val accessibilityManager: AccessibilityManager) {
private val _preview = MutableStateFlow<Bitmap?>(null)
val preview: StateFlow<Bitmap?> = _preview
- private val _previewAction = MutableStateFlow<Runnable?>(null)
- val previewAction: StateFlow<Runnable?> = _previewAction
+ private val _previewAction = MutableStateFlow<(() -> Unit)?>(null)
+ val previewAction: StateFlow<(() -> Unit)?> = _previewAction
private val _actions = MutableStateFlow(emptyList<ActionButtonViewModel>())
val actions: StateFlow<List<ActionButtonViewModel>> = _actions
val showDismissButton: Boolean
@@ -35,8 +35,14 @@
_preview.value = bitmap
}
- fun setPreviewAction(runnable: Runnable) {
- _previewAction.value = runnable
+ fun setPreviewAction(onClick: () -> Unit) {
+ _previewAction.value = onClick
+ }
+
+ fun addAction(action: ActionButtonViewModel) {
+ val actionList = _actions.value.toMutableList()
+ actionList.add(action)
+ _actions.value = actionList
}
fun addActions(actions: List<ActionButtonViewModel>) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionExecutorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionExecutorTest.kt
new file mode 100644
index 0000000..5e7d8fb
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionExecutorTest.kt
@@ -0,0 +1,85 @@
+/*
+ * 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.app.PendingIntent
+import android.content.Intent
+import android.os.Bundle
+import android.os.UserHandle
+import android.testing.AndroidTestingRunner
+import android.view.View
+import android.view.Window
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import kotlin.test.Test
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestCoroutineScheduler
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.any
+import org.mockito.kotlin.capture
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.verifyBlocking
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class ActionExecutorTest : SysuiTestCase() {
+ private val scheduler = TestCoroutineScheduler()
+ private val mainDispatcher = StandardTestDispatcher(scheduler)
+ private val testScope = TestScope(mainDispatcher)
+
+ private val intentExecutor = mock<ActionIntentExecutor>()
+ private val window = mock<Window>()
+ private val view = mock<View>()
+ private val onDismiss = mock<(() -> Unit)>()
+ private val pendingIntent = mock<PendingIntent>()
+
+ private lateinit var actionExecutor: ActionExecutor
+
+ @Test
+ fun startSharedTransition_callsLaunchIntent() = runTest {
+ actionExecutor = createActionExecutor()
+
+ actionExecutor.startSharedTransition(Intent(Intent.ACTION_EDIT), UserHandle.CURRENT, true)
+ scheduler.advanceUntilIdle()
+
+ val intentCaptor = argumentCaptor<Intent>()
+ verifyBlocking(intentExecutor) {
+ launchIntent(capture(intentCaptor), eq(UserHandle.CURRENT), eq(true), any(), any())
+ }
+ assertThat(intentCaptor.value.action).isEqualTo(Intent.ACTION_EDIT)
+ }
+
+ @Test
+ fun sendPendingIntent_dismisses() = runTest {
+ actionExecutor = createActionExecutor()
+
+ actionExecutor.sendPendingIntent(pendingIntent)
+
+ verify(pendingIntent).send(any(Bundle::class.java))
+ verify(onDismiss).invoke()
+ }
+
+ private fun createActionExecutor(): ActionExecutor {
+ return ActionExecutor(intentExecutor, testScope, window, view, onDismiss)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionIntentExecutorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionIntentExecutorTest.kt
index 0c32470..5e53fe1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionIntentExecutorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionIntentExecutorTest.kt
@@ -64,7 +64,7 @@
val intent = Intent(Intent.ACTION_EDIT).apply { flags = Intent.FLAG_ACTIVITY_NEW_TASK }
val userHandle = myUserHandle()
- actionIntentExecutor.launchIntent(intent, null, userHandle, false)
+ actionIntentExecutor.launchIntent(intent, userHandle, false, null, null)
scheduler.advanceUntilIdle()
verify(activityManagerWrapper)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/DefaultScreenshotActionsProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/DefaultScreenshotActionsProviderTest.kt
new file mode 100644
index 0000000..bde821b
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/DefaultScreenshotActionsProviderTest.kt
@@ -0,0 +1,164 @@
+/*
+ * 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.app.Notification
+import android.app.PendingIntent
+import android.content.Intent
+import android.net.Uri
+import android.os.Process
+import android.os.UserHandle
+import android.testing.AndroidTestingRunner
+import android.view.accessibility.AccessibilityManager
+import androidx.test.filters.SmallTest
+import com.android.internal.logging.UiEventLogger
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.clipboardoverlay.EditTextActivity
+import com.android.systemui.res.R
+import com.android.systemui.screenshot.ui.viewmodel.ScreenshotViewModel
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.capture
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import kotlin.test.Test
+import kotlinx.coroutines.test.runTest
+import org.junit.Assert.assertNotNull
+import org.junit.Before
+import org.junit.runner.RunWith
+import org.mockito.Mockito.verifyNoMoreInteractions
+import org.mockito.kotlin.any
+import org.mockito.kotlin.never
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class DefaultScreenshotActionsProviderTest : SysuiTestCase() {
+ private val actionExecutor = mock<ActionExecutor>()
+ private val accessibilityManager = mock<AccessibilityManager>()
+ private val uiEventLogger = mock<UiEventLogger>()
+ private val smartActionsProvider = mock<SmartActionsProvider>()
+
+ private val request = ScreenshotData.forTesting()
+ private val validResult = ScreenshotSavedResult(Uri.EMPTY, Process.myUserHandle(), 0)
+
+ private lateinit var viewModel: ScreenshotViewModel
+ private lateinit var actionsProvider: ScreenshotActionsProvider
+
+ @Before
+ fun setUp() {
+ viewModel = ScreenshotViewModel(accessibilityManager)
+ request.userHandle = UserHandle.OWNER
+ }
+
+ @Test
+ fun previewActionAccessed_beforeScreenshotCompleted_doesNothing() {
+ actionsProvider = createActionsProvider()
+
+ assertNotNull(viewModel.previewAction.value)
+ viewModel.previewAction.value!!.invoke()
+ verifyNoMoreInteractions(actionExecutor)
+ }
+
+ @Test
+ fun actionButtonsAccessed_beforeScreenshotCompleted_doesNothing() {
+ actionsProvider = createActionsProvider()
+
+ assertThat(viewModel.actions.value.size).isEqualTo(2)
+ val firstAction = viewModel.actions.value[0]
+ assertThat(firstAction.onClicked).isNotNull()
+ val secondAction = viewModel.actions.value[1]
+ assertThat(secondAction.onClicked).isNotNull()
+ firstAction.onClicked!!.invoke()
+ secondAction.onClicked!!.invoke()
+ verifyNoMoreInteractions(actionExecutor)
+ }
+
+ @Test
+ fun actionAccessed_withResult_launchesIntent() = runTest {
+ actionsProvider = createActionsProvider()
+
+ actionsProvider.setCompletedScreenshot(validResult)
+ viewModel.actions.value[0].onClicked!!.invoke()
+
+ verify(uiEventLogger).log(eq(ScreenshotEvent.SCREENSHOT_EDIT_TAPPED), eq(0), eq(""))
+ val intentCaptor = argumentCaptor<Intent>()
+ verify(actionExecutor)
+ .startSharedTransition(capture(intentCaptor), eq(Process.myUserHandle()), eq(true))
+ assertThat(intentCaptor.value.action).isEqualTo(Intent.ACTION_EDIT)
+ }
+
+ @Test
+ fun actionAccessed_whilePending_launchesMostRecentAction() = runTest {
+ actionsProvider = createActionsProvider()
+
+ viewModel.actions.value[0].onClicked!!.invoke()
+ viewModel.previewAction.value!!.invoke()
+ viewModel.actions.value[1].onClicked!!.invoke()
+ actionsProvider.setCompletedScreenshot(validResult)
+
+ verify(uiEventLogger).log(eq(ScreenshotEvent.SCREENSHOT_SHARE_TAPPED), eq(0), eq(""))
+ val intentCaptor = argumentCaptor<Intent>()
+ verify(actionExecutor)
+ .startSharedTransition(capture(intentCaptor), eq(Process.myUserHandle()), eq(false))
+ assertThat(intentCaptor.value.action).isEqualTo(Intent.ACTION_CHOOSER)
+ }
+
+ @Test
+ fun quickShareTapped_wrapsAndSendsIntent() = runTest {
+ val quickShare =
+ Notification.Action(
+ R.drawable.ic_screenshot_edit,
+ "TestQuickShare",
+ PendingIntent.getActivity(
+ context,
+ 0,
+ Intent(context, EditTextActivity::class.java),
+ PendingIntent.FLAG_MUTABLE
+ )
+ )
+ whenever(smartActionsProvider.requestQuickShare(any(), any(), any())).then {
+ (it.getArgument(2) as ((Notification.Action) -> Unit)).invoke(quickShare)
+ }
+ whenever(smartActionsProvider.wrapIntent(any(), any(), any(), any())).thenAnswer {
+ (it.getArgument(0) as Notification.Action).actionIntent
+ }
+ actionsProvider = createActionsProvider()
+
+ viewModel.actions.value[2].onClicked?.invoke()
+ verify(uiEventLogger, never())
+ .log(eq(ScreenshotEvent.SCREENSHOT_SMART_ACTION_TAPPED), any(), any())
+ verify(smartActionsProvider, never()).wrapIntent(any(), any(), any(), any())
+ actionsProvider.setCompletedScreenshot(validResult)
+ verify(smartActionsProvider)
+ .wrapIntent(eq(quickShare), eq(validResult.uri), eq(validResult.subject), eq("testid"))
+ verify(uiEventLogger).log(eq(ScreenshotEvent.SCREENSHOT_SMART_ACTION_TAPPED), eq(0), eq(""))
+ }
+
+ private fun createActionsProvider(): ScreenshotActionsProvider {
+ return DefaultScreenshotActionsProvider(
+ context,
+ viewModel,
+ smartActionsProvider,
+ uiEventLogger,
+ request,
+ "testid",
+ actionExecutor
+ )
+ }
+}
diff --git a/services/core/java/com/android/server/biometrics/Utils.java b/services/core/java/com/android/server/biometrics/Utils.java
index f51b62d..4af30a9 100644
--- a/services/core/java/com/android/server/biometrics/Utils.java
+++ b/services/core/java/com/android/server/biometrics/Utils.java
@@ -89,11 +89,23 @@
return true;
}
- /** If virtualized biometrics are supported (requires debug build). */
- public static boolean isVirtualEnabled(@NonNull Context context) {
+ /** If virtualized fingerprint sensor is supported. */
+ public static boolean isFingerprintVirtualEnabled(@NonNull Context context) {
return Build.isDebuggable()
- && Settings.Secure.getIntForUser(context.getContentResolver(),
- Settings.Secure.BIOMETRIC_VIRTUAL_ENABLED, 0, UserHandle.USER_CURRENT) == 1;
+ && (Settings.Secure.getIntForUser(context.getContentResolver(),
+ Settings.Secure.BIOMETRIC_FINGERPRINT_VIRTUAL_ENABLED, 0,
+ UserHandle.USER_CURRENT) == 1
+ || Settings.Secure.getIntForUser(context.getContentResolver(),
+ Settings.Secure.BIOMETRIC_VIRTUAL_ENABLED, 0, UserHandle.USER_CURRENT) == 1);
+ }
+
+ /** If virtualized face sensor is supported. */
+ public static boolean isFaceVirtualEnabled(@NonNull Context context) {
+ return Build.isDebuggable()
+ && (Settings.Secure.getIntForUser(context.getContentResolver(),
+ Settings.Secure.BIOMETRIC_FACE_VIRTUAL_ENABLED, 0, UserHandle.USER_CURRENT) == 1
+ || Settings.Secure.getIntForUser(context.getContentResolver(),
+ Settings.Secure.BIOMETRIC_VIRTUAL_ENABLED, 0, UserHandle.USER_CURRENT) == 1);
}
/**
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
index 7ee2a7a..f4c34ca 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
@@ -748,7 +748,7 @@
final Pair<String, SensorProps[]> virtualSensorProps = faceSensorConfigurations
.getSensorPairForInstance("virtual");
- if (Utils.isVirtualEnabled(getContext())) {
+ if (Utils.isFaceVirtualEnabled(getContext())) {
if (virtualSensorProps != null) {
return virtualSensorProps;
} else {
@@ -778,7 +778,7 @@
}
final int virtualAt = aidlInstances.indexOf("virtual");
- if (Flags.faceVhalFeature() && Utils.isVirtualEnabled(getContext())) {
+ if (Flags.faceVhalFeature() && Utils.isFaceVirtualEnabled(getContext())) {
if (virtualAt != -1) {
//only virtual instance should be returned
Slog.i(TAG, "virtual hal is used");
@@ -920,7 +920,7 @@
void syncEnrollmentsNow() {
Utils.checkPermissionOrShell(getContext(), MANAGE_FACE);
- if (Flags.faceVhalFeature() && Utils.isVirtualEnabled(getContext())) {
+ if (Flags.faceVhalFeature() && Utils.isFaceVirtualEnabled(getContext())) {
Slog.i(TAG, "Sync virtual enrollments");
final int userId = ActivityManager.getCurrentUser();
for (ServiceProvider provider : mRegistry.getProviders()) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
index 1ba1213..9785691 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
@@ -1136,7 +1136,7 @@
final Pair<String, SensorProps[]> virtualSensorPropsPair = fingerprintSensorConfigurations
.getSensorPairForInstance("virtual");
- if (Utils.isVirtualEnabled(getContext())) {
+ if (Utils.isFingerprintVirtualEnabled(getContext())) {
if (virtualSensorPropsPair != null) {
return virtualSensorPropsPair;
} else {
@@ -1160,7 +1160,7 @@
}
final int virtualAt = aidlInstances.indexOf("virtual");
- if (Utils.isVirtualEnabled(getContext())) {
+ if (Utils.isFingerprintVirtualEnabled(getContext())) {
if (virtualAt != -1) {
//only virtual instance should be returned
Slog.i(TAG, "virtual hal is used");
@@ -1286,7 +1286,7 @@
void syncEnrollmentsNow() {
Utils.checkPermissionOrShell(getContext(), MANAGE_FINGERPRINT);
- if (Utils.isVirtualEnabled(getContext())) {
+ if (Utils.isFingerprintVirtualEnabled(getContext())) {
Slog.i(TAG, "Sync virtual enrollments");
final int userId = ActivityManager.getCurrentUser();
final CountDownLatch latch = new CountDownLatch(mRegistry.getProviders().size());
@@ -1315,7 +1315,7 @@
}
void simulateVhalFingerDown() {
- if (Utils.isVirtualEnabled(getContext())) {
+ if (Utils.isFingerprintVirtualEnabled(getContext())) {
Slog.i(TAG, "Simulate virtual HAL finger down event");
final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider();
if (provider != null) {
diff --git a/services/core/java/com/android/server/display/ExternalDisplayPolicy.java b/services/core/java/com/android/server/display/ExternalDisplayPolicy.java
index a12d248..b24caf4 100644
--- a/services/core/java/com/android/server/display/ExternalDisplayPolicy.java
+++ b/services/core/java/com/android/server/display/ExternalDisplayPolicy.java
@@ -222,7 +222,7 @@
} else {
// As external display is enabled by default, need to disable it now.
// TODO(b/292196201) Remove when the display can be disabled before DPC is created.
- logicalDisplay.setEnabledLocked(false);
+ mLogicalDisplayMapper.setDisplayEnabledLocked(logicalDisplay, false);
}
if (!isExternalDisplayAllowed()) {
diff --git a/services/core/java/com/android/server/pm/PackageArchiver.java b/services/core/java/com/android/server/pm/PackageArchiver.java
index 372e18b..7fc72ae 100644
--- a/services/core/java/com/android/server/pm/PackageArchiver.java
+++ b/services/core/java/com/android/server/pm/PackageArchiver.java
@@ -186,7 +186,7 @@
}
public static boolean isArchivingEnabled() {
- return Flags.archiving() || SystemProperties.getBoolean("pm.archiving.enabled", false);
+ return Flags.archiving();
}
@VisibleForTesting
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 3f2e12b..a6954ee 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -1922,6 +1922,7 @@
accessibilityManager.performSystemAction(
AccessibilityService.GLOBAL_ACTION_ACCESSIBILITY_ALL_APPS);
}
+ dismissKeyboardShortcutsMenu();
}
private void toggleNotificationPanel() {
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 149f55f..271c74e 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -8519,7 +8519,9 @@
}
// If activity in fullscreen mode is letterboxed because of fixed orientation then bounds
// are already calculated in resolveFixedOrientationConfiguration.
- } else if (!isLetterboxedForFixedOrientationAndAspectRatio()) {
+ // Don't apply aspect ratio if app is overridden to fullscreen by device user/manufacturer.
+ } else if (!isLetterboxedForFixedOrientationAndAspectRatio()
+ && !mLetterboxUiController.hasFullscreenOverride()) {
resolveAspectRatioRestriction(newParentConfiguration);
}
@@ -8620,7 +8622,7 @@
// Override starts here.
final Rect stableInsets = mDisplayContent.getDisplayPolicy().getDecorInsetsInfo(
- rotation, fullBounds.width(), fullBounds.height()).mLegacyConfigInsets;
+ rotation, fullBounds.width(), fullBounds.height()).mOverrideConfigInsets;
// This should be the only place override the configuration for ActivityRecord. Override
// the value if not calculated yet.
Rect outAppBounds = inOutConfig.windowConfiguration.getAppBounds();
@@ -8656,7 +8658,7 @@
mDisplayContent.getDisplay().getDisplayInfo(info);
mDisplayContent.computeSizeRanges(info, rotated, info.logicalWidth,
info.logicalHeight, mDisplayContent.getDisplayMetrics().density,
- inOutConfig, true /* legacyConfig */);
+ inOutConfig, true /* overrideConfig */);
}
// It's possible that screen size will be considered in different orientation with or
@@ -9035,8 +9037,8 @@
// vertically centered within parent bounds with insets, so position vertical bounds
// within parent bounds with insets to prevent insets from unnecessarily trimming
// vertical bounds.
- final int bottom = Math.min(parentBoundsWithInsets.top + parentBounds.width() - 1,
- parentBoundsWithInsets.bottom);
+ final int bottom = Math.min(parentBoundsWithInsets.top
+ + parentBoundsWithInsets.width() - 1, parentBoundsWithInsets.bottom);
containingBounds.set(parentBounds.left, parentBoundsWithInsets.top, parentBounds.right,
bottom);
containingBoundsWithInsets.set(parentBoundsWithInsets.left, parentBoundsWithInsets.top,
@@ -9047,8 +9049,8 @@
// horizontally centered within parent bounds with insets, so position horizontal bounds
// within parent bounds with insets to prevent insets from unnecessarily trimming
// horizontal bounds.
- final int right = Math.min(parentBoundsWithInsets.left + parentBounds.height(),
- parentBoundsWithInsets.right);
+ final int right = Math.min(parentBoundsWithInsets.left
+ + parentBoundsWithInsets.height(), parentBoundsWithInsets.right);
containingBounds.set(parentBoundsWithInsets.left, parentBounds.top, right,
parentBounds.bottom);
containingBoundsWithInsets.set(parentBoundsWithInsets.left, parentBoundsWithInsets.top,
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 577d2a6..add0f1f 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -2302,7 +2302,7 @@
}
computeSizeRanges(mDisplayInfo, rotated, dw, dh, mDisplayMetrics.density, outConfig,
- false /* legacyConfig */);
+ false /* overrideConfig */);
setDisplayInfoOverride();
@@ -2439,7 +2439,7 @@
final DisplayCutout displayCutout = calculateDisplayCutoutForRotation(rotation);
displayInfo.displayCutout = displayCutout.isEmpty() ? null : displayCutout;
computeSizeRanges(displayInfo, rotated, dw, dh, mDisplayMetrics.density, outConfig,
- false /* legacyConfig */);
+ false /* overrideConfig */);
return displayInfo;
}
@@ -2613,11 +2613,10 @@
* @param dh Display Height in current rotation.
* @param density Display density.
* @param outConfig The output configuration to
- * @param legacyConfig Whether we need to report the legacy result, which is excluding system
- * decorations.
+ * @param overrideConfig Whether we need to report the override config result
*/
void computeSizeRanges(DisplayInfo displayInfo, boolean rotated,
- int dw, int dh, float density, Configuration outConfig, boolean legacyConfig) {
+ int dw, int dh, float density, Configuration outConfig, boolean overrideConfig) {
// We need to determine the smallest width that will occur under normal
// operation. To this, start with the base screen size and compute the
@@ -2635,10 +2634,12 @@
displayInfo.smallestNominalAppHeight = 1<<30;
displayInfo.largestNominalAppWidth = 0;
displayInfo.largestNominalAppHeight = 0;
- adjustDisplaySizeRanges(displayInfo, Surface.ROTATION_0, unrotDw, unrotDh, legacyConfig);
- adjustDisplaySizeRanges(displayInfo, Surface.ROTATION_90, unrotDh, unrotDw, legacyConfig);
- adjustDisplaySizeRanges(displayInfo, Surface.ROTATION_180, unrotDw, unrotDh, legacyConfig);
- adjustDisplaySizeRanges(displayInfo, Surface.ROTATION_270, unrotDh, unrotDw, legacyConfig);
+ adjustDisplaySizeRanges(displayInfo, Surface.ROTATION_0, unrotDw, unrotDh, overrideConfig);
+ adjustDisplaySizeRanges(displayInfo, Surface.ROTATION_90, unrotDh, unrotDw, overrideConfig);
+ adjustDisplaySizeRanges(displayInfo, Surface.ROTATION_180, unrotDw, unrotDh,
+ overrideConfig);
+ adjustDisplaySizeRanges(displayInfo, Surface.ROTATION_270, unrotDh, unrotDw,
+ overrideConfig);
if (outConfig == null) {
return;
@@ -2648,17 +2649,17 @@
}
private void adjustDisplaySizeRanges(DisplayInfo displayInfo, int rotation, int dw, int dh,
- boolean legacyConfig) {
+ boolean overrideConfig) {
final DisplayPolicy.DecorInsets.Info info = mDisplayPolicy.getDecorInsetsInfo(
rotation, dw, dh);
final int w;
final int h;
- if (!legacyConfig) {
+ if (!overrideConfig) {
w = info.mConfigFrame.width();
h = info.mConfigFrame.height();
} else {
- w = info.mLegacyConfigFrame.width();
- h = info.mLegacyConfigFrame.height();
+ w = info.mOverrideConfigFrame.width();
+ h = info.mOverrideConfigFrame.height();
}
if (w < displayInfo.smallestNominalAppWidth) {
displayInfo.smallestNominalAppWidth = w;
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 90a9fee..3559ba8 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -1919,9 +1919,9 @@
final Rect mConfigInsets = new Rect();
/**
- * Legacy value of mConfigInsets for app compatibility purpose.
+ * Override value of mConfigInsets for app compatibility purpose.
*/
- final Rect mLegacyConfigInsets = new Rect();
+ final Rect mOverrideConfigInsets = new Rect();
/** The display frame available after excluding {@link #mNonDecorInsets}. */
final Rect mNonDecorFrame = new Rect();
@@ -1934,9 +1934,9 @@
final Rect mConfigFrame = new Rect();
/**
- * Legacy value of mConfigFrame for app compatibility purpose.
+ * Override value of mConfigFrame for app compatibility purpose.
*/
- final Rect mLegacyConfigFrame = new Rect();
+ final Rect mOverrideConfigFrame = new Rect();
private boolean mNeedUpdate = true;
@@ -1952,22 +1952,22 @@
? decor
: insetsState.calculateInsets(displayFrame, dc.mWmService.mConfigTypes,
true /* ignoreVisibility */);
- final Insets legacyConfigInsets = dc.mWmService.mConfigTypes
- == dc.mWmService.mLegacyConfigTypes
+ final Insets overrideConfigInsets = dc.mWmService.mConfigTypes
+ == dc.mWmService.mOverrideConfigTypes
? configInsets
: insetsState.calculateInsets(displayFrame,
- dc.mWmService.mLegacyConfigTypes, true /* ignoreVisibility */);
+ dc.mWmService.mOverrideConfigTypes, true /* ignoreVisibility */);
mNonDecorInsets.set(decor.left, decor.top, decor.right, decor.bottom);
mConfigInsets.set(configInsets.left, configInsets.top, configInsets.right,
configInsets.bottom);
- mLegacyConfigInsets.set(legacyConfigInsets.left, legacyConfigInsets.top,
- legacyConfigInsets.right, legacyConfigInsets.bottom);
+ mOverrideConfigInsets.set(overrideConfigInsets.left, overrideConfigInsets.top,
+ overrideConfigInsets.right, overrideConfigInsets.bottom);
mNonDecorFrame.set(displayFrame);
mNonDecorFrame.inset(mNonDecorInsets);
mConfigFrame.set(displayFrame);
mConfigFrame.inset(mConfigInsets);
- mLegacyConfigFrame.set(displayFrame);
- mLegacyConfigFrame.inset(mLegacyConfigInsets);
+ mOverrideConfigFrame.set(displayFrame);
+ mOverrideConfigFrame.inset(mOverrideConfigInsets);
mNeedUpdate = false;
return insetsState;
}
@@ -1975,10 +1975,10 @@
void set(Info other) {
mNonDecorInsets.set(other.mNonDecorInsets);
mConfigInsets.set(other.mConfigInsets);
- mLegacyConfigInsets.set(other.mLegacyConfigInsets);
+ mOverrideConfigInsets.set(other.mOverrideConfigInsets);
mNonDecorFrame.set(other.mNonDecorFrame);
mConfigFrame.set(other.mConfigFrame);
- mLegacyConfigFrame.set(other.mLegacyConfigFrame);
+ mOverrideConfigFrame.set(other.mOverrideConfigFrame);
mNeedUpdate = false;
}
@@ -2091,7 +2091,7 @@
final InsetsState newInsetsState = newInfo.update(mDisplayContent, rotation, dw, dh);
final DecorInsets.Info currentInfo = getDecorInsetsInfo(rotation, dw, dh);
if (newInfo.mConfigFrame.equals(currentInfo.mConfigFrame)
- && newInfo.mLegacyConfigFrame.equals(currentInfo.mLegacyConfigFrame)) {
+ && newInfo.mOverrideConfigFrame.equals(currentInfo.mOverrideConfigFrame)) {
// Even if the config frame is not changed in current rotation, it may change the
// insets in other rotations if the frame of insets source is changed.
final InsetsState currentInsetsState = mDisplayContent.mDisplayFrames.mInsetsState;
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index 5d613cf..f8ae17c 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -1216,6 +1216,10 @@
|| mUserAspectRatio == USER_MIN_ASPECT_RATIO_FULLSCREEN);
}
+ boolean hasFullscreenOverride() {
+ return isSystemOverrideToFullscreenEnabled() || shouldApplyUserFullscreenOverride();
+ }
+
float getUserMinAspectRatio() {
switch (mUserAspectRatio) {
case USER_MIN_ASPECT_RATIO_DISPLAY_SIZE:
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index e716c38..acffc65 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -2449,7 +2449,7 @@
if (!useLegacyInsetsForStableBounds) {
intersectWithInsetsIfFits(outStableBounds, mTmpBounds, info.mConfigInsets);
} else {
- intersectWithInsetsIfFits(outStableBounds, mTmpBounds, info.mLegacyConfigInsets);
+ intersectWithInsetsIfFits(outStableBounds, mTmpBounds, info.mOverrideConfigInsets);
}
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 8c6478d..395cd86 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -561,7 +561,7 @@
/** Device default insets types shall be excluded from config app sizes. */
final int mConfigTypes;
- final int mLegacyConfigTypes;
+ final int mOverrideConfigTypes;
final boolean mLimitedAlphaCompositing;
final int mMaxUiWidth;
@@ -1195,20 +1195,18 @@
if (mFlags.mInsetsDecoupledConfiguration) {
mDecorTypes = 0;
mConfigTypes = 0;
- } else if (isScreenSizeDecoupledFromStatusBarAndCutout) {
- mDecorTypes = WindowInsets.Type.navigationBars();
- mConfigTypes = WindowInsets.Type.navigationBars();
} else {
mDecorTypes = WindowInsets.Type.displayCutout() | WindowInsets.Type.navigationBars();
mConfigTypes = WindowInsets.Type.displayCutout() | WindowInsets.Type.statusBars()
| WindowInsets.Type.navigationBars();
}
- if (isScreenSizeDecoupledFromStatusBarAndCutout) {
- // Do not fallback to legacy value for enabled devices.
- mLegacyConfigTypes = WindowInsets.Type.navigationBars();
+ if (isScreenSizeDecoupledFromStatusBarAndCutout && !mFlags.mInsetsDecoupledConfiguration) {
+ // If the global new behavior is not there, but the partial decouple flag is on.
+ mOverrideConfigTypes = 0;
} else {
- mLegacyConfigTypes = WindowInsets.Type.displayCutout() | WindowInsets.Type.statusBars()
- | WindowInsets.Type.navigationBars();
+ mOverrideConfigTypes =
+ WindowInsets.Type.displayCutout() | WindowInsets.Type.statusBars()
+ | WindowInsets.Type.navigationBars();
}
mLetterboxConfiguration = new LetterboxConfiguration(
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
index 18f0311..8b23668 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -2429,8 +2429,9 @@
LogicalDisplay display =
logicalDisplayMapper.getDisplayLocked(displayDevice, /* includeDisabled= */ true);
assertThat(display.isEnabledLocked()).isFalse();
+ // TODO(b/332711269) make sure only one DISPLAY_GROUP_EVENT_ADDED sent.
assertThat(callback.receivedEvents()).containsExactly(DISPLAY_GROUP_EVENT_ADDED,
- EVENT_DISPLAY_CONNECTED).inOrder();
+ DISPLAY_GROUP_EVENT_ADDED, EVENT_DISPLAY_CONNECTED).inOrder();
}
@Test
diff --git a/services/tests/displayservicetests/src/com/android/server/display/ExternalDisplayPolicyTest.java b/services/tests/displayservicetests/src/com/android/server/display/ExternalDisplayPolicyTest.java
index 1a71e77..ea08be4 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/ExternalDisplayPolicyTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/ExternalDisplayPolicyTest.java
@@ -317,7 +317,8 @@
mDisplayEventCaptor.capture());
assertThat(mLogicalDisplayCaptor.getValue()).isEqualTo(mMockedLogicalDisplay);
assertThat(mDisplayEventCaptor.getValue()).isEqualTo(EVENT_DISPLAY_CONNECTED);
- verify(mMockedLogicalDisplay).setEnabledLocked(false);
+ verify(mMockedLogicalDisplayMapper).setDisplayEnabledLocked(eq(mMockedLogicalDisplay),
+ eq(false));
clearInvocations(mMockedLogicalDisplayMapper);
clearInvocations(mMockedLogicalDisplay);
}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/FaceServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/FaceServiceTest.java
index 3aaac2e..65999ae 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/FaceServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/FaceServiceTest.java
@@ -196,6 +196,21 @@
}
@Test
+ @RequiresFlagsEnabled({Flags.FLAG_DE_HIDL, Flags.FLAG_FACE_VHAL_FEATURE})
+ public void registerAuthenticatorsLegacy_virtualFaceOnly() throws Exception {
+ initService();
+ Settings.Secure.putInt(mSettingsRule.mockContentResolver(mContext),
+ Settings.Secure.BIOMETRIC_FACE_VIRTUAL_ENABLED, 1);
+
+ mFaceService.mServiceWrapper.registerAuthenticatorsLegacy(mFaceSensorConfigurations);
+ waitForRegistration();
+
+ verify(mIBiometricService).registerAuthenticator(eq(ID_VIRTUAL),
+ eq(BiometricAuthenticator.TYPE_FACE),
+ eq(Utils.propertyStrengthToAuthenticatorStrength(STRENGTH_STRONG)), any());
+ }
+
+ @Test
@RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
public void registerAuthenticatorsLegacy_virtualAlwaysWhenNoOther() throws Exception {
mFaceSensorConfigurations = new FaceSensorConfigurations(false);
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceTest.java
index 88956b6..cdbf1f7 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceTest.java
@@ -243,6 +243,18 @@
}
@Test
+ public void registerAuthenticators_virtualFingerprintOnly() throws Exception {
+ initServiceWith(NAME_DEFAULT, NAME_VIRTUAL);
+ Settings.Secure.putInt(mSettingsRule.mockContentResolver(mContext),
+ Settings.Secure.BIOMETRIC_FINGERPRINT_VIRTUAL_ENABLED, 1);
+
+ mService.mServiceWrapper.registerAuthenticators(HIDL_AUTHENTICATORS);
+ waitForRegistration();
+
+ verify(mIBiometricService).registerAuthenticator(eq(ID_VIRTUAL), anyInt(), anyInt(), any());
+ }
+
+ @Test
@RequiresFlagsEnabled(Flags.FLAG_DE_HIDL)
public void registerAuthenticatorsLegacy_virtualOnly() throws Exception {
initServiceWith(NAME_DEFAULT, NAME_VIRTUAL);
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index b96f39d..faa36a8 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -129,6 +129,7 @@
import org.junit.After;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestRule;
@@ -2326,6 +2327,92 @@
}
@Test
+ public void testUserOverrideFullscreenForLandscapeDisplay() {
+ final int displayWidth = 1600;
+ final int displayHeight = 1400;
+ setUpDisplaySizeWithApp(displayWidth, displayHeight);
+ mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+ spyOn(mActivity.mWmService.mLetterboxConfiguration);
+ doReturn(true).when(mActivity.mWmService.mLetterboxConfiguration)
+ .isUserAppAspectRatioFullscreenEnabled();
+
+ // Set user aspect ratio override
+ spyOn(mActivity.mLetterboxUiController);
+ doReturn(USER_MIN_ASPECT_RATIO_FULLSCREEN).when(mActivity.mLetterboxUiController)
+ .getUserMinAspectRatioOverrideCode();
+
+ prepareMinAspectRatio(mActivity, 16 / 9f, SCREEN_ORIENTATION_PORTRAIT);
+
+ final Rect bounds = mActivity.getBounds();
+
+ // bounds should be fullscreen
+ assertEquals(displayHeight, bounds.height());
+ assertEquals(displayWidth, bounds.width());
+ }
+
+ @Test
+ public void testUserOverrideFullscreenForPortraitDisplay() {
+ final int displayWidth = 1400;
+ final int displayHeight = 1600;
+ setUpDisplaySizeWithApp(displayWidth, displayHeight);
+ mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+ spyOn(mActivity.mWmService.mLetterboxConfiguration);
+ doReturn(true).when(mActivity.mWmService.mLetterboxConfiguration)
+ .isUserAppAspectRatioFullscreenEnabled();
+
+ // Set user aspect ratio override
+ spyOn(mActivity.mLetterboxUiController);
+ doReturn(USER_MIN_ASPECT_RATIO_FULLSCREEN).when(mActivity.mLetterboxUiController)
+ .getUserMinAspectRatioOverrideCode();
+
+ prepareMinAspectRatio(mActivity, 16 / 9f, SCREEN_ORIENTATION_LANDSCAPE);
+
+ final Rect bounds = mActivity.getBounds();
+
+ // bounds should be fullscreen
+ assertEquals(displayHeight, bounds.height());
+ assertEquals(displayWidth, bounds.width());
+ }
+
+ @Test
+ public void testSystemFullscreenOverrideForLandscapeDisplay() {
+ final int displayWidth = 1600;
+ final int displayHeight = 1400;
+ setUpDisplaySizeWithApp(displayWidth, displayHeight);
+ mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+ spyOn(mActivity.mLetterboxUiController);
+ doReturn(true).when(mActivity.mLetterboxUiController)
+ .isSystemOverrideToFullscreenEnabled();
+
+ prepareMinAspectRatio(mActivity, 16 / 9f, SCREEN_ORIENTATION_PORTRAIT);
+
+ final Rect bounds = mActivity.getBounds();
+
+ // bounds should be fullscreen
+ assertEquals(displayHeight, bounds.height());
+ assertEquals(displayWidth, bounds.width());
+ }
+
+ @Test
+ public void testSystemFullscreenOverrideForPortraitDisplay() {
+ final int displayWidth = 1400;
+ final int displayHeight = 1600;
+ setUpDisplaySizeWithApp(displayWidth, displayHeight);
+ mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+ spyOn(mActivity.mLetterboxUiController);
+ doReturn(true).when(mActivity.mLetterboxUiController)
+ .isSystemOverrideToFullscreenEnabled();
+
+ prepareMinAspectRatio(mActivity, 16 / 9f, SCREEN_ORIENTATION_LANDSCAPE);
+
+ final Rect bounds = mActivity.getBounds();
+
+ // bounds should be fullscreen
+ assertEquals(displayHeight, bounds.height());
+ assertEquals(displayWidth, bounds.width());
+ }
+
+ @Test
public void testUserOverrideSplitScreenAspectRatioForLandscapeDisplay() {
final int displayWidth = 1600;
final int displayHeight = 1400;
@@ -4075,6 +4162,7 @@
}
@Test
+ @Ignore // TODO(b/330888878): fix test in main
public void testPortraitCloseToSquareDisplayWithTaskbar_notLetterboxed() {
// Set up portrait close to square display
setUpDisplaySizeWithApp(2200, 2280);
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 03d9871..7bba5e8 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -9872,6 +9872,50 @@
*/
public static final String KEY_SATELLITE_INFORMATION_REDIRECT_URL_STRING =
"satellite_information_redirect_url_string";
+ /**
+ * Indicate whether a carrier supports emergency messaging. When this config is {@code false},
+ * emergency call to satellite T911 handover will be disabled.
+ *
+ * This will need agreement with carriers before enabling this flag.
+ *
+ * The default value is false.
+ *
+ * @hide
+ */
+ public static final String KEY_EMERGENCY_MESSAGING_SUPPORTED_BOOL =
+ "emergency_messaging_supported_bool";
+
+ /**
+ * An integer key holds the timeout duration in milliseconds used to determine whether to hand
+ * over an emergency call to satellite T911.
+ *
+ * The timer is started when there is an ongoing emergency call, and the IMS is not registered,
+ * and cellular service is not available. When the timer expires,
+ * {@link com.android.internal.telephony.satellite.SatelliteSOSMessageRecommender} will send the
+ * event {@link TelephonyManager#EVENT_DISPLAY_EMERGENCY_MESSAGE} to Dialer, which will then
+ * prompt user to switch to using satellite emergency messaging.
+ *
+ * The default value is 30 seconds.
+ *
+ * @hide
+ */
+ public static final String KEY_EMERGENCY_CALL_TO_SATELLITE_T911_HANDOVER_TIMEOUT_MILLIS_INT =
+ "emergency_call_to_satellite_t911_handover_timeout_millis_int";
+
+ /**
+ * An int array that contains default capabilities for carrier enabled satellite roaming.
+ * If any PLMN is provided from the entitlement server, and it is not listed in
+ * {@link #KEY_CARRIER_SUPPORTED_SATELLITE_SERVICES_PER_PROVIDER_BUNDLE}, default capabilities
+ * will be used instead.
+ * <p>
+ * The default capabilities are
+ * {@link NetworkRegistrationInfo#SERVICE_TYPE_SMS}, and
+ * {@link NetworkRegistrationInfo#SERVICE_TYPE_MMS}
+ *
+ * @hide
+ */
+ public static final String KEY_CARRIER_ROAMING_SATELLITE_DEFAULT_SERVICES_INT_ARRAY =
+ "carrier_roaming_satellite_default_services_int_array";
/**
* Indicating whether DUN APN should be disabled when the device is roaming. In that case,
@@ -10993,7 +11037,15 @@
sDefaults.putBoolean(KEY_SATELLITE_ENTITLEMENT_SUPPORTED_BOOL, false);
sDefaults.putString(KEY_SATELLITE_ENTITLEMENT_APP_NAME_STRING, "androidSatmode");
sDefaults.putString(KEY_SATELLITE_INFORMATION_REDIRECT_URL_STRING, "");
+ sDefaults.putIntArray(KEY_CARRIER_ROAMING_SATELLITE_DEFAULT_SERVICES_INT_ARRAY,
+ new int[] {
+ NetworkRegistrationInfo.SERVICE_TYPE_SMS,
+ NetworkRegistrationInfo.SERVICE_TYPE_MMS
+ });
sDefaults.putBoolean(KEY_DISABLE_DUN_APN_WHILE_ROAMING_WITH_PRESET_APN_BOOL, false);
+ sDefaults.putBoolean(KEY_EMERGENCY_MESSAGING_SUPPORTED_BOOL, false);
+ sDefaults.putInt(KEY_EMERGENCY_CALL_TO_SATELLITE_T911_HANDOVER_TIMEOUT_MILLIS_INT,
+ (int) TimeUnit.SECONDS.toMillis(30));
sDefaults.putString(KEY_DEFAULT_PREFERRED_APN_NAME_STRING, "");
sDefaults.putBoolean(KEY_SUPPORTS_CALL_COMPOSER_BOOL, false);
sDefaults.putBoolean(KEY_SUPPORTS_BUSINESS_CALL_COMPOSER_BOOL, false);
diff --git a/telephony/java/android/telephony/satellite/ISatelliteTransmissionUpdateCallback.aidl b/telephony/java/android/telephony/satellite/ISatelliteTransmissionUpdateCallback.aidl
index 06fc3c6..579fda3 100644
--- a/telephony/java/android/telephony/satellite/ISatelliteTransmissionUpdateCallback.aidl
+++ b/telephony/java/android/telephony/satellite/ISatelliteTransmissionUpdateCallback.aidl
@@ -26,11 +26,13 @@
/**
* Called when satellite datagram send state changed.
*
+ * @param datagramType The datagram type of currently being sent.
* @param state The new send datagram transfer state.
* @param sendPendingCount The number of datagrams that are currently being sent.
* @param errorCode If datagram transfer failed, the reason for failure.
*/
- void onSendDatagramStateChanged(in int state, in int sendPendingCount, in int errorCode);
+ void onSendDatagramStateChanged(int datagramType, int state, int sendPendingCount,
+ int errorCode);
/**
* Called when satellite datagram receive state changed.
@@ -39,7 +41,7 @@
* @param receivePendingCount The number of datagrams that are currently pending to be received.
* @param errorCode If datagram transfer failed, the reason for failure.
*/
- void onReceiveDatagramStateChanged(in int state, in int receivePendingCount, in int errorCode);
+ void onReceiveDatagramStateChanged(int state, int receivePendingCount, int errorCode);
/**
* Called when the satellite position changed.
diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java
index 20b24b9..87bb0f0 100644
--- a/telephony/java/android/telephony/satellite/SatelliteManager.java
+++ b/telephony/java/android/telephony/satellite/SatelliteManager.java
@@ -992,12 +992,19 @@
*/
@FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
public static final int DATAGRAM_TYPE_LOCATION_SHARING = 2;
+ /**
+ * This type of datagram is used to keep the device in satellite connected state or check if
+ * there is any incoming message.
+ * @hide
+ */
+ public static final int DATAGRAM_TYPE_KEEP_ALIVE = 3;
/** @hide */
@IntDef(prefix = "DATAGRAM_TYPE_", value = {
DATAGRAM_TYPE_UNKNOWN,
DATAGRAM_TYPE_SOS_MESSAGE,
- DATAGRAM_TYPE_LOCATION_SHARING
+ DATAGRAM_TYPE_LOCATION_SHARING,
+ DATAGRAM_TYPE_KEEP_ALIVE
})
@Retention(RetentionPolicy.SOURCE)
public @interface DatagramType {}
@@ -1077,8 +1084,13 @@
}
@Override
- public void onSendDatagramStateChanged(int state, int sendPendingCount,
- int errorCode) {
+ public void onSendDatagramStateChanged(int datagramType, int state,
+ int sendPendingCount, int errorCode) {
+ executor.execute(() -> Binder.withCleanCallingIdentity(
+ () -> callback.onSendDatagramStateChanged(datagramType,
+ state, sendPendingCount, errorCode)));
+
+ // For backward compatibility
executor.execute(() -> Binder.withCleanCallingIdentity(
() -> callback.onSendDatagramStateChanged(
state, sendPendingCount, errorCode)));
diff --git a/telephony/java/android/telephony/satellite/SatelliteTransmissionUpdateCallback.java b/telephony/java/android/telephony/satellite/SatelliteTransmissionUpdateCallback.java
index e020970..d8bd662 100644
--- a/telephony/java/android/telephony/satellite/SatelliteTransmissionUpdateCallback.java
+++ b/telephony/java/android/telephony/satellite/SatelliteTransmissionUpdateCallback.java
@@ -52,6 +52,19 @@
@SatelliteManager.SatelliteResult int errorCode);
/**
+ * Called when satellite datagram send state changed.
+ *
+ * @param datagramType The datagram type of currently being sent.
+ * @param state The new send datagram transfer state.
+ * @param sendPendingCount The number of datagrams that are currently being sent.
+ * @param errorCode If datagram transfer failed, the reason for failure.
+ *
+ * @hide
+ */
+ void onSendDatagramStateChanged(@SatelliteManager.DatagramType int datagramType,
+ @SatelliteManager.SatelliteDatagramTransferState int state, int sendPendingCount,
+ @SatelliteManager.SatelliteResult int errorCode);
+ /**
* Called when satellite datagram receive state changed.
*
* @param state The new receive datagram transfer state.