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