• 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         return call(callId, blockId, bytes);
112       } catch (TransactionTooLargeException e) {
113         if (retries-- <= 0) {
114           throw e;
115         }
116 
117         try {
118           Thread.sleep(RETRY_DELAY_MILLIS);
119         } catch (InterruptedException ex) {
120           Log.w(LOG_TAG, "Interrupted on prepare retry", ex);
121           // If we can't sleep we'll just try again immediately
122         }
123       }
124     }
125   }
126 
127   /**
128    * The arguments passed to this should be passed to {@link
129    * BundleCallReceiver#getPreparedResponse(long, int)}.
130    */
fetchResponse(long callId, int blockId)131   abstract byte[] fetchResponse(long callId, int blockId) throws RemoteException;
132 
133   /**
134    * The arguments passed to this should be passed to {@link
135    * BundleCallReceiver#getPreparedResponseBundle(long, int)}.
136    */
fetchResponseBundle(long callId, int bundleId)137   abstract Bundle fetchResponseBundle(long callId, int bundleId) throws RemoteException;
138 
fetchResponseAndRetry(long callId, int blockId, int retries)139   private byte[] fetchResponseAndRetry(long callId, int blockId, int retries)
140       throws RemoteException {
141     while (true) {
142       try {
143         return fetchResponse(callId, blockId);
144       } catch (TransactionTooLargeException e) {
145         if (retries-- <= 0) {
146           throw e;
147         }
148 
149         try {
150           Thread.sleep(RETRY_DELAY_MILLIS);
151         } catch (InterruptedException ex) {
152           Log.w(LOG_TAG, "Interrupted on prepare retry", ex);
153           // If we can't sleep we'll just try again immediately
154         }
155       }
156     }
157   }
158 
fetchResponseBundleAndRetry(long callId, int bundleId, int retries)159   private Bundle fetchResponseBundleAndRetry(long callId, int bundleId, int retries)
160       throws RemoteException {
161     while (true) {
162       try {
163         Bundle b = fetchResponseBundle(callId, bundleId);
164         b.setClassLoader(Bundler.class.getClassLoader());
165         return b;
166       } catch (TransactionTooLargeException e) {
167         if (retries-- <= 0) {
168           throw e;
169         }
170 
171         try {
172           Thread.sleep(RETRY_DELAY_MILLIS);
173         } catch (InterruptedException ex) {
174           Log.w(LOG_TAG, "Interrupted on prepare retry", ex);
175           // If we can't sleep we'll just try again immediately
176         }
177       }
178     }
179   }
180 
181   /**
182    * Use the prepareCall(long, int, int, byte[])} and {@link #call(long, int, byte[])} methods to
183    * make a call.
184    *
185    * @throws UnavailableProfileException if any call fails
186    */
makeBundleCall(Bundle bundle)187   public Bundle makeBundleCall(Bundle bundle) throws UnavailableProfileException {
188     long callIdentifier = UUID.randomUUID().getMostSignificantBits();
189 
190     Parcel parcel = Parcel.obtain();
191     bundle.writeToParcel(parcel, /* flags= */ 0);
192     parcel.setDataPosition(0);
193 
194     byte[] bytes;
195 
196     try {
197       bytes = parcel.marshall();
198     } catch (RuntimeException | AssertionError e) {
199       // We can't marshall the parcel so we send the bundle directly
200       try {
201         prepareBundleAndRetry(callIdentifier, /* bundleId= */ 0, bundle, MAX_RETRIES);
202       } catch (RemoteException e1) {
203         throw new UnavailableProfileException("Error passing bundle for call", e1);
204       }
205       bytes = new byte[] {STATUS_INCLUDES_BUNDLES};
206     } finally {
207       parcel.recycle();
208     }
209 
210     byte[] returnBytes = makeParcelCall(callIdentifier, bytes);
211 
212     if (returnBytes.length == 0) {
213       return null;
214     }
215 
216     if (bytesRefersToBundle(returnBytes)) {
217       try {
218         return fetchResponseBundleAndRetry(callIdentifier, /* bundleId= */ 0, MAX_RETRIES);
219       } catch (RemoteException e) {
220         throw new UnavailableProfileException("Error fetching bundle for response", e);
221       }
222     }
223 
224     Parcel returnParcel = fetchResponseParcel(callIdentifier, returnBytes);
225     Bundle returnBundle = new Bundle(Bundler.class.getClassLoader());
226     returnBundle.readFromParcel(returnParcel);
227     returnParcel.recycle();
228 
229     return returnBundle;
230   }
231 
bytesAreIncomplete(byte[] bytes)232   private boolean bytesAreIncomplete(byte[] bytes) {
233     return bytes[0] == STATUS_INCOMPLETE;
234   }
235 
makeParcelCall(long callIdentifier, byte[] bytes)236   private byte[] makeParcelCall(long callIdentifier, byte[] bytes)
237       throws UnavailableProfileException {
238     try {
239       int numberOfBlocks = (int) Math.ceil(bytes.length * 1.0 / MAX_BYTES_PER_BLOCK);
240       int blockIdentifier = 0;
241 
242       if (numberOfBlocks > 1) {
243         byte[] block = new byte[MAX_BYTES_PER_BLOCK];
244 
245         // Loop through all but the last one and send them over to be cached (retrying any failures)
246         while (blockIdentifier < numberOfBlocks - 1) {
247           System.arraycopy(
248               bytes, blockIdentifier * MAX_BYTES_PER_BLOCK, block, 0, MAX_BYTES_PER_BLOCK);
249 
250           // Since we know block size is below the limit any errors will be temporary so we should
251           // retry
252           prepareCallAndRetry(callIdentifier, blockIdentifier, bytes.length, block, MAX_RETRIES);
253           blockIdentifier++;
254         }
255 
256         bytes = Arrays.copyOfRange(bytes, blockIdentifier * MAX_BYTES_PER_BLOCK, bytes.length);
257       }
258 
259       // Since we know block size is below the limit any errors will be temporary so we should retry
260       return callAndRetry(callIdentifier, blockIdentifier, bytes, MAX_RETRIES);
261     } catch (RemoteException e) {
262       throw new UnavailableProfileException("Could not access other profile", e);
263     }
264   }
265 
fetchResponseParcel(long callIdentifier, byte[] returnBytes)266   private Parcel fetchResponseParcel(long callIdentifier, byte[] returnBytes)
267       throws UnavailableProfileException {
268 
269     // returnBytes[0] is 0 if the bytes are complete, or 1 if we need to fetch more
270     int byteOffset = 1;
271     if (bytesAreIncomplete(returnBytes)) {
272       // returnBytes[1] - returnBytes[4] are an int representing the total size of the return
273       // value
274       int totalBytes = ByteBuffer.wrap(returnBytes).getInt(/* index= */ 1);
275 
276       try {
277         returnBytes = fetchReturnBytes(totalBytes, callIdentifier, returnBytes);
278       } catch (RemoteException e) {
279         throw new UnavailableProfileException("Could not access other profile", e);
280       }
281       byteOffset = 0;
282     }
283     Parcel p = Parcel.obtain(); // Recycled by caller
284     p.unmarshall(
285         returnBytes, /* offset= */ byteOffset, /* length= */ returnBytes.length - byteOffset);
286     p.setDataPosition(0);
287     return p;
288   }
289 
fetchReturnBytes(int totalBytes, long callId, byte[] initialBytes)290   private byte[] fetchReturnBytes(int totalBytes, long callId, byte[] initialBytes)
291       throws RemoteException {
292     byte[] returnBytes = new byte[totalBytes];
293 
294     // Skip the first 5 bytes which are used for status
295     System.arraycopy(
296         initialBytes,
297         /* srcPos= */ 5,
298         returnBytes,
299         /* destPos= */ 0,
300         /* length= */ MAX_BYTES_PER_BLOCK);
301 
302     int numberOfBlocks = (int) Math.ceil(totalBytes * 1.0 / MAX_BYTES_PER_BLOCK);
303 
304     for (int block = 1; block < numberOfBlocks; block++) { // Skip 0 as we already have it
305       // Since we know block size is below the limit any errors will be temporary so we should retry
306       byte[] bytes = fetchResponseAndRetry(callId, block, MAX_RETRIES);
307       System.arraycopy(
308           bytes,
309           /* srcPos= */ 0,
310           returnBytes,
311           /* destPos= */ block * MAX_BYTES_PER_BLOCK,
312           /* length= */ bytes.length);
313     }
314     return returnBytes;
315   }
316 }
317