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 com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; 20 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; 21 22 import android.annotation.MainThread; 23 import android.annotation.NonNull; 24 import android.annotation.Nullable; 25 import android.os.Environment; 26 import android.util.AtomicFile; 27 import android.util.Slog; 28 29 import com.android.internal.annotations.VisibleForTesting; 30 import com.android.server.wm.AppCompatConfiguration.LetterboxHorizontalReachabilityPosition; 31 import com.android.server.wm.AppCompatConfiguration.LetterboxVerticalReachabilityPosition; 32 import com.android.server.wm.nano.WindowManagerProtos; 33 34 import java.io.ByteArrayOutputStream; 35 import java.io.File; 36 import java.io.FileInputStream; 37 import java.io.FileOutputStream; 38 import java.io.IOException; 39 import java.io.InputStream; 40 import java.util.function.Consumer; 41 import java.util.function.Supplier; 42 43 /** 44 * Persists the values of letterboxPositionForHorizontalReachability and 45 * letterboxPositionForVerticalReachability for {@link AppCompatConfiguration}. 46 */ 47 class AppCompatConfigurationPersister { 48 49 private static final String TAG = 50 TAG_WITH_CLASS_NAME ? "AppCompatConfigurationPersister" : TAG_WM; 51 52 private static final String LETTERBOX_CONFIGURATION_FILENAME = "letterbox_config"; 53 54 private final Supplier<Integer> mDefaultHorizontalReachabilitySupplier; 55 private final Supplier<Integer> mDefaultVerticalReachabilitySupplier; 56 private final Supplier<Integer> mDefaultBookModeReachabilitySupplier; 57 private final Supplier<Integer> mDefaultTabletopModeReachabilitySupplier; 58 59 // Horizontal position of a center of the letterboxed app window which is global to prevent 60 // "jumps" when switching between letterboxed apps. It's updated to reposition the app window 61 // in response to a double tap gesture (see LetterboxUiController#handleDoubleTap). Used in 62 // LetterboxUiController#getHorizontalPositionMultiplier which is called from 63 // ActivityRecord#updateResolvedBoundsPosition. 64 @LetterboxHorizontalReachabilityPosition 65 private volatile int mLetterboxPositionForHorizontalReachability; 66 67 // The same as mLetterboxPositionForHorizontalReachability but used when the device is 68 // half-folded. 69 @LetterboxHorizontalReachabilityPosition 70 private volatile int mLetterboxPositionForBookModeReachability; 71 72 // Vertical position of a center of the letterboxed app window which is global to prevent 73 // "jumps" when switching between letterboxed apps. It's updated to reposition the app window 74 // in response to a double tap gesture (see LetterboxUiController#handleDoubleTap). Used in 75 // LetterboxUiController#getVerticalPositionMultiplier which is called from 76 // ActivityRecord#updateResolvedBoundsPosition. 77 @LetterboxVerticalReachabilityPosition 78 private volatile int mLetterboxPositionForVerticalReachability; 79 80 // The same as mLetterboxPositionForVerticalReachability but used when the device is 81 // half-folded. 82 @LetterboxVerticalReachabilityPosition 83 private volatile int mLetterboxPositionForTabletopModeReachability; 84 85 @NonNull 86 private final AtomicFile mConfigurationFile; 87 88 @Nullable 89 private final Consumer<String> mCompletionCallback; 90 91 @NonNull 92 private final PersisterQueue mPersisterQueue; 93 AppCompatConfigurationPersister( @onNull Supplier<Integer> defaultHorizontalReachabilitySupplier, @NonNull Supplier<Integer> defaultVerticalReachabilitySupplier, @NonNull Supplier<Integer> defaultBookModeReachabilitySupplier, @NonNull Supplier<Integer> defaultTabletopModeReachabilitySupplier)94 AppCompatConfigurationPersister( 95 @NonNull Supplier<Integer> defaultHorizontalReachabilitySupplier, 96 @NonNull Supplier<Integer> defaultVerticalReachabilitySupplier, 97 @NonNull Supplier<Integer> defaultBookModeReachabilitySupplier, 98 @NonNull Supplier<Integer> defaultTabletopModeReachabilitySupplier) { 99 this(defaultHorizontalReachabilitySupplier, defaultVerticalReachabilitySupplier, 100 defaultBookModeReachabilitySupplier, defaultTabletopModeReachabilitySupplier, 101 Environment.getDataSystemDirectory(), new PersisterQueue(), 102 /* completionCallback */ null, LETTERBOX_CONFIGURATION_FILENAME); 103 } 104 105 @VisibleForTesting AppCompatConfigurationPersister( @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)106 AppCompatConfigurationPersister( 107 @NonNull Supplier<Integer> defaultHorizontalReachabilitySupplier, 108 @NonNull Supplier<Integer> defaultVerticalReachabilitySupplier, 109 @NonNull Supplier<Integer> defaultBookModeReachabilitySupplier, 110 @NonNull Supplier<Integer> defaultTabletopModeReachabilitySupplier, 111 @NonNull File configFolder, @NonNull PersisterQueue persisterQueue, 112 @Nullable Consumer<String> completionCallback, 113 @NonNull String letterboxConfigurationFileName) { 114 mDefaultHorizontalReachabilitySupplier = defaultHorizontalReachabilitySupplier; 115 mDefaultVerticalReachabilitySupplier = defaultVerticalReachabilitySupplier; 116 mDefaultBookModeReachabilitySupplier = defaultBookModeReachabilitySupplier; 117 mDefaultTabletopModeReachabilitySupplier = defaultTabletopModeReachabilitySupplier; 118 mCompletionCallback = completionCallback; 119 final File prefFiles = new File(configFolder, letterboxConfigurationFileName); 120 mConfigurationFile = new AtomicFile(prefFiles); 121 mPersisterQueue = persisterQueue; 122 readCurrentConfiguration(); 123 } 124 125 /** 126 * Startes the persistence queue 127 */ start()128 void start() { 129 mPersisterQueue.startPersisting(); 130 } 131 132 /* 133 * Gets the horizontal position of the letterboxed app window when horizontal reachability is 134 * enabled. 135 */ 136 @LetterboxHorizontalReachabilityPosition getLetterboxPositionForHorizontalReachability(boolean forBookMode)137 int getLetterboxPositionForHorizontalReachability(boolean forBookMode) { 138 if (forBookMode) { 139 return mLetterboxPositionForBookModeReachability; 140 } else { 141 return mLetterboxPositionForHorizontalReachability; 142 } 143 } 144 145 /* 146 * Gets the vertical position of the letterboxed app window when vertical reachability is 147 * enabled. 148 */ 149 @LetterboxVerticalReachabilityPosition getLetterboxPositionForVerticalReachability(boolean forTabletopMode)150 int getLetterboxPositionForVerticalReachability(boolean forTabletopMode) { 151 if (forTabletopMode) { 152 return mLetterboxPositionForTabletopModeReachability; 153 } else { 154 return mLetterboxPositionForVerticalReachability; 155 } 156 } 157 158 /** 159 * Updates letterboxPositionForVerticalReachability if different from the current value 160 */ setLetterboxPositionForHorizontalReachability(boolean forBookMode, int letterboxPositionForHorizontalReachability)161 void setLetterboxPositionForHorizontalReachability(boolean forBookMode, 162 int letterboxPositionForHorizontalReachability) { 163 if (forBookMode) { 164 if (mLetterboxPositionForBookModeReachability 165 != letterboxPositionForHorizontalReachability) { 166 mLetterboxPositionForBookModeReachability = 167 letterboxPositionForHorizontalReachability; 168 updateConfiguration(); 169 } 170 } else { 171 if (mLetterboxPositionForHorizontalReachability 172 != letterboxPositionForHorizontalReachability) { 173 mLetterboxPositionForHorizontalReachability = 174 letterboxPositionForHorizontalReachability; 175 updateConfiguration(); 176 } 177 } 178 } 179 180 /** 181 * Updates letterboxPositionForVerticalReachability if different from the current value 182 */ setLetterboxPositionForVerticalReachability(boolean forTabletopMode, int letterboxPositionForVerticalReachability)183 void setLetterboxPositionForVerticalReachability(boolean forTabletopMode, 184 int letterboxPositionForVerticalReachability) { 185 if (forTabletopMode) { 186 if (mLetterboxPositionForTabletopModeReachability 187 != letterboxPositionForVerticalReachability) { 188 mLetterboxPositionForTabletopModeReachability = 189 letterboxPositionForVerticalReachability; 190 updateConfiguration(); 191 } 192 } else { 193 if (mLetterboxPositionForVerticalReachability 194 != letterboxPositionForVerticalReachability) { 195 mLetterboxPositionForVerticalReachability = 196 letterboxPositionForVerticalReachability; 197 updateConfiguration(); 198 } 199 } 200 } 201 202 @VisibleForTesting useDefaultValue()203 void useDefaultValue() { 204 mLetterboxPositionForHorizontalReachability = mDefaultHorizontalReachabilitySupplier.get(); 205 mLetterboxPositionForVerticalReachability = mDefaultVerticalReachabilitySupplier.get(); 206 mLetterboxPositionForBookModeReachability = 207 mDefaultBookModeReachabilitySupplier.get(); 208 mLetterboxPositionForTabletopModeReachability = 209 mDefaultTabletopModeReachabilitySupplier.get(); 210 } 211 212 @MainThread readCurrentConfiguration()213 private void readCurrentConfiguration() { 214 if (!mConfigurationFile.exists()) { 215 useDefaultValue(); 216 return; 217 } 218 FileInputStream fis = null; 219 try { 220 fis = mConfigurationFile.openRead(); 221 byte[] protoData = readInputStream(fis); 222 final WindowManagerProtos.LetterboxProto letterboxData = 223 WindowManagerProtos.LetterboxProto.parseFrom(protoData); 224 mLetterboxPositionForHorizontalReachability = 225 letterboxData.letterboxPositionForHorizontalReachability; 226 mLetterboxPositionForVerticalReachability = 227 letterboxData.letterboxPositionForVerticalReachability; 228 mLetterboxPositionForBookModeReachability = 229 letterboxData.letterboxPositionForBookModeReachability; 230 mLetterboxPositionForTabletopModeReachability = 231 letterboxData.letterboxPositionForTabletopModeReachability; 232 } catch (IOException ioe) { 233 Slog.e(TAG, 234 "Error reading from AppCompatConfigurationPersister. " 235 + "Using default values!", ioe); 236 useDefaultValue(); 237 } finally { 238 if (fis != null) { 239 try { 240 fis.close(); 241 } catch (IOException e) { 242 useDefaultValue(); 243 Slog.e(TAG, "Error reading from AppCompatConfigurationPersister ", e); 244 } 245 } 246 } 247 } 248 updateConfiguration()249 private void updateConfiguration() { 250 mPersisterQueue.addItem(new UpdateValuesCommand(mConfigurationFile, 251 mLetterboxPositionForHorizontalReachability, 252 mLetterboxPositionForVerticalReachability, 253 mLetterboxPositionForBookModeReachability, 254 mLetterboxPositionForTabletopModeReachability, 255 mCompletionCallback), /* flush */ true); 256 } 257 readInputStream(InputStream in)258 private static byte[] readInputStream(InputStream in) throws IOException { 259 ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); 260 try { 261 byte[] buffer = new byte[1024]; 262 int size = in.read(buffer); 263 while (size > 0) { 264 outputStream.write(buffer, 0, size); 265 size = in.read(buffer); 266 } 267 return outputStream.toByteArray(); 268 } finally { 269 outputStream.close(); 270 } 271 } 272 273 private static class UpdateValuesCommand implements 274 PersisterQueue.WriteQueueItem<UpdateValuesCommand> { 275 276 @NonNull 277 private final AtomicFile mFileToUpdate; 278 @Nullable 279 private final Consumer<String> mOnComplete; 280 281 282 private final int mHorizontalReachability; 283 private final int mVerticalReachability; 284 private final int mBookModeReachability; 285 private final int mTabletopModeReachability; 286 UpdateValuesCommand(@onNull AtomicFile fileToUpdate, int horizontalReachability, int verticalReachability, int bookModeReachability, int tabletopModeReachability, @Nullable Consumer<String> onComplete)287 UpdateValuesCommand(@NonNull AtomicFile fileToUpdate, 288 int horizontalReachability, int verticalReachability, 289 int bookModeReachability, int tabletopModeReachability, 290 @Nullable Consumer<String> onComplete) { 291 mFileToUpdate = fileToUpdate; 292 mHorizontalReachability = horizontalReachability; 293 mVerticalReachability = verticalReachability; 294 mBookModeReachability = bookModeReachability; 295 mTabletopModeReachability = tabletopModeReachability; 296 mOnComplete = onComplete; 297 } 298 299 @Override process()300 public void process() { 301 final WindowManagerProtos.LetterboxProto letterboxData = 302 new WindowManagerProtos.LetterboxProto(); 303 letterboxData.letterboxPositionForHorizontalReachability = mHorizontalReachability; 304 letterboxData.letterboxPositionForVerticalReachability = mVerticalReachability; 305 letterboxData.letterboxPositionForBookModeReachability = 306 mBookModeReachability; 307 letterboxData.letterboxPositionForTabletopModeReachability = 308 mTabletopModeReachability; 309 final byte[] bytes = WindowManagerProtos.LetterboxProto.toByteArray(letterboxData); 310 311 FileOutputStream fos = null; 312 try { 313 fos = mFileToUpdate.startWrite(); 314 fos.write(bytes); 315 mFileToUpdate.finishWrite(fos); 316 } catch (IOException ioe) { 317 mFileToUpdate.failWrite(fos); 318 Slog.e(TAG, 319 "Error writing to AppCompatConfigurationPersister. " 320 + "Using default values!", ioe); 321 } finally { 322 if (mOnComplete != null) { 323 mOnComplete.accept("UpdateValuesCommand"); 324 } 325 } 326 } 327 } 328 } 329