• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2021 Google LLC
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  *   https://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 package com.google.android.enterprise.connectedapps.internal;
17 
18 import static com.google.android.enterprise.connectedapps.CrossProfileSender.MAX_BYTES_PER_BLOCK;
19 import static com.google.android.enterprise.connectedapps.internal.BundleCallReceiver.STATUS_INCLUDES_BUNDLES;
20 import static com.google.android.enterprise.connectedapps.internal.BundleCallReceiver.STATUS_INCOMPLETE;
21 import static com.google.android.enterprise.connectedapps.internal.BundleCallReceiver.bytesRefersToBundle;
22 
23 import android.os.Bundle;
24 import android.os.Parcel;
25 import android.os.RemoteException;
26 import android.os.TransactionTooLargeException;
27 import android.util.Log;
28 import com.google.android.enterprise.connectedapps.exceptions.UnavailableProfileException;
29 import java.nio.ByteBuffer;
30 import java.util.Arrays;
31 import java.util.UUID;
32 
33 /**
34  * This represents a single action of (sending a {@link Parcel} and possibly fetching a response,
35  * which may be split up over many calls (if the payload is large).
36  *
37  * <p>The receiver should relay calls to a {@link BundleCallReceiver}.
38  */
39 abstract class BundleCallSender {
40 
41   private static final String LOG_TAG = "BundleCallSender";
42 
43   private static final long RETRY_DELAY_MILLIS = 10;
44   private static final int MAX_RETRIES = 10;
45 
46   /**
47    * The arguments passed to this should be passed to {@link BundleCallReceiver#prepareCall(long,
48    * int, int, byte[])}.
49    */
prepareCall(long callId, int blockId, int totalBytes, byte[] bytes)50   abstract void prepareCall(long callId, int blockId, int totalBytes, byte[] bytes)
51       throws RemoteException;
52 
53   /**
54    * The arguments passed to this should be passed to {@link BundleCallReceiver#prepareBundle(long,
55    * int, Bundle)}.
56    */
prepareBundle(long callId, int bundleId, Bundle bundle)57   abstract void prepareBundle(long callId, int bundleId, Bundle bundle) throws RemoteException;
58 
prepareCallAndRetry( long callId, int blockId, int totalBytes, byte[] bytes, int retries)59   private void prepareCallAndRetry(
60       long callId, int blockId, int totalBytes, byte[] bytes, int retries) throws RemoteException {
61     while (true) {
62       try {
63         prepareCall(callId, blockId, totalBytes, bytes);
64         break;
65       } catch (TransactionTooLargeException e) {
66         if (retries-- <= 0) {
67           throw e;
68         }
69 
70         try {
71           Thread.sleep(RETRY_DELAY_MILLIS);
72         } catch (InterruptedException ex) {
73           Log.w(LOG_TAG, "Interrupted on prepare retry", ex);
74           // If we can't sleep we'll just try again immediately
75         }
76       }
77     }
78   }
79 
prepareBundleAndRetry(long callId, int bundleId, Bundle bundle, int retries)80   private void prepareBundleAndRetry(long callId, int bundleId, Bundle bundle, int retries)
81       throws RemoteException {
82     while (true) {
83       try {
84         prepareBundle(callId, bundleId, bundle);
85         break;
86       } catch (TransactionTooLargeException e) {
87         if (retries-- <= 0) {
88           throw e;
89         }
90 
91         try {
92           Thread.sleep(RETRY_DELAY_MILLIS);
93         } catch (InterruptedException ex) {
94           Log.w(LOG_TAG, "Interrupted on prepare retry", ex);
95           // If we can't sleep we'll just try again immediately
96         }
97       }
98     }
99   }
100 
101   /**
102    * The arguments passed to this should be passed to {@link
103    * BundleCallReceiver#getPreparedCall(long, int, byte[])} and used to complete the call.
104    */
call(long callId, int blockId, byte[] bytes)105   abstract byte[] call(long callId, int blockId, byte[] bytes) throws RemoteException;
106 
callAndRetry(long callId, int blockId, byte[] bytes, int retries)107   private byte[] callAndRetry(long callId, int blockId, byte[] bytes, int retries)
108       throws RemoteException {
109     while (true) {
110       try {
111         byte[] returnBytes = call(callId, blockId, bytes);
112         if (returnBytes == null || returnBytes.length == 0) {
113           Log.w(
114               LOG_TAG,
115               String.format(
116                   "Call returned null or empty bytes from %s", super.getClass().getName()));
117         }
118         return returnBytes;
119       } catch (TransactionTooLargeException e) {
120         if (retries-- <= 0) {
121           throw e;
122         }
123 
124         try {
125           Thread.sleep(RETRY_DELAY_MILLIS);
126         } catch (InterruptedException ex) {
127           Log.w(LOG_TAG, "Interrupted on prepare retry", ex);
128           // If we can't sleep we'll just try again immediately
129         }
130       }
131     }
132   }
133 
134   /**
135    * The arguments passed to this should be passed to {@link
136    * BundleCallReceiver#getPreparedResponse(long, int)}.
137    */
fetchResponse(long callId, int blockId)138   abstract byte[] fetchResponse(long callId, int blockId) throws RemoteException;
139 
140   /**
141    * The arguments passed to this should be passed to {@link
142    * BundleCallReceiver#getPreparedResponseBundle(long, int)}.
143    */
fetchResponseBundle(long callId, int bundleId)144   abstract Bundle fetchResponseBundle(long callId, int bundleId) throws RemoteException;
145 
fetchResponseAndRetry(long callId, int blockId, int retries)146   private byte[] fetchResponseAndRetry(long callId, int blockId, int retries)
147       throws RemoteException {
148     while (true) {
149       try {
150         return fetchResponse(callId, blockId);
151       } catch (TransactionTooLargeException e) {
152         if (retries-- <= 0) {
153           throw e;
154         }
155 
156         try {
157           Thread.sleep(RETRY_DELAY_MILLIS);
158         } catch (InterruptedException ex) {
159           Log.w(LOG_TAG, "Interrupted on prepare retry", ex);
160           // If we can't sleep we'll just try again immediately
161         }
162       }
163     }
164   }
165 
fetchResponseBundleAndRetry(long callId, int bundleId, int retries)166   private Bundle fetchResponseBundleAndRetry(long callId, int bundleId, int retries)
167       throws RemoteException {
168     while (true) {
169       try {
170         Bundle b = fetchResponseBundle(callId, bundleId);
171         b.setClassLoader(Bundler.class.getClassLoader());
172         return b;
173       } catch (TransactionTooLargeException e) {
174         if (retries-- <= 0) {
175           throw e;
176         }
177 
178         try {
179           Thread.sleep(RETRY_DELAY_MILLIS);
180         } catch (InterruptedException ex) {
181           Log.w(LOG_TAG, "Interrupted on prepare retry", ex);
182           // If we can't sleep we'll just try again immediately
183         }
184       }
185     }
186   }
187 
188   /**
189    * Use the prepareCall(long, int, int, byte[])} and {@link #call(long, int, byte[])} methods to
190    * make a call.
191    *
192    * @throws UnavailableProfileException if any call fails
193    */
makeBundleCall(Bundle bundle)194   public Bundle makeBundleCall(Bundle bundle) throws UnavailableProfileException {
195     long callIdentifier = UUID.randomUUID().getMostSignificantBits();
196 
197     Parcel parcel = Parcel.obtain();
198     bundle.writeToParcel(parcel, /* flags= */ 0);
199     parcel.setDataPosition(0);
200 
201     byte[] bytes;
202 
203     try {
204       bytes = parcel.marshall();
205     } catch (RuntimeException | AssertionError e) {
206       // We can't marshall the parcel so we send the bundle directly
207       try {
208         prepareBundleAndRetry(callIdentifier, /* bundleId= */ 0, bundle, MAX_RETRIES);
209       } catch (RemoteException e1) {
210         throw new UnavailableProfileException("Error passing bundle for call", e1);
211       }
212       bytes = new byte[] {STATUS_INCLUDES_BUNDLES};
213     } finally {
214       parcel.recycle();
215     }
216 
217     byte[] returnBytes = makeParcelCall(callIdentifier, bytes);
218 
219     if (returnBytes == null) {
220       throw new IllegalStateException("Return bytes are null");
221     }
222     if (returnBytes.length == 0) {
223       Log.w(LOG_TAG, "Return bytes are empty");
224       return null;
225     }
226 
227     if (bytesRefersToBundle(returnBytes)) {
228       try {
229         return fetchResponseBundleAndRetry(callIdentifier, /* bundleId= */ 0, MAX_RETRIES);
230       } catch (RemoteException e) {
231         throw new UnavailableProfileException("Error fetching bundle for response", e);
232       }
233     }
234 
235     Parcel returnParcel = fetchResponseParcel(callIdentifier, returnBytes);
236     Bundle returnBundle = new Bundle(Bundler.class.getClassLoader());
237     returnBundle.readFromParcel(returnParcel);
238     returnParcel.recycle();
239 
240     return returnBundle;
241   }
242 
bytesAreIncomplete(byte[] bytes)243   private boolean bytesAreIncomplete(byte[] bytes) {
244     return bytes[0] == STATUS_INCOMPLETE;
245   }
246 
makeParcelCall(long callIdentifier, byte[] bytes)247   private byte[] makeParcelCall(long callIdentifier, byte[] bytes)
248       throws UnavailableProfileException {
249     try {
250       int numberOfBlocks = (int) Math.ceil(bytes.length * 1.0 / MAX_BYTES_PER_BLOCK);
251       int blockIdentifier = 0;
252 
253       if (numberOfBlocks > 1) {
254         byte[] block = new byte[MAX_BYTES_PER_BLOCK];
255 
256         // Loop through all but the last one and send them over to be cached (retrying any failures)
257         while (blockIdentifier < numberOfBlocks - 1) {
258           System.arraycopy(
259               bytes, blockIdentifier * MAX_BYTES_PER_BLOCK, block, 0, MAX_BYTES_PER_BLOCK);
260 
261           // Since we know block size is below the limit any errors will be temporary so we should
262           // retry
263           prepareCallAndRetry(callIdentifier, blockIdentifier, bytes.length, block, MAX_RETRIES);
264           blockIdentifier++;
265         }
266 
267         bytes = Arrays.copyOfRange(bytes, blockIdentifier * MAX_BYTES_PER_BLOCK, bytes.length);
268       }
269 
270       // Since we know block size is below the limit any errors will be temporary so we should retry
271       return callAndRetry(callIdentifier, blockIdentifier, bytes, MAX_RETRIES);
272     } catch (RemoteException e) {
273       throw new UnavailableProfileException("Could not access other profile", e);
274     }
275   }
276 
fetchResponseParcel(long callIdentifier, byte[] returnBytes)277   private Parcel fetchResponseParcel(long callIdentifier, byte[] returnBytes)
278       throws UnavailableProfileException {
279 
280     // returnBytes[0] is 0 if the bytes are complete, or 1 if we need to fetch more
281     int byteOffset = 1;
282     if (bytesAreIncomplete(returnBytes)) {
283       // returnBytes[1] - returnBytes[4] are an int representing the total size of the return
284       // value
285       int totalBytes = ByteBuffer.wrap(returnBytes).getInt(/* index= */ 1);
286 
287       try {
288         returnBytes = fetchReturnBytes(totalBytes, callIdentifier, returnBytes);
289       } catch (RemoteException e) {
290         throw new UnavailableProfileException("Could not access other profile", e);
291       }
292       byteOffset = 0;
293     }
294     Parcel p = Parcel.obtain(); // Recycled by caller
295     p.unmarshall(
296         returnBytes, /* offset= */ byteOffset, /* length= */ returnBytes.length - byteOffset);
297     p.setDataPosition(0);
298     return p;
299   }
300 
fetchReturnBytes(int totalBytes, long callId, byte[] initialBytes)301   private byte[] fetchReturnBytes(int totalBytes, long callId, byte[] initialBytes)
302       throws RemoteException {
303     byte[] returnBytes = new byte[totalBytes];
304 
305     // Skip the first 5 bytes which are used for status
306     System.arraycopy(
307         initialBytes,
308         /* srcPos= */ 5,
309         returnBytes,
310         /* destPos= */ 0,
311         /* length= */ MAX_BYTES_PER_BLOCK);
312 
313     int numberOfBlocks = (int) Math.ceil(totalBytes * 1.0 / MAX_BYTES_PER_BLOCK);
314 
315     for (int block = 1; block < numberOfBlocks; block++) { // Skip 0 as we already have it
316       // Since we know block size is below the limit any errors will be temporary so we should retry
317       byte[] bytes = fetchResponseAndRetry(callId, block, MAX_RETRIES);
318       System.arraycopy(
319           bytes,
320           /* srcPos= */ 0,
321           returnBytes,
322           /* destPos= */ block * MAX_BYTES_PER_BLOCK,
323           /* length= */ bytes.length);
324     }
325     return returnBytes;
326   }
327 }
328