Letterbox Reacheability Multiplier Persistence
Persist values for horizontal and vertical position multipliers.
Test: atest LetterboxConfigurationTest LetterboxConfigurationPersisterTest
Fixes: 199434061
Change-Id: I194828be402ff1fd234e371055522962647a4d32
diff --git a/proto/src/task_snapshot.proto b/proto/src/task_snapshot.proto
deleted file mode 100644
index 1cbc17e..0000000
--- a/proto/src/task_snapshot.proto
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Copyright (C) 2017 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.
- */
-
- syntax = "proto3";
-
- package com.android.server.wm;
-
- option java_package = "com.android.server.wm";
- option java_outer_classname = "WindowManagerProtos";
-
- message TaskSnapshotProto {
- int32 orientation = 1;
- int32 inset_left = 2;
- int32 inset_top = 3;
- int32 inset_right = 4;
- int32 inset_bottom = 5;
- bool is_real_snapshot = 6;
- int32 windowing_mode = 7;
- int32 system_ui_visibility = 8 [deprecated=true];
- bool is_translucent = 9;
- string top_activity_component = 10;
- // deprecated because original width and height are stored now instead of the scale.
- float legacy_scale = 11 [deprecated=true];
- int64 id = 12;
- int32 rotation = 13;
- // The task width when the snapshot was taken
- int32 task_width = 14;
- // The task height when the snapshot was taken
- int32 task_height = 15;
- int32 appearance = 16;
- int32 letterbox_inset_left = 17;
- int32 letterbox_inset_top = 18;
- int32 letterbox_inset_right = 19;
- int32 letterbox_inset_bottom = 20;
- }
diff --git a/proto/src/windowmanager.proto b/proto/src/windowmanager.proto
new file mode 100644
index 0000000..f26404c6
--- /dev/null
+++ b/proto/src/windowmanager.proto
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+syntax = "proto3";
+
+package com.android.server.wm;
+
+option java_package = "com.android.server.wm";
+option java_outer_classname = "WindowManagerProtos";
+
+message TaskSnapshotProto {
+ int32 orientation = 1;
+ int32 inset_left = 2;
+ int32 inset_top = 3;
+ int32 inset_right = 4;
+ int32 inset_bottom = 5;
+ bool is_real_snapshot = 6;
+ int32 windowing_mode = 7;
+ int32 system_ui_visibility = 8 [deprecated=true];
+ bool is_translucent = 9;
+ string top_activity_component = 10;
+ // deprecated because original width and height are stored now instead of the scale.
+ float legacy_scale = 11 [deprecated=true];
+ int64 id = 12;
+ int32 rotation = 13;
+ // The task width when the snapshot was taken
+ int32 task_width = 14;
+ // The task height when the snapshot was taken
+ int32 task_height = 15;
+ int32 appearance = 16;
+ int32 letterbox_inset_left = 17;
+ int32 letterbox_inset_top = 18;
+ int32 letterbox_inset_right = 19;
+ int32 letterbox_inset_bottom = 20;
+}
+
+// Persistent letterboxing configurations
+message LetterboxProto {
+
+ // Possible values for the letterbox horizontal reachability
+ enum LetterboxHorizontalReachability {
+ LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT = 0;
+ LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_CENTER = 1;
+ LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_RIGHT = 2;
+ }
+
+ // Possible values for the letterbox vertical reachability
+ enum LetterboxVerticalReachability {
+ LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP = 0;
+ LETTERBOX_VERTICAL_REACHABILITY_POSITION_CENTER = 1;
+ LETTERBOX_VERTICAL_REACHABILITY_POSITION_BOTTOM = 2;
+ }
+
+ // Represents the current horizontal position for the letterboxed activity
+ LetterboxHorizontalReachability letterbox_position_for_horizontal_reachability = 1;
+ // Represents the current vertical position for the letterboxed activity
+ LetterboxVerticalReachability letterbox_position_for_vertical_reachability = 2;
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/wm/LetterboxConfiguration.java b/services/core/java/com/android/server/wm/LetterboxConfiguration.java
index a469c6b..c19353c 100644
--- a/services/core/java/com/android/server/wm/LetterboxConfiguration.java
+++ b/services/core/java/com/android/server/wm/LetterboxConfiguration.java
@@ -17,14 +17,17 @@
package com.android.server.wm;
import android.annotation.IntDef;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.graphics.Color;
import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.function.Function;
/** Reads letterbox configs from resources and controls their overrides at runtime. */
final class LetterboxConfiguration {
@@ -156,34 +159,25 @@
// portrait device orientation.
private boolean mIsVerticalReachabilityEnabled;
-
- // Horizontal position of a center of the letterboxed app window which is global to prevent
- // "jumps" when switching between letterboxed apps. It's updated to reposition the app window
- // in response to a double tap gesture (see LetterboxUiController#handleDoubleTap). Used in
- // LetterboxUiController#getHorizontalPositionMultiplier which is called from
- // ActivityRecord#updateResolvedBoundsPosition.
- // TODO(b/199426138): Global reachability setting causes a jump when resuming an app from
- // Overview after changing position in another app.
- @LetterboxHorizontalReachabilityPosition
- private volatile int mLetterboxPositionForHorizontalReachability;
-
- // Vertical position of a center of the letterboxed app window which is global to prevent
- // "jumps" when switching between letterboxed apps. It's updated to reposition the app window
- // in response to a double tap gesture (see LetterboxUiController#handleDoubleTap). Used in
- // LetterboxUiController#getVerticalPositionMultiplier which is called from
- // ActivityRecord#updateResolvedBoundsPosition.
- // TODO(b/199426138): Global reachability setting causes a jump when resuming an app from
- // Overview after changing position in another app.
- @LetterboxVerticalReachabilityPosition
- private volatile int mLetterboxPositionForVerticalReachability;
-
// Whether education is allowed for letterboxed fullscreen apps.
private boolean mIsEducationEnabled;
// Whether using split screen aspect ratio as a default aspect ratio for unresizable apps.
private boolean mIsSplitScreenAspectRatioForUnresizableAppsEnabled;
+ // Responsible for the persistence of letterbox[Horizontal|Vertical]PositionMultiplier
+ @NonNull
+ private final LetterboxConfigurationPersister mLetterboxConfigurationPersister;
+
LetterboxConfiguration(Context systemUiContext) {
+ this(systemUiContext, new LetterboxConfigurationPersister(systemUiContext,
+ () -> readLetterboxHorizontalReachabilityPositionFromConfig(systemUiContext),
+ () -> readLetterboxVerticalReachabilityPositionFromConfig(systemUiContext)));
+ }
+
+ @VisibleForTesting
+ LetterboxConfiguration(Context systemUiContext,
+ LetterboxConfigurationPersister letterboxConfigurationPersister) {
mContext = systemUiContext;
mFixedOrientationLetterboxAspectRatio = mContext.getResources().getFloat(
R.dimen.config_fixedOrientationLetterboxAspectRatio);
@@ -206,14 +200,14 @@
readLetterboxHorizontalReachabilityPositionFromConfig(mContext);
mDefaultPositionForVerticalReachability =
readLetterboxVerticalReachabilityPositionFromConfig(mContext);
- mLetterboxPositionForHorizontalReachability = mDefaultPositionForHorizontalReachability;
- mLetterboxPositionForVerticalReachability = mDefaultPositionForVerticalReachability;
mIsEducationEnabled = mContext.getResources().getBoolean(
R.bool.config_letterboxIsEducationEnabled);
setDefaultMinAspectRatioForUnresizableApps(mContext.getResources().getFloat(
R.dimen.config_letterboxDefaultMinAspectRatioForUnresizableApps));
mIsSplitScreenAspectRatioForUnresizableAppsEnabled = mContext.getResources().getBoolean(
R.bool.config_letterboxIsSplitScreenAspectRatioForUnresizableAppsEnabled);
+ mLetterboxConfigurationPersister = letterboxConfigurationPersister;
+ mLetterboxConfigurationPersister.start();
}
/**
@@ -653,7 +647,9 @@
* <p>The position multiplier is changed after each double tap in the letterbox area.
*/
float getHorizontalMultiplierForReachability() {
- switch (mLetterboxPositionForHorizontalReachability) {
+ final int letterboxPositionForHorizontalReachability =
+ mLetterboxConfigurationPersister.getLetterboxPositionForHorizontalReachability();
+ switch (letterboxPositionForHorizontalReachability) {
case LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT:
return 0.0f;
case LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_CENTER:
@@ -662,10 +658,11 @@
return 1.0f;
default:
throw new AssertionError(
- "Unexpected letterbox position type: "
- + mLetterboxPositionForHorizontalReachability);
+ "Unexpected letterbox position type: "
+ + letterboxPositionForHorizontalReachability);
}
}
+
/*
* Gets vertical position of a center of the letterboxed app window when reachability
* is enabled specified. 0 corresponds to the top side of the screen and 1 to the bottom side.
@@ -673,7 +670,9 @@
* <p>The position multiplier is changed after each double tap in the letterbox area.
*/
float getVerticalMultiplierForReachability() {
- switch (mLetterboxPositionForVerticalReachability) {
+ final int letterboxPositionForVerticalReachability =
+ mLetterboxConfigurationPersister.getLetterboxPositionForVerticalReachability();
+ switch (letterboxPositionForVerticalReachability) {
case LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP:
return 0.0f;
case LETTERBOX_VERTICAL_REACHABILITY_POSITION_CENTER:
@@ -683,7 +682,7 @@
default:
throw new AssertionError(
"Unexpected letterbox position type: "
- + mLetterboxPositionForVerticalReachability);
+ + letterboxPositionForVerticalReachability);
}
}
@@ -693,7 +692,7 @@
*/
@LetterboxHorizontalReachabilityPosition
int getLetterboxPositionForHorizontalReachability() {
- return mLetterboxPositionForHorizontalReachability;
+ return mLetterboxConfigurationPersister.getLetterboxPositionForHorizontalReachability();
}
/*
@@ -702,7 +701,7 @@
*/
@LetterboxVerticalReachabilityPosition
int getLetterboxPositionForVerticalReachability() {
- return mLetterboxPositionForVerticalReachability;
+ return mLetterboxConfigurationPersister.getLetterboxPositionForVerticalReachability();
}
/** Returns a string representing the given {@link LetterboxHorizontalReachabilityPosition}. */
@@ -742,9 +741,8 @@
* right side.
*/
void movePositionForHorizontalReachabilityToNextRightStop() {
- mLetterboxPositionForHorizontalReachability = Math.min(
- mLetterboxPositionForHorizontalReachability + 1,
- LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_RIGHT);
+ updatePositionForHorizontalReachability(prev -> Math.min(
+ prev + 1, LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_RIGHT));
}
/**
@@ -752,8 +750,7 @@
* side.
*/
void movePositionForHorizontalReachabilityToNextLeftStop() {
- mLetterboxPositionForHorizontalReachability =
- Math.max(mLetterboxPositionForHorizontalReachability - 1, 0);
+ updatePositionForHorizontalReachability(prev -> Math.max(prev - 1, 0));
}
/**
@@ -761,9 +758,8 @@
* side.
*/
void movePositionForVerticalReachabilityToNextBottomStop() {
- mLetterboxPositionForVerticalReachability = Math.min(
- mLetterboxPositionForVerticalReachability + 1,
- LETTERBOX_VERTICAL_REACHABILITY_POSITION_BOTTOM);
+ updatePositionForVerticalReachability(prev -> Math.min(
+ prev + 1, LETTERBOX_VERTICAL_REACHABILITY_POSITION_BOTTOM));
}
/**
@@ -771,8 +767,7 @@
* side.
*/
void movePositionForVerticalReachabilityToNextTopStop() {
- mLetterboxPositionForVerticalReachability =
- Math.max(mLetterboxPositionForVerticalReachability - 1, 0);
+ updatePositionForVerticalReachability(prev -> Math.max(prev - 1, 0));
}
/**
@@ -822,4 +817,26 @@
R.bool.config_letterboxIsSplitScreenAspectRatioForUnresizableAppsEnabled);
}
+ /** Calculates a new letterboxPositionForHorizontalReachability value and updates the store */
+ private void updatePositionForHorizontalReachability(
+ Function<Integer, Integer> newHorizonalPositionFun) {
+ final int letterboxPositionForHorizontalReachability =
+ mLetterboxConfigurationPersister.getLetterboxPositionForHorizontalReachability();
+ final int nextHorizontalPosition = newHorizonalPositionFun.apply(
+ letterboxPositionForHorizontalReachability);
+ mLetterboxConfigurationPersister.setLetterboxPositionForHorizontalReachability(
+ nextHorizontalPosition);
+ }
+
+ /** Calculates a new letterboxPositionForVerticalReachability value and updates the store */
+ private void updatePositionForVerticalReachability(
+ Function<Integer, Integer> newVerticalPositionFun) {
+ final int letterboxPositionForVerticalReachability =
+ mLetterboxConfigurationPersister.getLetterboxPositionForVerticalReachability();
+ final int nextVerticalPosition = newVerticalPositionFun.apply(
+ letterboxPositionForVerticalReachability);
+ mLetterboxConfigurationPersister.setLetterboxPositionForVerticalReachability(
+ nextVerticalPosition);
+ }
+
}
diff --git a/services/core/java/com/android/server/wm/LetterboxConfigurationPersister.java b/services/core/java/com/android/server/wm/LetterboxConfigurationPersister.java
new file mode 100644
index 0000000..70639b1
--- /dev/null
+++ b/services/core/java/com/android/server/wm/LetterboxConfigurationPersister.java
@@ -0,0 +1,259 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.os.Environment;
+import android.util.AtomicFile;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.wm.LetterboxConfiguration.LetterboxHorizontalReachabilityPosition;
+import com.android.server.wm.LetterboxConfiguration.LetterboxVerticalReachabilityPosition;
+import com.android.server.wm.nano.WindowManagerProtos;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+
+/**
+ * Persists the values of letterboxPositionForHorizontalReachability and
+ * letterboxPositionForVerticalReachability for {@link LetterboxConfiguration}.
+ */
+class LetterboxConfigurationPersister {
+
+ private static final String TAG =
+ TAG_WITH_CLASS_NAME ? "LetterboxConfigurationPersister" : TAG_WM;
+
+ @VisibleForTesting
+ static final String LETTERBOX_CONFIGURATION_FILENAME = "letterbox_config";
+
+ private final Context mContext;
+ private final Supplier<Integer> mDefaultHorizontalReachabilitySupplier;
+ private final Supplier<Integer> mDefaultVerticalReachabilitySupplier;
+
+ // Horizontal position of a center of the letterboxed app window which is global to prevent
+ // "jumps" when switching between letterboxed apps. It's updated to reposition the app window
+ // in response to a double tap gesture (see LetterboxUiController#handleDoubleTap). Used in
+ // LetterboxUiController#getHorizontalPositionMultiplier which is called from
+ // ActivityRecord#updateResolvedBoundsPosition.
+ @LetterboxHorizontalReachabilityPosition
+ private volatile int mLetterboxPositionForHorizontalReachability;
+
+ // Vertical position of a center of the letterboxed app window which is global to prevent
+ // "jumps" when switching between letterboxed apps. It's updated to reposition the app window
+ // in response to a double tap gesture (see LetterboxUiController#handleDoubleTap). Used in
+ // LetterboxUiController#getVerticalPositionMultiplier which is called from
+ // ActivityRecord#updateResolvedBoundsPosition.
+ @LetterboxVerticalReachabilityPosition
+ private volatile int mLetterboxPositionForVerticalReachability;
+
+ @NonNull
+ private final AtomicFile mConfigurationFile;
+
+ @Nullable
+ private final Consumer<String> mCompletionCallback;
+
+ @NonNull
+ private final PersisterQueue mPersisterQueue;
+
+ LetterboxConfigurationPersister(Context systemUiContext,
+ Supplier<Integer> defaultHorizontalReachabilitySupplier,
+ Supplier<Integer> defaultVerticalReachabilitySupplier) {
+ this(systemUiContext, defaultHorizontalReachabilitySupplier,
+ defaultVerticalReachabilitySupplier,
+ Environment.getDataSystemDirectory(), new PersisterQueue(),
+ /* completionCallback */ null);
+ }
+
+ @VisibleForTesting
+ LetterboxConfigurationPersister(Context systemUiContext,
+ Supplier<Integer> defaultHorizontalReachabilitySupplier,
+ Supplier<Integer> defaultVerticalReachabilitySupplier, File configFolder,
+ PersisterQueue persisterQueue, @Nullable Consumer<String> completionCallback) {
+ mContext = systemUiContext.createDeviceProtectedStorageContext();
+ mDefaultHorizontalReachabilitySupplier = defaultHorizontalReachabilitySupplier;
+ mDefaultVerticalReachabilitySupplier = defaultVerticalReachabilitySupplier;
+ mCompletionCallback = completionCallback;
+ final File prefFiles = new File(configFolder, LETTERBOX_CONFIGURATION_FILENAME);
+ mConfigurationFile = new AtomicFile(prefFiles);
+ mPersisterQueue = persisterQueue;
+ readCurrentConfiguration();
+ }
+
+ /**
+ * Startes the persistence queue
+ */
+ void start() {
+ mPersisterQueue.startPersisting();
+ }
+
+ /*
+ * Gets the horizontal position of the letterboxed app window when horizontal reachability is
+ * enabled.
+ */
+ @LetterboxHorizontalReachabilityPosition
+ int getLetterboxPositionForHorizontalReachability() {
+ return mLetterboxPositionForHorizontalReachability;
+ }
+
+ /*
+ * Gets the vertical position of the letterboxed app window when vertical reachability is
+ * enabled.
+ */
+ @LetterboxVerticalReachabilityPosition
+ int getLetterboxPositionForVerticalReachability() {
+ return mLetterboxPositionForVerticalReachability;
+ }
+
+ /**
+ * Updates letterboxPositionForVerticalReachability if different from the current value
+ */
+ void setLetterboxPositionForHorizontalReachability(
+ int letterboxPositionForHorizontalReachability) {
+ if (mLetterboxPositionForHorizontalReachability
+ != letterboxPositionForHorizontalReachability) {
+ mLetterboxPositionForHorizontalReachability =
+ letterboxPositionForHorizontalReachability;
+ updateConfiguration();
+ }
+ }
+
+ /**
+ * Updates letterboxPositionForVerticalReachability if different from the current value
+ */
+ void setLetterboxPositionForVerticalReachability(
+ int letterboxPositionForVerticalReachability) {
+ if (mLetterboxPositionForVerticalReachability != letterboxPositionForVerticalReachability) {
+ mLetterboxPositionForVerticalReachability = letterboxPositionForVerticalReachability;
+ updateConfiguration();
+ }
+ }
+
+ @VisibleForTesting
+ void useDefaultValue() {
+ mLetterboxPositionForHorizontalReachability = mDefaultHorizontalReachabilitySupplier.get();
+ mLetterboxPositionForVerticalReachability = mDefaultVerticalReachabilitySupplier.get();
+ }
+
+ private void readCurrentConfiguration() {
+ FileInputStream fis = null;
+ try {
+ fis = mConfigurationFile.openRead();
+ byte[] protoData = readInputStream(fis);
+ final WindowManagerProtos.LetterboxProto letterboxData =
+ WindowManagerProtos.LetterboxProto.parseFrom(protoData);
+ mLetterboxPositionForHorizontalReachability =
+ letterboxData.letterboxPositionForHorizontalReachability;
+ mLetterboxPositionForVerticalReachability =
+ letterboxData.letterboxPositionForVerticalReachability;
+ } catch (IOException ioe) {
+ Slog.e(TAG,
+ "Error reading from LetterboxConfigurationPersister. "
+ + "Using default values!", ioe);
+ useDefaultValue();
+ } finally {
+ if (fis != null) {
+ try {
+ fis.close();
+ } catch (IOException e) {
+ useDefaultValue();
+ Slog.e(TAG, "Error reading from LetterboxConfigurationPersister ", e);
+ }
+ }
+ }
+ }
+
+ private void updateConfiguration() {
+ mPersisterQueue.addItem(new UpdateValuesCommand(mConfigurationFile,
+ mLetterboxPositionForHorizontalReachability,
+ mLetterboxPositionForVerticalReachability,
+ mCompletionCallback), /* flush */ true);
+ }
+
+ private static byte[] readInputStream(InputStream in) throws IOException {
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ try {
+ byte[] buffer = new byte[1024];
+ int size = in.read(buffer);
+ while (size > 0) {
+ outputStream.write(buffer, 0, size);
+ size = in.read(buffer);
+ }
+ return outputStream.toByteArray();
+ } finally {
+ outputStream.close();
+ }
+ }
+
+ private static class UpdateValuesCommand implements
+ PersisterQueue.WriteQueueItem<UpdateValuesCommand> {
+
+ @NonNull
+ private final AtomicFile mFileToUpdate;
+ @Nullable
+ private final Consumer<String> mOnComplete;
+
+
+ private final int mHorizontalReachability;
+ private final int mVerticalReachability;
+
+ UpdateValuesCommand(@NonNull AtomicFile fileToUpdate,
+ int horizontalReachability, int verticalReachability,
+ @Nullable Consumer<String> onComplete) {
+ mFileToUpdate = fileToUpdate;
+ mHorizontalReachability = horizontalReachability;
+ mVerticalReachability = verticalReachability;
+ mOnComplete = onComplete;
+ }
+
+ @Override
+ public void process() {
+ final WindowManagerProtos.LetterboxProto letterboxData =
+ new WindowManagerProtos.LetterboxProto();
+ letterboxData.letterboxPositionForHorizontalReachability = mHorizontalReachability;
+ letterboxData.letterboxPositionForVerticalReachability = mVerticalReachability;
+ final byte[] bytes = WindowManagerProtos.LetterboxProto.toByteArray(letterboxData);
+
+ FileOutputStream fos = null;
+ try {
+ fos = mFileToUpdate.startWrite();
+ fos.write(bytes);
+ mFileToUpdate.finishWrite(fos);
+ } catch (IOException ioe) {
+ mFileToUpdate.failWrite(fos);
+ Slog.e(TAG,
+ "Error writing to LetterboxConfigurationPersister. "
+ + "Using default values!", ioe);
+ } finally {
+ if (mOnComplete != null) {
+ mOnComplete.accept("UpdateValuesCommand");
+ }
+ }
+ }
+ }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationPersisterTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationPersisterTest.java
new file mode 100644
index 0000000..1246d1e
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationPersisterTest.java
@@ -0,0 +1,263 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT;
+import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP;
+import static com.android.server.wm.LetterboxConfigurationPersister.LETTERBOX_CONFIGURATION_FILENAME;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.platform.test.annotations.Presubmit;
+import android.util.AtomicFile;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
+
+import junit.framework.Assert;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.File;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Consumer;
+
+@SmallTest
+@Presubmit
+public class LetterboxConfigurationPersisterTest {
+
+ private static final long TIMEOUT = 2000L; // 2 secs
+
+ private LetterboxConfigurationPersister mLetterboxConfigurationPersister;
+ private Context mContext;
+ private PersisterQueue mPersisterQueue;
+ private QueueState mQueueState;
+ private PersisterQueue.Listener mQueueListener;
+ private File mConfigFolder;
+
+ @Before
+ public void setUp() throws Exception {
+ mContext = getInstrumentation().getTargetContext();
+ mConfigFolder = mContext.getFilesDir();
+ mPersisterQueue = new PersisterQueue();
+ mQueueState = new QueueState();
+ mLetterboxConfigurationPersister = new LetterboxConfigurationPersister(mContext,
+ () -> mContext.getResources().getInteger(
+ R.integer.config_letterboxDefaultPositionForHorizontalReachability),
+ () -> mContext.getResources().getInteger(
+ R.integer.config_letterboxDefaultPositionForVerticalReachability),
+ mConfigFolder, mPersisterQueue, mQueueState);
+ mQueueListener = queueEmpty -> mQueueState.onItemAdded();
+ mPersisterQueue.addListener(mQueueListener);
+ mLetterboxConfigurationPersister.start();
+ }
+
+ public void tearDown() throws InterruptedException {
+ deleteConfiguration(mLetterboxConfigurationPersister, mPersisterQueue);
+ waitForCompletion(mPersisterQueue);
+ mPersisterQueue.removeListener(mQueueListener);
+ stopPersisterSafe(mPersisterQueue);
+ }
+
+ @Test
+ public void test_whenStoreIsCreated_valuesAreDefaults() {
+ final int positionForHorizontalReachability =
+ mLetterboxConfigurationPersister.getLetterboxPositionForHorizontalReachability();
+ final int defaultPositionForHorizontalReachability =
+ mContext.getResources().getInteger(
+ R.integer.config_letterboxDefaultPositionForHorizontalReachability);
+ Assert.assertEquals(defaultPositionForHorizontalReachability,
+ positionForHorizontalReachability);
+ final int positionForVerticalReachability =
+ mLetterboxConfigurationPersister.getLetterboxPositionForVerticalReachability();
+ final int defaultPositionForVerticalReachability =
+ mContext.getResources().getInteger(
+ R.integer.config_letterboxDefaultPositionForVerticalReachability);
+ Assert.assertEquals(defaultPositionForVerticalReachability,
+ positionForVerticalReachability);
+ }
+
+ @Test
+ public void test_whenUpdatedWithNewValues_valuesAreWritten() {
+ mLetterboxConfigurationPersister.setLetterboxPositionForHorizontalReachability(
+ LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT);
+ mLetterboxConfigurationPersister.setLetterboxPositionForVerticalReachability(
+ LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP);
+ waitForCompletion(mPersisterQueue);
+ final int newPositionForHorizontalReachability =
+ mLetterboxConfigurationPersister.getLetterboxPositionForHorizontalReachability();
+ final int newPositionForVerticalReachability =
+ mLetterboxConfigurationPersister.getLetterboxPositionForVerticalReachability();
+ Assert.assertEquals(LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT,
+ newPositionForHorizontalReachability);
+ Assert.assertEquals(LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP,
+ newPositionForVerticalReachability);
+ }
+
+ @Test
+ public void test_whenUpdatedWithNewValues_valuesAreReadAfterRestart() {
+ final PersisterQueue firstPersisterQueue = new PersisterQueue();
+ final LetterboxConfigurationPersister firstPersister = new LetterboxConfigurationPersister(
+ mContext, () -> -1, () -> -1, mContext.getFilesDir(), firstPersisterQueue,
+ mQueueState);
+ firstPersister.start();
+ firstPersister.setLetterboxPositionForHorizontalReachability(
+ LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT);
+ firstPersister.setLetterboxPositionForVerticalReachability(
+ LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP);
+ waitForCompletion(firstPersisterQueue);
+ stopPersisterSafe(firstPersisterQueue);
+ final PersisterQueue secondPersisterQueue = new PersisterQueue();
+ final LetterboxConfigurationPersister secondPersister = new LetterboxConfigurationPersister(
+ mContext, () -> -1, () -> -1, mContext.getFilesDir(), secondPersisterQueue,
+ mQueueState);
+ secondPersister.start();
+ final int newPositionForHorizontalReachability =
+ secondPersister.getLetterboxPositionForHorizontalReachability();
+ final int newPositionForVerticalReachability =
+ secondPersister.getLetterboxPositionForVerticalReachability();
+ Assert.assertEquals(LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT,
+ newPositionForHorizontalReachability);
+ Assert.assertEquals(LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP,
+ newPositionForVerticalReachability);
+ deleteConfiguration(secondPersister, secondPersisterQueue);
+ waitForCompletion(secondPersisterQueue);
+ stopPersisterSafe(secondPersisterQueue);
+ }
+
+ @Test
+ public void test_whenUpdatedWithNewValuesAndDeleted_valuesAreDefaults() {
+ mLetterboxConfigurationPersister.setLetterboxPositionForHorizontalReachability(
+ LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT);
+ mLetterboxConfigurationPersister.setLetterboxPositionForVerticalReachability(
+ LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP);
+ waitForCompletion(mPersisterQueue);
+ final int newPositionForHorizontalReachability =
+ mLetterboxConfigurationPersister.getLetterboxPositionForHorizontalReachability();
+ final int newPositionForVerticalReachability =
+ mLetterboxConfigurationPersister.getLetterboxPositionForVerticalReachability();
+ Assert.assertEquals(LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT,
+ newPositionForHorizontalReachability);
+ Assert.assertEquals(LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP,
+ newPositionForVerticalReachability);
+ deleteConfiguration(mLetterboxConfigurationPersister, mPersisterQueue);
+ waitForCompletion(mPersisterQueue);
+ final int positionForHorizontalReachability =
+ mLetterboxConfigurationPersister.getLetterboxPositionForHorizontalReachability();
+ final int defaultPositionForHorizontalReachability =
+ mContext.getResources().getInteger(
+ R.integer.config_letterboxDefaultPositionForHorizontalReachability);
+ Assert.assertEquals(defaultPositionForHorizontalReachability,
+ positionForHorizontalReachability);
+ final int positionForVerticalReachability =
+ mLetterboxConfigurationPersister.getLetterboxPositionForVerticalReachability();
+ final int defaultPositionForVerticalReachability =
+ mContext.getResources().getInteger(
+ R.integer.config_letterboxDefaultPositionForVerticalReachability);
+ Assert.assertEquals(defaultPositionForVerticalReachability,
+ positionForVerticalReachability);
+ }
+
+ private void stopPersisterSafe(PersisterQueue persisterQueue) {
+ try {
+ persisterQueue.stopPersisting();
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+
+ private void waitForCompletion(PersisterQueue persisterQueue) {
+ final long endTime = System.currentTimeMillis() + TIMEOUT;
+ // The queue could be empty but the last item still processing and not completed. For this
+ // reason the completion happens when there are not more items to process and the last one
+ // has completed.
+ while (System.currentTimeMillis() < endTime && (!isQueueEmpty(persisterQueue)
+ || !hasLastItemCompleted())) {
+ try {
+ Thread.sleep(100);
+ } catch (InterruptedException ie) { /* Nope */}
+ }
+ }
+
+ private boolean isQueueEmpty(PersisterQueue persisterQueue) {
+ return persisterQueue.findLastItem(
+ writeQueueItem -> true, PersisterQueue.WriteQueueItem.class) != null;
+ }
+
+ private boolean hasLastItemCompleted() {
+ return mQueueState.isEmpty();
+ }
+
+ private void deleteConfiguration(LetterboxConfigurationPersister persister,
+ PersisterQueue persisterQueue) {
+ final AtomicFile fileToDelete = new AtomicFile(
+ new File(mConfigFolder, LETTERBOX_CONFIGURATION_FILENAME));
+ persisterQueue.addItem(
+ new DeleteFileCommand(fileToDelete, mQueueState.andThen(
+ s -> persister.useDefaultValue())), true);
+ }
+
+ private static class DeleteFileCommand implements
+ PersisterQueue.WriteQueueItem<DeleteFileCommand> {
+
+ @NonNull
+ private final AtomicFile mFileToDelete;
+ @Nullable
+ private final Consumer<String> mOnComplete;
+
+ DeleteFileCommand(@NonNull AtomicFile fileToDelete, Consumer<String> onComplete) {
+ mFileToDelete = fileToDelete;
+ mOnComplete = onComplete;
+ }
+
+ @Override
+ public void process() {
+ mFileToDelete.delete();
+ if (mOnComplete != null) {
+ mOnComplete.accept("DeleteFileCommand");
+ }
+ }
+ }
+
+ // Contains the current length of the persister queue
+ private static class QueueState implements Consumer<String> {
+
+ // The current number of commands in the queue
+ @VisibleForTesting
+ private final AtomicInteger mCounter = new AtomicInteger(0);
+
+ @Override
+ public void accept(String s) {
+ mCounter.decrementAndGet();
+ }
+
+ void onItemAdded() {
+ mCounter.incrementAndGet();
+ }
+
+ boolean isEmpty() {
+ return mCounter.get() == 0;
+ }
+
+ }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationTest.java
new file mode 100644
index 0000000..c927f9e
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationTest.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_CENTER;
+import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT;
+import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_RIGHT;
+import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_VERTICAL_REACHABILITY_POSITION_BOTTOM;
+import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_VERTICAL_REACHABILITY_POSITION_CENTER;
+import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.function.Consumer;
+
+@SmallTest
+@Presubmit
+public class LetterboxConfigurationTest {
+
+ private LetterboxConfiguration mLetterboxConfiguration;
+ private LetterboxConfigurationPersister mLetterboxConfigurationPersister;
+
+ @Before
+ public void setUp() throws Exception {
+ Context context = getInstrumentation().getTargetContext();
+ mLetterboxConfigurationPersister = mock(LetterboxConfigurationPersister.class);
+ mLetterboxConfiguration = new LetterboxConfiguration(context,
+ mLetterboxConfigurationPersister);
+ }
+
+ @Test
+ public void test_whenReadingValues_storeIsInvoked() {
+ mLetterboxConfiguration.getLetterboxPositionForHorizontalReachability();
+ verify(mLetterboxConfigurationPersister).getLetterboxPositionForHorizontalReachability();
+ mLetterboxConfiguration.getLetterboxPositionForVerticalReachability();
+ verify(mLetterboxConfigurationPersister).getLetterboxPositionForVerticalReachability();
+ }
+
+ @Test
+ public void test_whenSettingValues_updateConfigurationIsInvoked() {
+ mLetterboxConfiguration.movePositionForHorizontalReachabilityToNextRightStop();
+ verify(mLetterboxConfigurationPersister).setLetterboxPositionForHorizontalReachability(
+ anyInt());
+ mLetterboxConfiguration.movePositionForVerticalReachabilityToNextBottomStop();
+ verify(mLetterboxConfigurationPersister).setLetterboxPositionForVerticalReachability(
+ anyInt());
+ }
+
+ @Test
+ public void test_whenMovedHorizontally_updatePositionAccordingly() {
+ // Starting from center
+ assertForHorizontalMove(
+ /* from */ LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_CENTER,
+ /* expected */ LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT,
+ /* expectedTime */ 1,
+ LetterboxConfiguration::movePositionForHorizontalReachabilityToNextLeftStop);
+ assertForHorizontalMove(
+ /* from */ LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_CENTER,
+ /* expected */ LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_RIGHT,
+ /* expectedTime */ 1,
+ LetterboxConfiguration::movePositionForHorizontalReachabilityToNextRightStop);
+ // Starting from left
+ assertForHorizontalMove(
+ /* from */ LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT,
+ /* expected */ LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT,
+ /* expectedTime */ 2,
+ LetterboxConfiguration::movePositionForHorizontalReachabilityToNextLeftStop);
+ assertForHorizontalMove(
+ /* from */ LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT,
+ /* expected */ LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_CENTER,
+ /* expectedTime */ 1,
+ LetterboxConfiguration::movePositionForHorizontalReachabilityToNextRightStop);
+ // Starting from right
+ assertForHorizontalMove(
+ /* from */ LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_RIGHT,
+ /* expected */ LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_RIGHT,
+ /* expectedTime */ 2,
+ LetterboxConfiguration::movePositionForHorizontalReachabilityToNextRightStop);
+ assertForHorizontalMove(
+ /* from */ LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_RIGHT,
+ /* expected */ LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_CENTER,
+ /* expectedTime */ 2,
+ LetterboxConfiguration::movePositionForHorizontalReachabilityToNextLeftStop);
+ }
+
+ @Test
+ public void test_whenMovedVertically_updatePositionAccordingly() {
+ // Starting from center
+ assertForVerticalMove(
+ /* from */ LETTERBOX_VERTICAL_REACHABILITY_POSITION_CENTER,
+ /* expected */ LETTERBOX_VERTICAL_REACHABILITY_POSITION_BOTTOM,
+ /* expectedTime */ 1,
+ LetterboxConfiguration::movePositionForVerticalReachabilityToNextBottomStop);
+ assertForVerticalMove(
+ /* from */ LETTERBOX_VERTICAL_REACHABILITY_POSITION_CENTER,
+ /* expected */ LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP,
+ /* expectedTime */ 1,
+ LetterboxConfiguration::movePositionForVerticalReachabilityToNextTopStop);
+ // Starting from top
+ assertForVerticalMove(
+ /* from */ LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP,
+ /* expected */ LETTERBOX_VERTICAL_REACHABILITY_POSITION_CENTER,
+ /* expectedTime */ 1,
+ LetterboxConfiguration::movePositionForVerticalReachabilityToNextBottomStop);
+ assertForVerticalMove(
+ /* from */ LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP,
+ /* expected */ LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP,
+ /* expectedTime */ 2,
+ LetterboxConfiguration::movePositionForVerticalReachabilityToNextTopStop);
+ // Starting from bottom
+ assertForVerticalMove(
+ /* from */ LETTERBOX_VERTICAL_REACHABILITY_POSITION_BOTTOM,
+ /* expected */ LETTERBOX_VERTICAL_REACHABILITY_POSITION_CENTER,
+ /* expectedTime */ 2,
+ LetterboxConfiguration::movePositionForVerticalReachabilityToNextTopStop);
+ assertForVerticalMove(
+ /* from */ LETTERBOX_VERTICAL_REACHABILITY_POSITION_BOTTOM,
+ /* expected */ LETTERBOX_VERTICAL_REACHABILITY_POSITION_BOTTOM,
+ /* expectedTime */ 2,
+ LetterboxConfiguration::movePositionForVerticalReachabilityToNextBottomStop);
+ }
+
+ private void assertForHorizontalMove(int from, int expected, int expectedTime,
+ Consumer<LetterboxConfiguration> move) {
+ // We are in the current position
+ when(mLetterboxConfiguration.getLetterboxPositionForHorizontalReachability())
+ .thenReturn(from);
+ move.accept(mLetterboxConfiguration);
+ verify(mLetterboxConfigurationPersister,
+ times(expectedTime)).setLetterboxPositionForHorizontalReachability(
+ expected);
+ }
+
+ private void assertForVerticalMove(int from, int expected, int expectedTime,
+ Consumer<LetterboxConfiguration> move) {
+ // We are in the current position
+ when(mLetterboxConfiguration.getLetterboxPositionForVerticalReachability())
+ .thenReturn(from);
+ move.accept(mLetterboxConfiguration);
+ verify(mLetterboxConfigurationPersister,
+ times(expectedTime)).setLetterboxPositionForVerticalReachability(
+ expected);
+ }
+}