1 /* 2 * Copyright (C) 2021 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.apphibernation; 18 19 import android.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.annotation.WorkerThread; 22 import android.text.format.DateUtils; 23 import android.util.AtomicFile; 24 import android.util.Slog; 25 import android.util.proto.ProtoInputStream; 26 import android.util.proto.ProtoOutputStream; 27 28 import com.android.internal.annotations.VisibleForTesting; 29 30 import java.io.File; 31 import java.io.FileInputStream; 32 import java.io.FileOutputStream; 33 import java.io.IOException; 34 import java.util.ArrayList; 35 import java.util.List; 36 import java.util.concurrent.ScheduledExecutorService; 37 import java.util.concurrent.ScheduledFuture; 38 import java.util.concurrent.TimeUnit; 39 40 /** 41 * Disk store utility class for hibernation states. 42 * 43 * @param <T> the type of hibernation state data 44 */ 45 class HibernationStateDiskStore<T> { 46 private static final String TAG = "HibernationStateDiskStore"; 47 48 // Time to wait before actually writing. Saves extra writes if data changes come in batches. 49 private static final long DISK_WRITE_DELAY = 1L * DateUtils.MINUTE_IN_MILLIS; 50 private static final String STATES_FILE_NAME = "states"; 51 52 private final File mHibernationFile; 53 private final ScheduledExecutorService mExecutorService; 54 private final ProtoReadWriter<List<T>> mProtoReadWriter; 55 private List<T> mScheduledStatesToWrite = new ArrayList<>(); 56 private ScheduledFuture<?> mFuture; 57 58 /** 59 * Initialize a disk store for hibernation states in the given directory. 60 * 61 * @param hibernationDir directory to write/read states file 62 * @param readWriter writer/reader of states proto 63 * @param executorService scheduled executor for writing data 64 */ HibernationStateDiskStore(@onNull File hibernationDir, @NonNull ProtoReadWriter<List<T>> readWriter, @NonNull ScheduledExecutorService executorService)65 HibernationStateDiskStore(@NonNull File hibernationDir, 66 @NonNull ProtoReadWriter<List<T>> readWriter, 67 @NonNull ScheduledExecutorService executorService) { 68 this(hibernationDir, readWriter, executorService, STATES_FILE_NAME); 69 } 70 71 @VisibleForTesting HibernationStateDiskStore(@onNull File hibernationDir, @NonNull ProtoReadWriter<List<T>> readWriter, @NonNull ScheduledExecutorService executorService, @NonNull String fileName)72 HibernationStateDiskStore(@NonNull File hibernationDir, 73 @NonNull ProtoReadWriter<List<T>> readWriter, 74 @NonNull ScheduledExecutorService executorService, 75 @NonNull String fileName) { 76 mHibernationFile = new File(hibernationDir, fileName); 77 mExecutorService = executorService; 78 mProtoReadWriter = readWriter; 79 } 80 81 /** 82 * Schedule a full write of all the hibernation states to the file on disk. Does not run 83 * immediately and subsequent writes override previous ones. 84 * 85 * @param hibernationStates list of hibernation states to write to disk 86 */ scheduleWriteHibernationStates(@onNull List<T> hibernationStates)87 void scheduleWriteHibernationStates(@NonNull List<T> hibernationStates) { 88 synchronized (this) { 89 mScheduledStatesToWrite = hibernationStates; 90 if (mExecutorService.isShutdown()) { 91 Slog.e(TAG, "Scheduled executor service is shut down."); 92 return; 93 } 94 95 // Already have write scheduled 96 if (mFuture != null) { 97 Slog.i(TAG, "Write already scheduled. Skipping schedule."); 98 return; 99 } 100 101 mFuture = mExecutorService.schedule(this::writeHibernationStates, DISK_WRITE_DELAY, 102 TimeUnit.MILLISECONDS); 103 } 104 } 105 106 /** 107 * Read hibernation states from disk. 108 * 109 * @return the parsed list of hibernation states, null if file does not exist 110 */ 111 @Nullable 112 @WorkerThread readHibernationStates()113 List<T> readHibernationStates() { 114 synchronized (this) { 115 if (!mHibernationFile.exists()) { 116 Slog.i(TAG, "No hibernation file on disk for file " + mHibernationFile.getPath()); 117 return null; 118 } 119 AtomicFile atomicFile = new AtomicFile(mHibernationFile); 120 121 try { 122 FileInputStream inputStream = atomicFile.openRead(); 123 ProtoInputStream protoInputStream = new ProtoInputStream(inputStream); 124 return mProtoReadWriter.readFromProto(protoInputStream); 125 } catch (IOException e) { 126 Slog.e(TAG, "Failed to read states protobuf.", e); 127 return null; 128 } 129 } 130 } 131 132 @WorkerThread writeHibernationStates()133 private void writeHibernationStates() { 134 synchronized (this) { 135 writeStateProto(mScheduledStatesToWrite); 136 mScheduledStatesToWrite.clear(); 137 mFuture = null; 138 } 139 } 140 141 @WorkerThread writeStateProto(List<T> states)142 private void writeStateProto(List<T> states) { 143 AtomicFile atomicFile = new AtomicFile(mHibernationFile); 144 145 FileOutputStream fileOutputStream; 146 try { 147 fileOutputStream = atomicFile.startWrite(); 148 } catch (IOException e) { 149 Slog.e(TAG, "Failed to start write to states protobuf.", e); 150 return; 151 } 152 153 try { 154 ProtoOutputStream protoOutputStream = new ProtoOutputStream(fileOutputStream); 155 mProtoReadWriter.writeToProto(protoOutputStream, states); 156 protoOutputStream.flush(); 157 atomicFile.finishWrite(fileOutputStream); 158 } catch (Exception e) { 159 Slog.e(TAG, "Failed to finish write to states protobuf.", e); 160 atomicFile.failWrite(fileOutputStream); 161 } 162 } 163 } 164