1 /*
2  * Copyright (C) 2022 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.server.wm;
18 
19 import static android.os.StrictMode.setThreadPolicy;
20 
21 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
22 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
23 
24 import android.annotation.NonNull;
25 import android.annotation.Nullable;
26 import android.os.Environment;
27 import android.os.StrictMode;
28 import android.os.StrictMode.ThreadPolicy;
29 import android.util.AtomicFile;
30 import android.util.Slog;
31 
32 import com.android.internal.annotations.VisibleForTesting;
33 import com.android.server.wm.LetterboxConfiguration.LetterboxHorizontalReachabilityPosition;
34 import com.android.server.wm.LetterboxConfiguration.LetterboxVerticalReachabilityPosition;
35 import com.android.server.wm.nano.WindowManagerProtos;
36 
37 import java.io.ByteArrayOutputStream;
38 import java.io.File;
39 import java.io.FileInputStream;
40 import java.io.FileOutputStream;
41 import java.io.IOException;
42 import java.io.InputStream;
43 import java.util.function.Consumer;
44 import java.util.function.Supplier;
45 
46 /**
47  * Persists the values of letterboxPositionForHorizontalReachability and
48  * letterboxPositionForVerticalReachability for {@link LetterboxConfiguration}.
49  */
50 class LetterboxConfigurationPersister {
51 
52     private static final String TAG =
53             TAG_WITH_CLASS_NAME ? "LetterboxConfigurationPersister" : TAG_WM;
54 
55     private static final String LETTERBOX_CONFIGURATION_FILENAME = "letterbox_config";
56 
57     private final Supplier<Integer> mDefaultHorizontalReachabilitySupplier;
58     private final Supplier<Integer> mDefaultVerticalReachabilitySupplier;
59     private final Supplier<Integer> mDefaultBookModeReachabilitySupplier;
60     private final Supplier<Integer> mDefaultTabletopModeReachabilitySupplier;
61 
62     // Horizontal position of a center of the letterboxed app window which is global to prevent
63     // "jumps" when switching between letterboxed apps. It's updated to reposition the app window
64     // in response to a double tap gesture (see LetterboxUiController#handleDoubleTap). Used in
65     // LetterboxUiController#getHorizontalPositionMultiplier which is called from
66     // ActivityRecord#updateResolvedBoundsPosition.
67     @LetterboxHorizontalReachabilityPosition
68     private volatile int mLetterboxPositionForHorizontalReachability;
69 
70     // The same as mLetterboxPositionForHorizontalReachability but used when the device is
71     // half-folded.
72     @LetterboxHorizontalReachabilityPosition
73     private volatile int mLetterboxPositionForBookModeReachability;
74 
75     // Vertical position of a center of the letterboxed app window which is global to prevent
76     // "jumps" when switching between letterboxed apps. It's updated to reposition the app window
77     // in response to a double tap gesture (see LetterboxUiController#handleDoubleTap). Used in
78     // LetterboxUiController#getVerticalPositionMultiplier which is called from
79     // ActivityRecord#updateResolvedBoundsPosition.
80     @LetterboxVerticalReachabilityPosition
81     private volatile int mLetterboxPositionForVerticalReachability;
82 
83     // The same as mLetterboxPositionForVerticalReachability but used when the device is
84     // half-folded.
85     @LetterboxVerticalReachabilityPosition
86     private volatile int mLetterboxPositionForTabletopModeReachability;
87 
88     @NonNull
89     private final AtomicFile mConfigurationFile;
90 
91     @Nullable
92     private final Consumer<String> mCompletionCallback;
93 
94     @NonNull
95     private final PersisterQueue mPersisterQueue;
96 
LetterboxConfigurationPersister( @onNull Supplier<Integer> defaultHorizontalReachabilitySupplier, @NonNull Supplier<Integer> defaultVerticalReachabilitySupplier, @NonNull Supplier<Integer> defaultBookModeReachabilitySupplier, @NonNull Supplier<Integer> defaultTabletopModeReachabilitySupplier)97     LetterboxConfigurationPersister(
98             @NonNull Supplier<Integer> defaultHorizontalReachabilitySupplier,
99             @NonNull Supplier<Integer> defaultVerticalReachabilitySupplier,
100             @NonNull Supplier<Integer> defaultBookModeReachabilitySupplier,
101             @NonNull Supplier<Integer> defaultTabletopModeReachabilitySupplier) {
102         this(defaultHorizontalReachabilitySupplier, defaultVerticalReachabilitySupplier,
103                 defaultBookModeReachabilitySupplier, defaultTabletopModeReachabilitySupplier,
104                 Environment.getDataSystemDirectory(), new PersisterQueue(),
105                 /* completionCallback */ null, LETTERBOX_CONFIGURATION_FILENAME);
106     }
107 
108     @VisibleForTesting
LetterboxConfigurationPersister( @onNull Supplier<Integer> defaultHorizontalReachabilitySupplier, @NonNull Supplier<Integer> defaultVerticalReachabilitySupplier, @NonNull Supplier<Integer> defaultBookModeReachabilitySupplier, @NonNull Supplier<Integer> defaultTabletopModeReachabilitySupplier, @NonNull File configFolder, @NonNull PersisterQueue persisterQueue, @Nullable Consumer<String> completionCallback, @NonNull String letterboxConfigurationFileName)109     LetterboxConfigurationPersister(
110             @NonNull Supplier<Integer> defaultHorizontalReachabilitySupplier,
111             @NonNull Supplier<Integer> defaultVerticalReachabilitySupplier,
112             @NonNull Supplier<Integer> defaultBookModeReachabilitySupplier,
113             @NonNull Supplier<Integer> defaultTabletopModeReachabilitySupplier,
114             @NonNull File configFolder, @NonNull PersisterQueue persisterQueue,
115             @Nullable Consumer<String> completionCallback,
116             @NonNull String letterboxConfigurationFileName) {
117         mDefaultHorizontalReachabilitySupplier = defaultHorizontalReachabilitySupplier;
118         mDefaultVerticalReachabilitySupplier = defaultVerticalReachabilitySupplier;
119         mDefaultBookModeReachabilitySupplier = defaultBookModeReachabilitySupplier;
120         mDefaultTabletopModeReachabilitySupplier = defaultTabletopModeReachabilitySupplier;
121         mCompletionCallback = completionCallback;
122         final File prefFiles = new File(configFolder, letterboxConfigurationFileName);
123         mConfigurationFile = new AtomicFile(prefFiles);
124         mPersisterQueue = persisterQueue;
125         runWithDiskReadsThreadPolicy(this::readCurrentConfiguration);
126     }
127 
128     /**
129      * Startes the persistence queue
130      */
start()131     void start() {
132         mPersisterQueue.startPersisting();
133     }
134 
135     /*
136      * Gets the horizontal position of the letterboxed app window when horizontal reachability is
137      * enabled.
138      */
139     @LetterboxHorizontalReachabilityPosition
getLetterboxPositionForHorizontalReachability(boolean forBookMode)140     int getLetterboxPositionForHorizontalReachability(boolean forBookMode) {
141         if (forBookMode) {
142             return mLetterboxPositionForBookModeReachability;
143         } else {
144             return mLetterboxPositionForHorizontalReachability;
145         }
146     }
147 
148     /*
149      * Gets the vertical position of the letterboxed app window when vertical reachability is
150      * enabled.
151      */
152     @LetterboxVerticalReachabilityPosition
getLetterboxPositionForVerticalReachability(boolean forTabletopMode)153     int getLetterboxPositionForVerticalReachability(boolean forTabletopMode) {
154         if (forTabletopMode) {
155             return mLetterboxPositionForTabletopModeReachability;
156         } else {
157             return mLetterboxPositionForVerticalReachability;
158         }
159     }
160 
161     /**
162      * Updates letterboxPositionForVerticalReachability if different from the current value
163      */
setLetterboxPositionForHorizontalReachability(boolean forBookMode, int letterboxPositionForHorizontalReachability)164     void setLetterboxPositionForHorizontalReachability(boolean forBookMode,
165             int letterboxPositionForHorizontalReachability) {
166         if (forBookMode) {
167             if (mLetterboxPositionForBookModeReachability
168                     != letterboxPositionForHorizontalReachability) {
169                 mLetterboxPositionForBookModeReachability =
170                         letterboxPositionForHorizontalReachability;
171                 updateConfiguration();
172             }
173         } else {
174             if (mLetterboxPositionForHorizontalReachability
175                     != letterboxPositionForHorizontalReachability) {
176                 mLetterboxPositionForHorizontalReachability =
177                         letterboxPositionForHorizontalReachability;
178                 updateConfiguration();
179             }
180         }
181     }
182 
183     /**
184      * Updates letterboxPositionForVerticalReachability if different from the current value
185      */
setLetterboxPositionForVerticalReachability(boolean forTabletopMode, int letterboxPositionForVerticalReachability)186     void setLetterboxPositionForVerticalReachability(boolean forTabletopMode,
187             int letterboxPositionForVerticalReachability) {
188         if (forTabletopMode) {
189             if (mLetterboxPositionForTabletopModeReachability
190                     != letterboxPositionForVerticalReachability) {
191                 mLetterboxPositionForTabletopModeReachability =
192                         letterboxPositionForVerticalReachability;
193                 updateConfiguration();
194             }
195         } else {
196             if (mLetterboxPositionForVerticalReachability
197                     != letterboxPositionForVerticalReachability) {
198                 mLetterboxPositionForVerticalReachability =
199                         letterboxPositionForVerticalReachability;
200                 updateConfiguration();
201             }
202         }
203     }
204 
205     @VisibleForTesting
useDefaultValue()206     void useDefaultValue() {
207         mLetterboxPositionForHorizontalReachability = mDefaultHorizontalReachabilitySupplier.get();
208         mLetterboxPositionForVerticalReachability = mDefaultVerticalReachabilitySupplier.get();
209         mLetterboxPositionForBookModeReachability =
210                 mDefaultBookModeReachabilitySupplier.get();
211         mLetterboxPositionForTabletopModeReachability =
212                 mDefaultTabletopModeReachabilitySupplier.get();
213     }
214 
readCurrentConfiguration()215     private void readCurrentConfiguration() {
216         if (!mConfigurationFile.exists()) {
217             useDefaultValue();
218             return;
219         }
220         FileInputStream fis = null;
221         try {
222             fis = mConfigurationFile.openRead();
223             byte[] protoData = readInputStream(fis);
224             final WindowManagerProtos.LetterboxProto letterboxData =
225                     WindowManagerProtos.LetterboxProto.parseFrom(protoData);
226             mLetterboxPositionForHorizontalReachability =
227                     letterboxData.letterboxPositionForHorizontalReachability;
228             mLetterboxPositionForVerticalReachability =
229                     letterboxData.letterboxPositionForVerticalReachability;
230             mLetterboxPositionForBookModeReachability =
231                     letterboxData.letterboxPositionForBookModeReachability;
232             mLetterboxPositionForTabletopModeReachability =
233                     letterboxData.letterboxPositionForTabletopModeReachability;
234         } catch (IOException ioe) {
235             Slog.e(TAG,
236                     "Error reading from LetterboxConfigurationPersister. "
237                             + "Using default values!", ioe);
238             useDefaultValue();
239         } finally {
240             if (fis != null) {
241                 try {
242                     fis.close();
243                 } catch (IOException e) {
244                     useDefaultValue();
245                     Slog.e(TAG, "Error reading from LetterboxConfigurationPersister ", e);
246                 }
247             }
248         }
249     }
250 
updateConfiguration()251     private void updateConfiguration() {
252         mPersisterQueue.addItem(new UpdateValuesCommand(mConfigurationFile,
253                 mLetterboxPositionForHorizontalReachability,
254                 mLetterboxPositionForVerticalReachability,
255                 mLetterboxPositionForBookModeReachability,
256                 mLetterboxPositionForTabletopModeReachability,
257                 mCompletionCallback), /* flush */ true);
258     }
259 
readInputStream(InputStream in)260     private static byte[] readInputStream(InputStream in) throws IOException {
261         ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
262         try {
263             byte[] buffer = new byte[1024];
264             int size = in.read(buffer);
265             while (size > 0) {
266                 outputStream.write(buffer, 0, size);
267                 size = in.read(buffer);
268             }
269             return outputStream.toByteArray();
270         } finally {
271             outputStream.close();
272         }
273     }
274 
275     // The LetterboxConfigurationDeviceConfig needs to access the
276     // file with the current reachability position once when the
277     // device boots. Because DisplayThread uses allowIo=false
278     // accessing a file triggers a DiskReadViolation.
279     // Here we use StrictMode to allow the current thread to read
280     // the AtomicFile once in the current thread restoring the
281     // original ThreadPolicy after that.
runWithDiskReadsThreadPolicy(Runnable runnable)282     private void runWithDiskReadsThreadPolicy(Runnable runnable) {
283         final ThreadPolicy currentPolicy = StrictMode.getThreadPolicy();
284         setThreadPolicy(new ThreadPolicy.Builder().permitDiskReads().build());
285         runnable.run();
286         setThreadPolicy(currentPolicy);
287     }
288 
289     private static class UpdateValuesCommand implements
290             PersisterQueue.WriteQueueItem<UpdateValuesCommand> {
291 
292         @NonNull
293         private final AtomicFile mFileToUpdate;
294         @Nullable
295         private final Consumer<String> mOnComplete;
296 
297 
298         private final int mHorizontalReachability;
299         private final int mVerticalReachability;
300         private final int mBookModeReachability;
301         private final int mTabletopModeReachability;
302 
UpdateValuesCommand(@onNull AtomicFile fileToUpdate, int horizontalReachability, int verticalReachability, int bookModeReachability, int tabletopModeReachability, @Nullable Consumer<String> onComplete)303         UpdateValuesCommand(@NonNull AtomicFile fileToUpdate,
304                 int horizontalReachability, int verticalReachability,
305                 int bookModeReachability, int tabletopModeReachability,
306                 @Nullable Consumer<String> onComplete) {
307             mFileToUpdate = fileToUpdate;
308             mHorizontalReachability = horizontalReachability;
309             mVerticalReachability = verticalReachability;
310             mBookModeReachability = bookModeReachability;
311             mTabletopModeReachability = tabletopModeReachability;
312             mOnComplete = onComplete;
313         }
314 
315         @Override
process()316         public void process() {
317             final WindowManagerProtos.LetterboxProto letterboxData =
318                     new WindowManagerProtos.LetterboxProto();
319             letterboxData.letterboxPositionForHorizontalReachability = mHorizontalReachability;
320             letterboxData.letterboxPositionForVerticalReachability = mVerticalReachability;
321             letterboxData.letterboxPositionForBookModeReachability =
322                     mBookModeReachability;
323             letterboxData.letterboxPositionForTabletopModeReachability =
324                     mTabletopModeReachability;
325             final byte[] bytes = WindowManagerProtos.LetterboxProto.toByteArray(letterboxData);
326 
327             FileOutputStream fos = null;
328             try {
329                 fos = mFileToUpdate.startWrite();
330                 fos.write(bytes);
331                 mFileToUpdate.finishWrite(fos);
332             } catch (IOException ioe) {
333                 mFileToUpdate.failWrite(fos);
334                 Slog.e(TAG,
335                         "Error writing to LetterboxConfigurationPersister. "
336                                 + "Using default values!", ioe);
337             } finally {
338                 if (mOnComplete != null) {
339                     mOnComplete.accept("UpdateValuesCommand");
340                 }
341             }
342         }
343     }
344 }
345