1 /* 2 * Copyright (C) 2024 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.carlaunchercommon.proto 18 19 import android.util.Log 20 import com.google.protobuf.MessageLite 21 import java.io.File 22 import java.io.FileInputStream 23 import java.io.FileOutputStream 24 import java.io.IOException 25 import java.io.InputStream 26 import java.io.OutputStream 27 28 /** 29 * Class level abstraction representing a proto file holding app data. 30 * 31 * Only a single controller should hold reference to this class. All methods that perform read or 32 * write operations must be thread safe and idempotent. 33 * 34 * @param <T> the proto object type that this data file is holding 35 </T> */ 36 // TODO: b/301482942 This class is copied from AppGrid. We should reuse it in AppGrid 37 abstract class ProtoDataSource<T : MessageLite>(private val dataFile: File) { 38 private var mInputStream: FileInputStream? = null 39 private var mOutputStream: FileOutputStream? = null 40 41 /** 42 * @return true if the file exists on disk, and false otherwise. 43 */ existsnull44 fun exists(): Boolean { 45 return try { 46 dataFile.exists() && dataFile.canRead() 47 } catch (e: Exception) { 48 false 49 } 50 } 51 52 /** 53 * Writes the [MessageLite] subclass T to the file represented by this object. 54 * This method will write data as bytes to the declared file using protobuf library. 55 */ writeToFilenull56 fun writeToFile(data: T) { 57 try { 58 if (mOutputStream == null) { 59 mOutputStream = FileOutputStream(dataFile, false) 60 } 61 writeDelimitedTo(data, mOutputStream) 62 } catch (e: IOException) { 63 Log.e(TAG, "Dock item list not written to file successfully.", e) 64 } finally { 65 try { 66 mOutputStream?.apply { 67 flush() 68 fd.sync() 69 close() 70 } 71 mOutputStream = null 72 } catch (e: IOException) { 73 Log.e(TAG, "Unable to close output stream. ") 74 } 75 } 76 } 77 78 /** 79 * Reads the [MessageLite] subclass T from the file represented by this object. 80 * This method will parse the bytes using protobuf library. 81 */ readFromFilenull82 fun readFromFile(): T? { 83 if (!exists()) { 84 Log.e(TAG, "File does not exist. Cannot read from file.") 85 return null 86 } 87 var result: T? = null 88 try { 89 if (mInputStream == null) { 90 mInputStream = FileInputStream(dataFile) 91 } 92 result = parseDelimitedFrom(mInputStream) 93 } catch (e: IOException) { 94 Log.e(TAG, "Read from input stream not successfully") 95 } finally { 96 try { 97 mInputStream?.close() 98 mInputStream = null 99 } catch (e: IOException) { 100 Log.e(TAG, "Unable to close input stream") 101 } 102 } 103 return result 104 } 105 106 /** 107 * This method will be called by [ProtoDataSource.readFromFile]. 108 * 109 * Implementation is left to subclass since [MessageLite.parseDelimitedFrom] 110 * requires a defined class at compile time. Subclasses should implement this method by directly 111 * calling YourMessageType.parseDelimitedFrom(inputStream) here. 112 * 113 * @param inputStream the input stream to be which the data source should read from. 114 * @return the object T written to this file. 115 * @throws IOException an IOException for when reading from proto fails. 116 */ 117 @Throws(IOException::class) parseDelimitedFromnull118 protected abstract fun parseDelimitedFrom(inputStream: InputStream?): T? 119 120 /** 121 * This method will be called by [ProtoDataSource.writeToFile]. 122 * 123 * Implementation is left to subclass since [MessageLite.writeDelimitedTo] 124 * requires a defined class at compile time. Subclasses should implement this method by directly 125 * calling T.writeDelimitedTo(outputStream) here. 126 * 127 * @param outputData the output data T to be written to the file. 128 * @param outputStream the output stream which the data should be written to. 129 * @throws IOException an IO Exception for when writing to proto fails. 130 */ 131 @Throws(IOException::class) 132 protected abstract fun writeDelimitedTo(outputData: T, outputStream: OutputStream?) 133 134 companion object { 135 private const val TAG = "ProtoDataSource" 136 } 137 toStringnull138 override fun toString(): String { 139 return dataFile.absolutePath 140 } 141 } 142