diff options
15 files changed, 323 insertions, 96 deletions
diff --git a/core/java/android/app/AppCompatTaskInfo.java b/core/java/android/app/AppCompatTaskInfo.java index 0bae5e67b87b..8e950c630918 100644 --- a/core/java/android/app/AppCompatTaskInfo.java +++ b/core/java/android/app/AppCompatTaskInfo.java @@ -76,6 +76,11 @@ public class AppCompatTaskInfo implements Parcelable { public boolean topActivityEligibleForLetterboxEducation; /** + * Whether the letterbox education is enabled + */ + public boolean isLetterboxEducationEnabled; + + /** * Whether the direct top activity is in size compat mode on foreground. */ public boolean topActivityInSizeCompat; @@ -224,6 +229,7 @@ public class AppCompatTaskInfo implements Parcelable { == that.topActivityEligibleForUserAspectRatioButton && topActivityEligibleForLetterboxEducation == that.topActivityEligibleForLetterboxEducation + && isLetterboxEducationEnabled == that.isLetterboxEducationEnabled && topActivityLetterboxVerticalPosition == that.topActivityLetterboxVerticalPosition && topActivityLetterboxHorizontalPosition == that.topActivityLetterboxHorizontalPosition @@ -238,6 +244,7 @@ public class AppCompatTaskInfo implements Parcelable { * Reads the TaskInfo from a parcel. */ void readFromParcel(Parcel source) { + isLetterboxEducationEnabled = source.readBoolean(); topActivityInSizeCompat = source.readBoolean(); topActivityEligibleForLetterboxEducation = source.readBoolean(); cameraCompatControlState = source.readInt(); @@ -258,6 +265,7 @@ public class AppCompatTaskInfo implements Parcelable { */ @Override public void writeToParcel(Parcel dest, int flags) { + dest.writeBoolean(isLetterboxEducationEnabled); dest.writeBoolean(topActivityInSizeCompat); dest.writeBoolean(topActivityEligibleForLetterboxEducation); dest.writeInt(cameraCompatControlState); @@ -278,6 +286,7 @@ public class AppCompatTaskInfo implements Parcelable { return "AppCompatTaskInfo { topActivityInSizeCompat=" + topActivityInSizeCompat + " topActivityEligibleForLetterboxEducation= " + topActivityEligibleForLetterboxEducation + + "isLetterboxEducationEnabled= " + isLetterboxEducationEnabled + " isLetterboxDoubleTapEnabled= " + isLetterboxDoubleTapEnabled + " topActivityEligibleForUserAspectRatioButton= " + topActivityEligibleForUserAspectRatioButton diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java index 86571cf9c622..5359e9faec3d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java @@ -188,6 +188,11 @@ public class CompatUIController implements OnDisplaysChangedListener, */ private boolean mHasShownUserAspectRatioSettingsButton = false; + /** + * This is true when the rechability education is displayed for the first time. + */ + private boolean mIsFirstReachabilityEducationRunning; + public CompatUIController(@NonNull Context context, @NonNull ShellInit shellInit, @NonNull ShellController shellController, @@ -252,9 +257,35 @@ public class CompatUIController implements OnDisplaysChangedListener, removeLayouts(taskInfo.taskId); return; } - + // We're showing the first reachability education so we ignore incoming TaskInfo + // until the education flow has completed or we double tap. + if (mIsFirstReachabilityEducationRunning) { + return; + } + if (taskInfo.appCompatTaskInfo.topActivityBoundsLetterboxed) { + if (taskInfo.appCompatTaskInfo.isLetterboxEducationEnabled) { + createOrUpdateLetterboxEduLayout(taskInfo, taskListener); + } else if (!taskInfo.appCompatTaskInfo.isFromLetterboxDoubleTap) { + // In this case the app is letterboxed and the letterbox education + // is disabled. In this case we need to understand if it's the first + // time we show the reachability education. When this is happening + // we need to ignore all the incoming TaskInfo until the education + // completes. If we come from a double tap we follow the normal flow. + final boolean topActivityPillarboxed = + taskInfo.appCompatTaskInfo.isTopActivityPillarboxed(); + final boolean isFirstTimeHorizontalReachabilityEdu = topActivityPillarboxed + && !mCompatUIConfiguration.hasSeenHorizontalReachabilityEducation(taskInfo); + final boolean isFirstTimeVerticalReachabilityEdu = !topActivityPillarboxed + && !mCompatUIConfiguration.hasSeenVerticalReachabilityEducation(taskInfo); + if (isFirstTimeHorizontalReachabilityEdu || isFirstTimeVerticalReachabilityEdu) { + mIsFirstReachabilityEducationRunning = true; + mCompatUIConfiguration.setSeenLetterboxEducation(taskInfo.userId); + createOrUpdateReachabilityEduLayout(taskInfo, taskListener); + return; + } + } + } createOrUpdateCompatLayout(taskInfo, taskListener); - createOrUpdateLetterboxEduLayout(taskInfo, taskListener); createOrUpdateRestartDialogLayout(taskInfo, taskListener); if (mCompatUIConfiguration.getHasSeenLetterboxEducation(taskInfo.userId)) { createOrUpdateReachabilityEduLayout(taskInfo, taskListener); @@ -589,6 +620,7 @@ public class CompatUIController implements OnDisplaysChangedListener, private void onInitialReachabilityEduDismissed(@NonNull TaskInfo taskInfo, @NonNull ShellTaskOrganizer.TaskListener taskListener) { // We need to update the UI otherwise it will not be shown until the user relaunches the app + mIsFirstReachabilityEducationRunning = false; createOrUpdateUserAspectRatioSettingsLayout(taskInfo, taskListener); } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java index fef81af8946b..2c85495ce5db 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java @@ -668,6 +668,18 @@ public class CompatUIControllerTest extends ShellTestCase { Assert.assertTrue(mController.hasShownUserAspectRatioSettingsButton()); } + @Test + public void testLetterboxEduLayout_notCreatedWhenLetterboxEducationIsDisabled() { + TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, /* hasSizeCompat= */ true, + CAMERA_COMPAT_CONTROL_HIDDEN); + taskInfo.appCompatTaskInfo.isLetterboxEducationEnabled = false; + + mController.onCompatInfoChanged(taskInfo, mMockTaskListener); + + verify(mController, never()).createLetterboxEduWindowManager(any(), eq(taskInfo), + eq(mMockTaskListener)); + } + private static TaskInfo createTaskInfo(int displayId, int taskId, boolean hasSizeCompat, @CameraCompatControlState int cameraCompatControlState) { return createTaskInfo(displayId, taskId, hasSizeCompat, cameraCompatControlState, @@ -693,6 +705,8 @@ public class CompatUIControllerTest extends ShellTestCase { taskInfo.isVisible = isVisible; taskInfo.isFocused = isFocused; taskInfo.isTopActivityTransparent = isTopActivityTransparent; + taskInfo.appCompatTaskInfo.isLetterboxEducationEnabled = true; + taskInfo.appCompatTaskInfo.topActivityBoundsLetterboxed = true; return taskInfo; } } diff --git a/media/java/android/media/RingtoneManager.java b/media/java/android/media/RingtoneManager.java index 3432b3f5995a..86113df3a091 100644 --- a/media/java/android/media/RingtoneManager.java +++ b/media/java/android/media/RingtoneManager.java @@ -16,7 +16,6 @@ package android.media; -import android.Manifest; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; @@ -573,12 +572,13 @@ public class RingtoneManager { FileUtils.closeQuietly(cursor); throw new FileNotFoundException("No item found for " + baseUri); } else if (cursor.getCount() > 1) { + int resultCount = cursor.getCount(); // Find more than 1 result. // We are not sure which one is the right ringtone file so just abandon this case. FileUtils.closeQuietly(cursor); throw new FileNotFoundException( "Find multiple ringtone candidates by title+ringtone_type query: count: " - + cursor.getCount()); + + resultCount); } if (cursor.moveToFirst()) { ringtoneUri = ContentUris.withAppendedId(baseUri, cursor.getLong(0)); diff --git a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/PowerAllowlistBackend.java b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/PowerAllowlistBackend.java index 8fd4e912e04a..c0eaaaaa828b 100644 --- a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/PowerAllowlistBackend.java +++ b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/PowerAllowlistBackend.java @@ -32,6 +32,7 @@ import android.text.TextUtils; import android.util.ArraySet; import android.util.Log; +import androidx.annotation.GuardedBy; import androidx.annotation.VisibleForTesting; import com.android.internal.telephony.SmsApplication; @@ -52,13 +53,22 @@ public class PowerAllowlistBackend { private static PowerAllowlistBackend sInstance; + private final Object mAllowlistedAppsLock = new Object(); + private final Object mSysAllowlistedAppsLock = new Object(); + private final Object mDefaultActiveAppsLock = new Object(); + private final Context mAppContext; private final IDeviceIdleController mDeviceIdleService; + + @GuardedBy("mAllowlistedAppsLock") private final ArraySet<String> mAllowlistedApps = new ArraySet<>(); + @GuardedBy("mSysAllowlistedAppsLock") private final ArraySet<String> mSysAllowlistedApps = new ArraySet<>(); + @GuardedBy("mDefaultActiveAppsLock") private final ArraySet<String> mDefaultActiveApps = new ArraySet<>(); - public PowerAllowlistBackend(Context context) { + @VisibleForTesting + PowerAllowlistBackend(Context context) { this(context, IDeviceIdleController.Stub.asInterface( ServiceManager.getService(DEVICE_IDLE_SERVICE))); } @@ -71,22 +81,28 @@ public class PowerAllowlistBackend { } public int getAllowlistSize() { - return mAllowlistedApps.size(); + synchronized (mAllowlistedAppsLock) { + return mAllowlistedApps.size(); + } } /** * Check if target package is in System allow list */ public boolean isSysAllowlisted(String pkg) { - return mSysAllowlistedApps.contains(pkg); + synchronized (mSysAllowlistedAppsLock) { + return mSysAllowlistedApps.contains(pkg); + } } /** * Check if target package is in allow list */ public boolean isAllowlisted(String pkg, int uid) { - if (mAllowlistedApps.contains(pkg)) { - return true; + synchronized (mAllowlistedAppsLock) { + if (mAllowlistedApps.contains(pkg)) { + return true; + } } if (isDefaultActiveApp(pkg, uid)) { @@ -103,9 +119,10 @@ public class PowerAllowlistBackend { // Additionally, check if pkg is default dialer/sms. They are considered essential apps and // should be automatically allowlisted (otherwise user may be able to set restriction on // them, leading to bad device behavior.) - - if (mDefaultActiveApps.contains(pkg)) { - return true; + synchronized (mDefaultActiveAppsLock) { + if (mDefaultActiveApps.contains(pkg)) { + return true; + } } final DevicePolicyManager devicePolicyManager = mAppContext.getSystemService( @@ -165,10 +182,12 @@ public class PowerAllowlistBackend { * Add app into power save allow list. * @param pkg packageName */ - public void addApp(String pkg) { + public synchronized void addApp(String pkg) { try { mDeviceIdleService.addPowerSaveWhitelistApp(pkg); - mAllowlistedApps.add(pkg); + synchronized (mAllowlistedAppsLock) { + mAllowlistedApps.add(pkg); + } } catch (RemoteException e) { Log.w(TAG, "Unable to reach IDeviceIdleController", e); } @@ -178,10 +197,12 @@ public class PowerAllowlistBackend { * Remove package from power save allow list. * @param pkg */ - public void removeApp(String pkg) { + public synchronized void removeApp(String pkg) { try { mDeviceIdleService.removePowerSaveWhitelistApp(pkg); - mAllowlistedApps.remove(pkg); + synchronized (mAllowlistedAppsLock) { + mAllowlistedApps.remove(pkg); + } } catch (RemoteException e) { Log.w(TAG, "Unable to reach IDeviceIdleController", e); } @@ -191,21 +212,31 @@ public class PowerAllowlistBackend { * Refresh all of lists */ @VisibleForTesting - public void refreshList() { - mSysAllowlistedApps.clear(); - mAllowlistedApps.clear(); - mDefaultActiveApps.clear(); + public synchronized void refreshList() { + synchronized (mSysAllowlistedAppsLock) { + mSysAllowlistedApps.clear(); + } + synchronized (mAllowlistedAppsLock) { + mAllowlistedApps.clear(); + } + synchronized (mDefaultActiveAppsLock) { + mDefaultActiveApps.clear(); + } if (mDeviceIdleService == null) { return; } try { final String[] allowlistedApps = mDeviceIdleService.getFullPowerWhitelist(); - for (String app : allowlistedApps) { - mAllowlistedApps.add(app); + synchronized (mAllowlistedAppsLock) { + for (String app : allowlistedApps) { + mAllowlistedApps.add(app); + } } final String[] sysAllowlistedApps = mDeviceIdleService.getSystemPowerWhitelist(); - for (String app : sysAllowlistedApps) { - mSysAllowlistedApps.add(app); + synchronized (mSysAllowlistedAppsLock) { + for (String app : sysAllowlistedApps) { + mSysAllowlistedApps.add(app); + } } final boolean hasTelephony = mAppContext.getPackageManager().hasSystemFeature( PackageManager.FEATURE_TELEPHONY); @@ -216,14 +247,18 @@ public class PowerAllowlistBackend { if (hasTelephony) { if (defaultSms != null) { - mDefaultActiveApps.add(defaultSms.getPackageName()); + synchronized (mDefaultActiveAppsLock) { + mDefaultActiveApps.add(defaultSms.getPackageName()); + } } if (!TextUtils.isEmpty(defaultDialer)) { - mDefaultActiveApps.add(defaultDialer); + synchronized (mDefaultActiveAppsLock) { + mDefaultActiveApps.add(defaultDialer); + } } } - } catch (RemoteException e) { - Log.w(TAG, "Unable to reach IDeviceIdleController", e); + } catch (Exception e) { + Log.e(TAG, "Failed to invoke refreshList()", e); } } @@ -232,10 +267,11 @@ public class PowerAllowlistBackend { * @return a PowerAllowlistBackend object */ public static PowerAllowlistBackend getInstance(Context context) { - if (sInstance == null) { - sInstance = new PowerAllowlistBackend(context); + synchronized (PowerAllowlistBackend.class) { + if (sInstance == null) { + sInstance = new PowerAllowlistBackend(context); + } + return sInstance; } - return sInstance; } - } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ui/ScreenshotShelfView.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ui/ScreenshotShelfView.kt index bd932260848b..969cf482be90 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ui/ScreenshotShelfView.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ui/ScreenshotShelfView.kt @@ -22,6 +22,8 @@ import android.graphics.Insets import android.graphics.Rect import android.graphics.Region import android.util.AttributeSet +import android.view.GestureDetector +import android.view.GestureDetector.SimpleOnGestureListener import android.view.MotionEvent import android.view.View import android.view.ViewGroup @@ -43,13 +45,43 @@ class ScreenshotShelfView(context: Context, attrs: AttributeSet? = null) : private val displayMetrics = context.resources.displayMetrics private val tmpRect = Rect() private lateinit var actionsContainerBackground: View + private lateinit var actionsContainer: View private lateinit var dismissButton: View + // Prepare an internal `GestureDetector` to determine when we can initiate a touch-interception + // session (with the client's provided `onTouchInterceptListener`). We delegate out to their + // listener only for gestures that can't be handled by scrolling our `actionsContainer`. + private val gestureDetector = + GestureDetector( + context, + object : SimpleOnGestureListener() { + override fun onScroll( + ev1: MotionEvent?, + ev2: MotionEvent, + distanceX: Float, + distanceY: Float + ): Boolean { + actionsContainer.getBoundsOnScreen(tmpRect) + val touchedInActionsContainer = + tmpRect.contains(ev2.rawX.toInt(), ev2.rawY.toInt()) + val canHandleInternallyByScrolling = + touchedInActionsContainer + && actionsContainer.canScrollHorizontally(distanceX.toInt()) + return !canHandleInternallyByScrolling + } + } + ) + init { - setOnTouchListener({ _: View, _: MotionEvent -> + + // Delegate to the client-provided `onTouchInterceptListener` if we've already initiated + // touch-interception. + setOnTouchListener({ _: View, ev: MotionEvent -> userInteractionCallback?.invoke() - true + onTouchInterceptListener?.invoke(ev) ?: false }) + + gestureDetector.setIsLongpressEnabled(false) } override fun onFinishInflate() { @@ -60,7 +92,15 @@ class ScreenshotShelfView(context: Context, attrs: AttributeSet? = null) : blurredScreenshotPreview = requireViewById(R.id.screenshot_preview_blur) screenshotStatic = requireViewById(R.id.screenshot_static) actionsContainerBackground = requireViewById(R.id.actions_container_background) + actionsContainer = requireViewById(R.id.actions_container) dismissButton = requireViewById(R.id.screenshot_dismiss_button) + + // Configure to extend the timeout during ongoing gestures (i.e. scrolls) that are already + // being handled by our child views. + actionsContainer.setOnTouchListener({ _: View, ev: MotionEvent -> + userInteractionCallback?.invoke() + false + }) } fun getTouchRegion(gestureInsets: Insets): Region { @@ -171,9 +211,16 @@ class ScreenshotShelfView(context: Context, attrs: AttributeSet? = null) : override fun onInterceptTouchEvent(ev: MotionEvent): Boolean { userInteractionCallback?.invoke() - if (onTouchInterceptListener?.invoke(ev) == true) { - return true + // Let the client-provided listener see all `DOWN` events so that they'll be able to + // interpret the remainder of the gesture, even if interception starts partway-through. + // TODO: is this really necessary? And if we don't go on to start interception, should we + // follow up with `ACTION_CANCEL`? + if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) { + onTouchInterceptListener?.invoke(ev) } - return super.onInterceptTouchEvent(ev) + + // Only allow the client-provided touch interceptor to take over the gesture if our + // top-level `GestureDetector` decides not to scroll the action container. + return gestureDetector.onTouchEvent(ev) } } diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java index e15512678104..cd7f73c28cc0 100644 --- a/services/core/java/com/android/server/wm/BackNavigationController.java +++ b/services/core/java/com/android/server/wm/BackNavigationController.java @@ -423,7 +423,6 @@ class BackNavigationController { // Searching previous final ActivityRecord prevActivity = currentTask.getActivity((below) -> !below.finishing, currentActivity, false /*includeBoundary*/, true /*traverseTopToBottom*/); - final TaskFragment currTF = currentActivity.getTaskFragment(); if (currTF != null && currTF.asTask() == null) { // The currentActivity is embedded, search for the candidate previous activities. @@ -432,13 +431,34 @@ class BackNavigationController { outPrevActivities.add(prevActivity); return true; } - if (currTF.getAdjacentTaskFragment() != null) { - // The two TFs are adjacent (visually displayed side-by-side), search if any - // activity below the lowest one - // If companion, those two TF will be closed together. - if (currTF.getCompanionTaskFragment() != null) { + if (currTF.getAdjacentTaskFragment() == null) { + final TaskFragment nextTF = findNextTaskFragment(currentTask, currTF); + if (isSecondCompanionToFirst(currTF, nextTF)) { + // TF is isStacked, search bottom activity from companion TF. + // + // Sample hierarchy: search for underPrevious if any. + // Current TF + // Companion TF (bottomActivityInCompanion) + // Bottom Activity not inside companion TF (underPrevious) + // find bottom activity in Companion TF. + final ActivityRecord bottomActivityInCompanion = nextTF.getActivity( + (below) -> !below.finishing, false /* traverseTopToBottom */); + final ActivityRecord underPrevious = currentTask.getActivity( + (below) -> !below.finishing, bottomActivityInCompanion, + false /*includeBoundary*/, true /*traverseTopToBottom*/); + if (underPrevious != null) { + outPrevActivities.add(underPrevious); + addPreviousAdjacentActivityIfExist(underPrevious, outPrevActivities); + } + return true; + } + } else { + // If adjacent TF has companion to current TF, those two TF will be closed together. + final TaskFragment adjacentTF = currTF.getAdjacentTaskFragment(); + if (isSecondCompanionToFirst(currTF, adjacentTF)) { + // The two TFs are adjacent (visually displayed side-by-side), search if any + // activity below the lowest one. final WindowContainer commonParent = currTF.getParent(); - final TaskFragment adjacentTF = currTF.getAdjacentTaskFragment(); final TaskFragment lowerTF = commonParent.mChildren.indexOf(currTF) < commonParent.mChildren.indexOf(adjacentTF) ? currTF : adjacentTF; @@ -452,25 +472,6 @@ class BackNavigationController { // Unable to predict if no companion, it can only close current activity and make // prev Activity full screened. return false; - } else if (currTF.getCompanionTaskFragment() != null) { - // TF is isStacked, search bottom activity from companion TF. - // - // Sample hierarchy: search for underPrevious if any. - // Current TF - // Companion TF (bottomActivityInCompanion) - // Bottom Activity not inside companion TF (underPrevious) - final TaskFragment companionTF = currTF.getCompanionTaskFragment(); - // find bottom activity in Companion TF. - final ActivityRecord bottomActivityInCompanion = companionTF.getActivity( - (below) -> !below.finishing, false /* traverseTopToBottom */); - final ActivityRecord underPrevious = currentTask.getActivity( - (below) -> !below.finishing, bottomActivityInCompanion, - false /*includeBoundary*/, true /*traverseTopToBottom*/); - if (underPrevious != null) { - outPrevActivities.add(underPrevious); - addPreviousAdjacentActivityIfExist(underPrevious, outPrevActivities); - } - return true; } } @@ -485,6 +486,24 @@ class BackNavigationController { return true; } + private static TaskFragment findNextTaskFragment(@NonNull Task currentTask, + @NonNull TaskFragment topTF) { + final int topIndex = currentTask.mChildren.indexOf(topTF); + if (topIndex <= 0) { + return null; + } + final WindowContainer next = currentTask.mChildren.get(topIndex - 1); + return next.asTaskFragment(); + } + + /** + * Whether the second TF has set companion to first TF. + * When set, the second TF will be removed by organizer if the first TF is removed. + */ + private static boolean isSecondCompanionToFirst(TaskFragment first, TaskFragment second) { + return second != null && second.getCompanionTaskFragment() == first; + } + private static void addPreviousAdjacentActivityIfExist(@NonNull ActivityRecord prevActivity, @NonNull ArrayList<ActivityRecord> outPrevActivities) { final TaskFragment prevTF = prevActivity.getTaskFragment(); diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java index b75869cf3812..eea8b6a76d08 100644 --- a/services/core/java/com/android/server/wm/LetterboxUiController.java +++ b/services/core/java/com/android/server/wm/LetterboxUiController.java @@ -970,6 +970,10 @@ final class LetterboxUiController { } } + boolean isLetterboxEducationEnabled() { + return mLetterboxConfiguration.getIsEducationEnabled(); + } + /** * Whether we use split screen aspect ratio for the activity when camera compat treatment * is active because the corresponding config is enabled and activity supports resizing. @@ -1373,7 +1377,8 @@ final class LetterboxUiController { } final boolean shouldShowLetterboxUi = - (mActivityRecord.isInLetterboxAnimation() || isSurfaceVisible(mainWindow)) + (mActivityRecord.isInLetterboxAnimation() || mActivityRecord.isVisible() + || mActivityRecord.isVisibleRequested()) && mainWindow.areAppWindowBoundsLetterboxed() // Check for FLAG_SHOW_WALLPAPER explicitly instead of using // WindowContainer#showWallpaper because the later will return true when this @@ -1385,12 +1390,6 @@ final class LetterboxUiController { return shouldShowLetterboxUi; } - @VisibleForTesting - boolean isSurfaceVisible(WindowState mainWindow) { - return mainWindow.isOnScreen() && (mActivityRecord.isVisible() - || mActivityRecord.isVisibleRequested()); - } - private Color getLetterboxBackgroundColor() { final WindowState w = mActivityRecord.findMainWindow(); if (w == null || w.isLetterboxedForDisplayCutout()) { diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 94d69307e4ff..b6760c55c0cd 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -3541,6 +3541,8 @@ class Task extends TaskFragment { // Whether the direct top activity is eligible for letterbox education. appCompatTaskInfo.topActivityEligibleForLetterboxEducation = isTopActivityResumed && top.isEligibleForLetterboxEducation(); + appCompatTaskInfo.isLetterboxEducationEnabled = top != null + && top.mLetterboxUiController.isLetterboxEducationEnabled(); // Whether the direct top activity requested showing camera compat control. appCompatTaskInfo.cameraCompatControlState = isTopActivityResumed ? top.getCameraCompatControlState() diff --git a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java index c29547f123aa..9b2a880881f8 100644 --- a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java @@ -291,13 +291,22 @@ public class BackNavigationControllerTests extends WindowTestsBase { assertTrue(predictable); outPrevActivities.clear(); - // Stacked + companion => predict for previous task + // Stacked + top companion to bottom but bottom didn't => predict for previous activity tf2.setCompanionTaskFragment(tf1); predictable = BackNavigationController.getAnimatablePrevActivities(task, topAr, outPrevActivities); - assertTrue(outPrevActivities.isEmpty()); + assertTrue(outPrevActivities.contains(prevAr)); assertTrue(predictable); tf2.setCompanionTaskFragment(null); + outPrevActivities.clear(); + + // Stacked + next companion to top => predict for previous task + tf1.setCompanionTaskFragment(tf2); + predictable = BackNavigationController.getAnimatablePrevActivities(task, topAr, + outPrevActivities); + assertTrue(outPrevActivities.isEmpty()); + assertTrue(predictable); + tf1.setCompanionTaskFragment(null); // Adjacent + no companion => unable to predict // TF1 | TF2 @@ -314,11 +323,13 @@ public class BackNavigationControllerTests extends WindowTestsBase { // Adjacent + companion => predict for previous task tf1.setCompanionTaskFragment(tf2); - tf2.setCompanionTaskFragment(tf1); predictable = BackNavigationController.getAnimatablePrevActivities(task, topAr, outPrevActivities); assertTrue(outPrevActivities.isEmpty()); assertTrue(predictable); + tf1.setCompanionTaskFragment(null); + + tf2.setCompanionTaskFragment(tf1); predictable = BackNavigationController.getAnimatablePrevActivities(task, prevAr, outPrevActivities); assertTrue(outPrevActivities.isEmpty()); @@ -361,18 +372,27 @@ public class BackNavigationControllerTests extends WindowTestsBase { tf3.setAdjacentTaskFragment(null); final TaskFragment tf4 = createTaskFragmentWithActivity(task); - // Stacked + companion => predict for previous activity below companion. + // Stacked + next companion to top => predict for previous activity below companion. // Tf4 // TF3 // TF2 // TF1 - tf4.setCompanionTaskFragment(tf3); tf3.setCompanionTaskFragment(tf4); topAr = tf4.getTopMostActivity(); predictable = BackNavigationController.getAnimatablePrevActivities(task, topAr, outPrevActivities); assertTrue(outPrevActivities.contains(tf2.getTopMostActivity())); assertTrue(predictable); + outPrevActivities.clear(); + tf3.setCompanionTaskFragment(null); + + // Stacked + top companion to next but next one didn't => predict for previous activity. + tf4.setCompanionTaskFragment(tf3); + topAr = tf4.getTopMostActivity(); + predictable = BackNavigationController.getAnimatablePrevActivities(task, topAr, + outPrevActivities); + assertTrue(outPrevActivities.contains(tf3.getTopMostActivity())); + assertTrue(predictable); } @Test diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java index 6806f6a9e461..ed205a5a3ffb 100644 --- a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java @@ -609,7 +609,8 @@ public class LetterboxUiControllerTest extends WindowTestsBase { doReturn(false).when(mActivity).isInLetterboxAnimation(); assertEquals(expectedRadius, mController.getRoundedCornersRadius(mainWindow)); - doReturn(false).when(mainWindow).isOnScreen(); + doReturn(false).when(mActivity).isVisibleRequested(); + doReturn(false).when(mActivity).isVisible(); assertEquals(0, mController.getRoundedCornersRadius(mainWindow)); doReturn(true).when(mActivity).isInLetterboxAnimation(); @@ -1581,6 +1582,12 @@ public class LetterboxUiControllerTest extends WindowTestsBase { assertTrue(mController.allowHorizontalReachabilityForThinLetterbox()); } + @Test + public void testIsLetterboxEducationEnabled() { + mController.isLetterboxEducationEnabled(); + verify(mLetterboxConfiguration).getIsEducationEnabled(); + } + private void mockThatProperty(String propertyName, boolean value) throws Exception { Property property = new Property(propertyName, /* value */ value, /* packageName */ "", /* className */ ""); 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 36c1c8cc7659..bf15bc89be97 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java @@ -909,8 +909,7 @@ public class SizeCompatTests extends WindowTestsBase { assertEquals(window, mActivity.findMainWindow()); spyOn(mActivity.mLetterboxUiController); - doReturn(true).when(mActivity.mLetterboxUiController) - .isSurfaceVisible(any()); + doReturn(true).when(mActivity).isVisibleRequested(); assertTrue(mActivity.mLetterboxUiController.shouldShowLetterboxUi( mActivity.findMainWindow())); diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index 88acbabc0e0f..25d8a3db566d 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -13112,39 +13112,41 @@ public class TelephonyManager { }) @RequiresFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS) public @Nullable ServiceState getServiceState(@IncludeLocationData int includeLocationData) { - return getServiceStateForSubscriber(getSubId(), + return getServiceStateForSlot(SubscriptionManager.getSlotIndex(getSubId()), includeLocationData != INCLUDE_LOCATION_DATA_FINE, includeLocationData == INCLUDE_LOCATION_DATA_NONE); } /** - * Returns the service state information on specified subscription. Callers require - * either READ_PRIVILEGED_PHONE_STATE or READ_PHONE_STATE to retrieve the information. + * Returns the service state information on specified SIM slot. * - * May return {@code null} when the subscription is inactive or when there was an error + * May return {@code null} when the {@code slotIndex} is invalid or when there was an error * communicating with the phone process. + * + * @param slotIndex of phone whose service state is returned * @param renounceFineLocationAccess Set this to true if the caller would not like to receive * location related information which will be sent if the caller already possess * {@link android.Manifest.permission#ACCESS_FINE_LOCATION} and do not renounce the permission * @param renounceCoarseLocationAccess Set this to true if the caller would not like to * receive location related information which will be sent if the caller already possess * {@link Manifest.permission#ACCESS_COARSE_LOCATION} and do not renounce the permissions. + * @return Service state on specified SIM slot. */ - private ServiceState getServiceStateForSubscriber(int subId, - boolean renounceFineLocationAccess, + private ServiceState getServiceStateForSlot(int slotIndex, boolean renounceFineLocationAccess, boolean renounceCoarseLocationAccess) { try { ITelephony service = getITelephony(); if (service != null) { - return service.getServiceStateForSubscriber(subId, renounceFineLocationAccess, - renounceCoarseLocationAccess, getOpPackageName(), getAttributionTag()); + return service.getServiceStateForSlot(slotIndex, + renounceFineLocationAccess, renounceCoarseLocationAccess, + getOpPackageName(), getAttributionTag()); } } catch (RemoteException e) { - Log.e(TAG, "Error calling ITelephony#getServiceStateForSubscriber", e); + Log.e(TAG, "Error calling ITelephony#getServiceStateForSlot", e); } catch (NullPointerException e) { AnomalyReporter.reportAnomaly( UUID.fromString("e2bed88e-def9-476e-bd71-3e572a8de6d1"), - "getServiceStateForSubscriber " + subId + " NPE"); + "getServiceStateForSlot " + slotIndex + " NPE"); } return null; } @@ -13159,7 +13161,35 @@ public class TelephonyManager { */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) public ServiceState getServiceStateForSubscriber(int subId) { - return getServiceStateForSubscriber(subId, false, false); + return getServiceStateForSlot( + SubscriptionManager.getSlotIndex(subId), false, false); + } + + /** + * Returns the service state information on specified SIM slot. + * + * If you want continuous updates of service state info, register a {@link TelephonyCallback} + * that implements {@link TelephonyCallback.ServiceStateListener} through + * {@link #registerTelephonyCallback}. + * + * May return {@code null} when the {@code slotIndex} is invalid or when there was an error + * communicating with the phone process + * + * See {@link #getActiveModemCount()} to get the total number of slots + * that are active on the device. + * + * @param slotIndex of phone whose service state is returned + * @return ServiceState on specified SIM slot. + * + * @hide + */ + @RequiresPermission(allOf = { + Manifest.permission.READ_PHONE_STATE, + Manifest.permission.ACCESS_COARSE_LOCATION + }) + @RequiresFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS) + public @Nullable ServiceState getServiceStateForSlot(int slotIndex) { + return getServiceStateForSlot(slotIndex, false, false); } /** diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java index 0bb5fd513fd1..47f53f372d33 100644 --- a/telephony/java/android/telephony/satellite/SatelliteManager.java +++ b/telephony/java/android/telephony/satellite/SatelliteManager.java @@ -1015,13 +1015,27 @@ public final class SatelliteManager { * @hide */ public static final int DATAGRAM_TYPE_KEEP_ALIVE = 3; + /** + * Datagram type indicating that the datagram to be sent or received is of type SOS message and + * is the last message to emergency service provider indicating still needs help. + * @hide + */ + public static final int DATAGRAM_TYPE_LAST_SOS_MESSAGE_STILL_NEED_HELP = 4; + /** + * Datagram type indicating that the datagram to be sent or received is of type SOS message and + * is the last message to emergency service provider indicating no more help is needed. + * @hide + */ + public static final int DATAGRAM_TYPE_LAST_SOS_MESSAGE_NO_HELP_NEEDED = 5; /** @hide */ @IntDef(prefix = "DATAGRAM_TYPE_", value = { DATAGRAM_TYPE_UNKNOWN, DATAGRAM_TYPE_SOS_MESSAGE, DATAGRAM_TYPE_LOCATION_SHARING, - DATAGRAM_TYPE_KEEP_ALIVE + DATAGRAM_TYPE_KEEP_ALIVE, + DATAGRAM_TYPE_LAST_SOS_MESSAGE_STILL_NEED_HELP, + DATAGRAM_TYPE_LAST_SOS_MESSAGE_NO_HELP_NEEDED }) @Retention(RetentionPolicy.SOURCE) public @interface DatagramType {} diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl index f591f40799d6..ccc68bcef582 100644 --- a/telephony/java/com/android/internal/telephony/ITelephony.aidl +++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl @@ -1399,19 +1399,18 @@ interface ITelephony { oneway void requestModemActivityInfo(in ResultReceiver result); /** - * Get the service state on specified subscription - * @param subId Subscription id + * Get the service state on specified SIM slot. + * @param slotIndex of phone whose service state is returned * @param renounceFineLocationAccess Set this to true if the caller would not like to * receive fine location related information * @param renounceCoarseLocationAccess Set this to true if the caller would not like to * receive coarse location related information * @param callingPackage The package making the call * @param callingFeatureId The feature in the package - * @return Service state on specified subscription. + * @return Service state on specified SIM slot. */ - ServiceState getServiceStateForSubscriber(int subId, boolean renounceFineLocationAccess, - boolean renounceCoarseLocationAccess, - String callingPackage, String callingFeatureId); + ServiceState getServiceStateForSlot(int slotIndex, boolean renounceFineLocationAccess, + boolean renounceCoarseLocationAccess, String callingPackage, String callingFeatureId); /** * Returns the URI for the per-account voicemail ringtone set in Phone settings. |