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 android.app.appsearch; 18 19 import android.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.annotation.SuppressLint; 22 import android.os.Build; 23 import android.os.Parcel; 24 import android.os.ParcelFileDescriptor; 25 import android.util.Log; 26 27 import java.io.DataInputStream; 28 import java.io.DataOutputStream; 29 import java.io.File; 30 import java.io.FileOutputStream; 31 import java.io.IOException; 32 33 /** 34 * Wrapper class to provide implementation for readBlob/writeBlob for all API levels. 35 * 36 * @hide 37 */ 38 public class ParcelableUtil { 39 private static final String TAG = "AppSearchParcel"; 40 private static final String TEMP_FILE_PREFIX = "AppSearchSerializedBytes"; 41 private static final String TEMP_FILE_SUFFIX = ".tmp"; 42 // Same as IBinder.MAX_IPC_LIMIT. Limit that should be placed on IPC sizes to keep them safely 43 // under the transaction buffer limit. 44 private static final int DOCUMENT_SIZE_LIMIT_IN_BYTES = 64 * 1024; 45 46 // TODO(b/232805516): Update SDK_INT in Android.bp to safeguard from unexpected compiler issues. 47 @SuppressLint("ObsoleteSdkInt") writeBlob(@onNull Parcel parcel, @NonNull byte[] bytes)48 public static void writeBlob(@NonNull Parcel parcel, @NonNull byte[] bytes) { 49 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { 50 // Since parcel.writeBlob was added in API level 33, it is not available 51 // on lower API levels. 52 parcel.writeBlob(bytes); 53 } else { 54 writeToParcelForSAndBelow(parcel, bytes); 55 } 56 } 57 writeToParcelForSAndBelow(Parcel parcel, byte[] bytes)58 private static void writeToParcelForSAndBelow(Parcel parcel, byte[] bytes) { 59 try { 60 parcel.writeInt(bytes.length); 61 if (bytes.length <= DOCUMENT_SIZE_LIMIT_IN_BYTES) { 62 parcel.writeByteArray(bytes); 63 } else { 64 ParcelFileDescriptor parcelFileDescriptor = writeDataToTempFileAndUnlinkFile(bytes); 65 parcel.writeFileDescriptor(parcelFileDescriptor.getFileDescriptor()); 66 } 67 } catch (IOException e) { 68 // TODO(b/232805516): Add abstraction to handle the exception based on environment. 69 Log.w(TAG, "Couldn't write to unlinked file.", e); 70 } 71 } 72 73 /** Calls parcel#readBlob on T+ and uses byte array or PFD on lower API levels. */ 74 @Nullable 75 // TODO(b/232805516): Update SDK_INT in Android.bp to safeguard from unexpected compiler issues. 76 @SuppressLint("ObsoleteSdkInt") readBlob(Parcel parcel)77 public static byte[] readBlob(Parcel parcel) { 78 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { 79 // Since parcel.readBlob was added in API level 33, it is not available 80 // on lower API levels. 81 return parcel.readBlob(); 82 } else { 83 return readFromParcelForSAndBelow(parcel); 84 } 85 } 86 87 @Nullable readFromParcelForSAndBelow(Parcel parcel)88 private static byte[] readFromParcelForSAndBelow(Parcel parcel) { 89 try { 90 int length = parcel.readInt(); 91 if (length <= DOCUMENT_SIZE_LIMIT_IN_BYTES) { 92 byte[] documentByteArray = new byte[length]; 93 parcel.readByteArray(documentByteArray); 94 return documentByteArray; 95 } else { 96 ParcelFileDescriptor pfd = parcel.readFileDescriptor(); 97 return getDataFromFd(pfd, length); 98 } 99 } catch (IOException e) { 100 // TODO(b/232805516): Add abstraction to handle the exception based on environment. 101 Log.w(TAG, "Couldn't read from unlinked file.", e); 102 return null; 103 } 104 } 105 106 /** 107 * Reads data bytes from file using provided FileDescriptor. It also closes the PFD so that will 108 * delete the underlying file if it's the only reference left. 109 * 110 * @param pfd ParcelFileDescriptor for the file to read. 111 * @param length Number of bytes to read from the file. 112 */ getDataFromFd(ParcelFileDescriptor pfd, int length)113 private static byte[] getDataFromFd(ParcelFileDescriptor pfd, int length) throws IOException { 114 try (DataInputStream in = 115 new DataInputStream(new ParcelFileDescriptor.AutoCloseInputStream(pfd))) { 116 byte[] data = new byte[length]; 117 in.read(data); 118 return data; 119 } 120 } 121 122 /** 123 * Writes to a temp file owned by the caller, then unlinks/deletes it, and returns an FD which 124 * is the only remaining reference to the tmp file. 125 * 126 * @param data Data in the form of byte array to write to the file. 127 */ writeDataToTempFileAndUnlinkFile(byte[] data)128 private static ParcelFileDescriptor writeDataToTempFileAndUnlinkFile(byte[] data) 129 throws IOException { 130 // TODO(b/232959004): Update directory to app-specific cache dir instead of null. 131 File unlinkedFile = 132 File.createTempFile(TEMP_FILE_PREFIX, TEMP_FILE_SUFFIX, /* directory= */ null); 133 try (DataOutputStream out = new DataOutputStream(new FileOutputStream(unlinkedFile))) { 134 out.write(data); 135 out.flush(); 136 } 137 ParcelFileDescriptor parcelFileDescriptor = 138 ParcelFileDescriptor.open( 139 unlinkedFile, 140 ParcelFileDescriptor.MODE_CREATE | ParcelFileDescriptor.MODE_READ_WRITE); 141 unlinkedFile.delete(); 142 return parcelFileDescriptor; 143 } 144 ParcelableUtil()145 private ParcelableUtil() {} 146 } 147