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