1 /* 2 * Copyright (C) 2023 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.car.carlauncher.datastore; 18 19 import android.util.Log; 20 21 import androidx.annotation.Nullable; 22 23 import com.google.protobuf.MessageLite; 24 25 import java.io.File; 26 import java.io.FileInputStream; 27 import java.io.FileOutputStream; 28 import java.io.IOException; 29 import java.io.InputStream; 30 import java.io.OutputStream; 31 import java.util.concurrent.ExecutorService; 32 import java.util.concurrent.Executors; 33 34 /** 35 * Class level abstraction representing a proto file holding app data. 36 * 37 * Only a single controller should hold reference to this class. All methods that perform read or 38 * write operations must be thread safe and idempotent. 39 * 40 * @param <T> the proto object type that this data file is holding 41 */ 42 public abstract class ProtoDataSource<T extends MessageLite> { 43 private final File mFile; 44 private static final String TAG = "ProtoDataSource"; 45 private FileInputStream mInputStream; 46 private FileOutputStream mOutputStream; 47 ProtoDataSource(File dataFileDirectory, String dataFileName)48 public ProtoDataSource(File dataFileDirectory, String dataFileName) { 49 mFile = new File(dataFileDirectory, dataFileName); 50 } 51 52 /** 53 * @return true if the file exists on disk, and false otherwise. 54 */ exists()55 public boolean exists() { 56 return mFile.exists(); 57 } 58 59 /** 60 * Used by subclasses to access the mFile object. 61 */ getDataFile()62 protected File getDataFile() { 63 return mFile; 64 } 65 66 /** 67 * Writes the {@link MessageLite} subclass T to the file represented by this object in the 68 * background thread. 69 */ writeToFileInBackgroundThread(T data)70 public void writeToFileInBackgroundThread(T data) { 71 ExecutorService executorService = Executors.newSingleThreadExecutor(); 72 executorService.execute(() -> { 73 writeToFile(data); 74 executorService.shutdown(); 75 }); 76 } 77 78 /** 79 * Writes the {@link MessageLite} subclass T to the file represented by this object. 80 */ writeToFile(T data)81 public boolean writeToFile(T data) { 82 boolean success = true; 83 try { 84 if (mOutputStream == null) { 85 mOutputStream = new FileOutputStream(getDataFile(), false); 86 } 87 writeDelimitedTo(data, mOutputStream); 88 } catch (IOException e) { 89 Log.e(TAG, "Launcher item list not written to file successfully."); 90 success = false; 91 } finally { 92 try { 93 if (mOutputStream != null) { 94 mOutputStream.flush(); 95 mOutputStream.getFD().sync(); 96 mOutputStream.close(); 97 mOutputStream = null; 98 } 99 } catch (IOException e) { 100 Log.e(TAG, "Unable to close output stream. "); 101 } 102 } 103 return success; 104 } 105 106 /** 107 * Reads the {@link MessageLite} subclass T from the file represented by this object. 108 */ 109 @Nullable readFromFile()110 public T readFromFile() { 111 if (!exists()) { 112 Log.e(TAG, "File does not exist. Cannot read from file."); 113 return null; 114 } 115 T result = null; 116 try { 117 if (mInputStream == null) { 118 mInputStream = new FileInputStream(getDataFile()); 119 } 120 result = parseDelimitedFrom(mInputStream); 121 } catch (IOException e) { 122 Log.e(TAG, "Read from input stream not successfully"); 123 } finally { 124 if (mInputStream != null) { 125 try { 126 mInputStream.close(); 127 mInputStream = null; 128 } catch (IOException e) { 129 Log.e(TAG, "Unable to close input stream"); 130 } 131 } 132 } 133 return result; 134 } 135 136 /** 137 * @return True if delete file was successful, false otherwise 138 */ deleteFile()139 public boolean deleteFile() { 140 boolean success = false; 141 try { 142 if (mFile.exists()) { 143 success = mFile.delete(); 144 } 145 } catch (SecurityException ex) { 146 Log.e(TAG, "deleteFile - " + ex); 147 } 148 return success; 149 } 150 151 /** 152 * This method will be called by {@link ProtoDataSource#readFromFile}. 153 * 154 * Implementation is left to subclass since {@link MessageLite.parseDelimitedFrom(InputStream)} 155 * requires a defined class at compile time. Subclasses should implement this method by directly 156 * calling YourMessageType.parseDelimitedFrom(inputStream) here. 157 * 158 * @param inputStream the input stream to be which the data source should read from. 159 * @return the object T written to this file. 160 * @throws IOException an IOException for when reading from proto fails. 161 */ 162 @Nullable parseDelimitedFrom(InputStream inputStream)163 protected abstract T parseDelimitedFrom(InputStream inputStream) throws IOException; 164 165 /** 166 * This method will be called by 167 * {@link ProtoDataSource#writeToFileInBackgroundThread(MessageLite)}. 168 * 169 * Implementation is left to subclass since {@link MessageLite#writeDelimitedTo(OutputStream)} 170 * requires a defined class at compile time. Subclasses should implement this method by directly 171 * calling T.writeDelimitedTo(outputStream) here. 172 * 173 * @param outputData the output data T to be written to the file. 174 * @param outputStream the output stream which the data should be written to. 175 * @throws IOException an IO Exception for when writing to proto fails. 176 */ writeDelimitedTo(T outputData, OutputStream outputStream)177 protected abstract void writeDelimitedTo(T outputData, OutputStream outputStream) 178 throws IOException; 179 } 180