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.health.connect.internal;
18 
19 import android.annotation.NonNull;
20 import android.os.IBinder;
21 import android.os.Parcel;
22 import android.os.SharedMemory;
23 import android.system.ErrnoException;
24 
25 import com.android.internal.annotations.VisibleForTesting;
26 
27 import java.nio.ByteBuffer;
28 
29 /** @hide */
30 public final class ParcelUtils {
31     @VisibleForTesting public static final int USING_SHARED_MEMORY = 0;
32     @VisibleForTesting public static final int USING_PARCEL = 1;
33 
34     @VisibleForTesting
35     public static final int IPC_PARCEL_LIMIT = IBinder.getSuggestedMaxIpcSizeBytes() / 2;
36 
37     public interface IPutToParcelRunnable {
writeToParcel(Parcel dest)38         void writeToParcel(Parcel dest);
39     }
40 
41     @NonNull
getParcelForSharedMemoryIfRequired(Parcel in)42     public static Parcel getParcelForSharedMemoryIfRequired(Parcel in) {
43         int parcelType = in.readInt();
44         if (parcelType == USING_SHARED_MEMORY) {
45             try (SharedMemory memory = SharedMemory.CREATOR.createFromParcel(in)) {
46                 Parcel dataParcel = Parcel.obtain();
47                 ByteBuffer buffer = memory.mapReadOnly();
48                 byte[] payload = new byte[buffer.limit()];
49                 buffer.get(payload);
50                 dataParcel.unmarshall(payload, 0, payload.length);
51                 dataParcel.setDataPosition(0);
52                 return dataParcel;
53             } catch (ErrnoException e) {
54                 throw new RuntimeException(e);
55             }
56         }
57         return in;
58     }
59 
getSharedMemoryForParcel(Parcel dataParcel, int dataParcelSize)60     public static SharedMemory getSharedMemoryForParcel(Parcel dataParcel, int dataParcelSize) {
61         try {
62             SharedMemory sharedMemory =
63                     SharedMemory.create("RecordsParcelSharedMemory", dataParcelSize);
64             ByteBuffer buffer = sharedMemory.mapReadWrite();
65             byte[] data = dataParcel.marshall();
66             buffer.put(data, 0, dataParcelSize);
67             return sharedMemory;
68         } catch (ErrnoException e) {
69             throw new RuntimeException(e);
70         }
71     }
72 
73     /**
74      * Determines which memory to use and puts the {@code parcel} in it, and details of it in {@code
75      * dest}
76      */
putToRequiredMemory( Parcel dest, int flags, IPutToParcelRunnable parcelRunnable)77     public static void putToRequiredMemory(
78             Parcel dest, int flags, IPutToParcelRunnable parcelRunnable) {
79         final Parcel dataParcel = Parcel.obtain();
80         try {
81             parcelRunnable.writeToParcel(dataParcel);
82             final int dataParcelSize = dataParcel.dataSize();
83             if (dataParcelSize > IPC_PARCEL_LIMIT) {
84                 try (SharedMemory sharedMemory =
85                         ParcelUtils.getSharedMemoryForParcel(dataParcel, dataParcelSize)) {
86                     dest.writeInt(USING_SHARED_MEMORY);
87                     sharedMemory.writeToParcel(dest, flags);
88                 }
89             } else {
90                 dest.writeInt(USING_PARCEL);
91                 parcelRunnable.writeToParcel(dest);
92             }
93         } finally {
94             dataParcel.recycle();
95         }
96     }
97 }
98