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