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