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