/* * Copyright (C) 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.car.carlaunchercommon.proto import android.util.Log import com.google.protobuf.MessageLite import java.io.File import java.io.FileInputStream import java.io.FileOutputStream import java.io.IOException import java.io.InputStream import java.io.OutputStream /** * Class level abstraction representing a proto file holding app data. * * Only a single controller should hold reference to this class. All methods that perform read or * write operations must be thread safe and idempotent. * * @param the proto object type that this data file is holding */ // TODO: b/301482942 This class is copied from AppGrid. We should reuse it in AppGrid abstract class ProtoDataSource(private val dataFile: File) { private var mInputStream: FileInputStream? = null private var mOutputStream: FileOutputStream? = null /** * @return true if the file exists on disk, and false otherwise. */ fun exists(): Boolean { return try { dataFile.exists() && dataFile.canRead() } catch (e: Exception) { false } } /** * Writes the [MessageLite] subclass T to the file represented by this object. * This method will write data as bytes to the declared file using protobuf library. */ fun writeToFile(data: T) { try { if (mOutputStream == null) { mOutputStream = FileOutputStream(dataFile, false) } writeDelimitedTo(data, mOutputStream) } catch (e: IOException) { Log.e(TAG, "Dock item list not written to file successfully.", e) } finally { try { mOutputStream?.apply { flush() fd.sync() close() } mOutputStream = null } catch (e: IOException) { Log.e(TAG, "Unable to close output stream. ") } } } /** * Reads the [MessageLite] subclass T from the file represented by this object. * This method will parse the bytes using protobuf library. */ fun readFromFile(): T? { if (!exists()) { Log.e(TAG, "File does not exist. Cannot read from file.") return null } var result: T? = null try { if (mInputStream == null) { mInputStream = FileInputStream(dataFile) } result = parseDelimitedFrom(mInputStream) } catch (e: IOException) { Log.e(TAG, "Read from input stream not successfully") } finally { try { mInputStream?.close() mInputStream = null } catch (e: IOException) { Log.e(TAG, "Unable to close input stream") } } return result } /** * This method will be called by [ProtoDataSource.readFromFile]. * * Implementation is left to subclass since [MessageLite.parseDelimitedFrom] * requires a defined class at compile time. Subclasses should implement this method by directly * calling YourMessageType.parseDelimitedFrom(inputStream) here. * * @param inputStream the input stream to be which the data source should read from. * @return the object T written to this file. * @throws IOException an IOException for when reading from proto fails. */ @Throws(IOException::class) protected abstract fun parseDelimitedFrom(inputStream: InputStream?): T? /** * This method will be called by [ProtoDataSource.writeToFile]. * * Implementation is left to subclass since [MessageLite.writeDelimitedTo] * requires a defined class at compile time. Subclasses should implement this method by directly * calling T.writeDelimitedTo(outputStream) here. * * @param outputData the output data T to be written to the file. * @param outputStream the output stream which the data should be written to. * @throws IOException an IO Exception for when writing to proto fails. */ @Throws(IOException::class) protected abstract fun writeDelimitedTo(outputData: T, outputStream: OutputStream?) companion object { private const val TAG = "ProtoDataSource" } override fun toString(): String { return dataFile.absolutePath } }