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 package com.android.internal.widget.remotecompose.player.platform; 17 18 import android.annotation.NonNull; 19 import android.annotation.Nullable; 20 import android.graphics.Bitmap; 21 import android.graphics.BitmapFactory; 22 import android.graphics.Canvas; 23 import android.graphics.Paint; 24 25 import com.android.internal.widget.remotecompose.core.RemoteContext; 26 import com.android.internal.widget.remotecompose.core.TouchListener; 27 import com.android.internal.widget.remotecompose.core.VariableSupport; 28 import com.android.internal.widget.remotecompose.core.operations.BitmapData; 29 import com.android.internal.widget.remotecompose.core.operations.FloatExpression; 30 import com.android.internal.widget.remotecompose.core.operations.ShaderData; 31 import com.android.internal.widget.remotecompose.core.operations.utilities.ArrayAccess; 32 import com.android.internal.widget.remotecompose.core.operations.utilities.DataMap; 33 import com.android.internal.widget.remotecompose.core.types.LongConstant; 34 35 import java.io.IOException; 36 import java.net.MalformedURLException; 37 import java.net.URL; 38 import java.util.HashMap; 39 40 /** 41 * An implementation of Context for Android. 42 * 43 * <p>This is used to play the RemoteCompose operations on Android. 44 */ 45 public class AndroidRemoteContext extends RemoteContext { 46 useCanvas(Canvas canvas)47 public void useCanvas(Canvas canvas) { 48 if (mPaintContext == null) { 49 mPaintContext = new AndroidPaintContext(this, canvas); 50 } else { 51 // need to make sure to update the canvas for the current one 52 mPaintContext.reset(); 53 ((AndroidPaintContext) mPaintContext).setCanvas(canvas); 54 } 55 mWidth = canvas.getWidth(); 56 mHeight = canvas.getHeight(); 57 } 58 59 /////////////////////////////////////////////////////////////////////////////////////////////// 60 // Data handling 61 /////////////////////////////////////////////////////////////////////////////////////////////// 62 63 @Override loadPathData(int instanceId, @NonNull float[] floatPath)64 public void loadPathData(int instanceId, @NonNull float[] floatPath) { 65 mRemoteComposeState.putPathData(instanceId, floatPath); 66 } 67 68 @Override getPathData(int instanceId)69 public float[] getPathData(int instanceId) { 70 return mRemoteComposeState.getPathData(instanceId); 71 } 72 73 static class VarName { 74 String mName; 75 int mId; 76 int mType; 77 VarName(String name, int id, int type)78 VarName(String name, int id, int type) { 79 mName = name; 80 mId = id; 81 mType = type; 82 } 83 } 84 85 HashMap<String, VarName> mVarNameHashMap = new HashMap<>(); 86 87 @Override loadVariableName(@onNull String varName, int varId, int varType)88 public void loadVariableName(@NonNull String varName, int varId, int varType) { 89 mVarNameHashMap.put(varName, new VarName(varName, varId, varType)); 90 } 91 92 @Override setNamedStringOverride(@onNull String stringName, @NonNull String value)93 public void setNamedStringOverride(@NonNull String stringName, @NonNull String value) { 94 if (mVarNameHashMap.get(stringName) != null) { 95 int id = mVarNameHashMap.get(stringName).mId; 96 overrideText(id, value); 97 } 98 } 99 100 @Override clearNamedStringOverride(@onNull String stringName)101 public void clearNamedStringOverride(@NonNull String stringName) { 102 if (mVarNameHashMap.get(stringName) != null) { 103 int id = mVarNameHashMap.get(stringName).mId; 104 clearDataOverride(id); 105 } 106 mVarNameHashMap.put(stringName, null); 107 } 108 109 @Override setNamedIntegerOverride(@onNull String stringName, int value)110 public void setNamedIntegerOverride(@NonNull String stringName, int value) { 111 if (mVarNameHashMap.get(stringName) != null) { 112 int id = mVarNameHashMap.get(stringName).mId; 113 overrideInt(id, value); 114 } 115 } 116 117 @Override clearNamedIntegerOverride(@onNull String integerName)118 public void clearNamedIntegerOverride(@NonNull String integerName) { 119 if (mVarNameHashMap.get(integerName) != null) { 120 int id = mVarNameHashMap.get(integerName).mId; 121 clearIntegerOverride(id); 122 } 123 mVarNameHashMap.put(integerName, null); 124 } 125 126 @Override setNamedFloatOverride(String floatName, float value)127 public void setNamedFloatOverride(String floatName, float value) { 128 if (mVarNameHashMap.get(floatName) != null) { 129 int id = mVarNameHashMap.get(floatName).mId; 130 overrideFloat(id, value); 131 } 132 } 133 134 @Override clearNamedFloatOverride(String floatName)135 public void clearNamedFloatOverride(String floatName) { 136 if (mVarNameHashMap.get(floatName) != null) { 137 int id = mVarNameHashMap.get(floatName).mId; 138 clearFloatOverride(id); 139 } 140 mVarNameHashMap.put(floatName, null); 141 } 142 143 @Override setNamedLong(String name, long value)144 public void setNamedLong(String name, long value) { 145 VarName entry = mVarNameHashMap.get(name); 146 if (entry != null) { 147 int id = entry.mId; 148 LongConstant longConstant = (LongConstant) mRemoteComposeState.getObject(id); 149 longConstant.setValue(value); 150 } 151 } 152 153 @Override setNamedDataOverride(String dataName, Object value)154 public void setNamedDataOverride(String dataName, Object value) { 155 if (mVarNameHashMap.get(dataName) != null) { 156 int id = mVarNameHashMap.get(dataName).mId; 157 overrideData(id, value); 158 } 159 } 160 161 @Override clearNamedDataOverride(String dataName)162 public void clearNamedDataOverride(String dataName) { 163 if (mVarNameHashMap.get(dataName) != null) { 164 int id = mVarNameHashMap.get(dataName).mId; 165 clearDataOverride(id); 166 } 167 mVarNameHashMap.put(dataName, null); 168 } 169 170 /** 171 * Override a color to force it to be the color provided 172 * 173 * @param colorName name of color 174 * @param color 175 */ setNamedColorOverride(@onNull String colorName, int color)176 public void setNamedColorOverride(@NonNull String colorName, int color) { 177 int id = mVarNameHashMap.get(colorName).mId; 178 mRemoteComposeState.overrideColor(id, color); 179 } 180 181 @Override addCollection(int id, @NonNull ArrayAccess collection)182 public void addCollection(int id, @NonNull ArrayAccess collection) { 183 mRemoteComposeState.addCollection(id, collection); 184 } 185 186 @Override putDataMap(int id, @NonNull DataMap map)187 public void putDataMap(int id, @NonNull DataMap map) { 188 mRemoteComposeState.putDataMap(id, map); 189 } 190 191 @Override getDataMap(int id)192 public DataMap getDataMap(int id) { 193 return mRemoteComposeState.getDataMap(id); 194 } 195 196 @Override runAction(int id, @NonNull String metadata)197 public void runAction(int id, @NonNull String metadata) { 198 mDocument.performClick(this, id, metadata); 199 } 200 201 @Override runNamedAction(int id, Object value)202 public void runNamedAction(int id, Object value) { 203 String text = getText(id); 204 mDocument.runNamedAction(text, value); 205 } 206 207 /** 208 * Decode a byte array into an image and cache it using the given imageId 209 * 210 * @param encoding how the data is encoded 0 = png, 1 = raw, 2 = url 211 * @param type the type of the data 0 = RGBA 8888, 1 = 888, 2 = 8 gray 212 * @param width with of image to be loaded largest dimension is 32767 213 * @param height height of image to be loaded 214 * @param data a byte array containing the image information 215 * @oaram imageId the id of the image 216 */ 217 @Override loadBitmap( int imageId, short encoding, short type, int width, int height, @NonNull byte[] data)218 public void loadBitmap( 219 int imageId, short encoding, short type, int width, int height, @NonNull byte[] data) { 220 if (!mRemoteComposeState.containsId(imageId)) { 221 Bitmap image = null; 222 switch (encoding) { 223 case BitmapData.ENCODING_INLINE: 224 switch (type) { 225 case BitmapData.TYPE_PNG_8888: 226 image = BitmapFactory.decodeByteArray(data, 0, data.length); 227 break; 228 case BitmapData.TYPE_PNG_ALPHA_8: 229 image = decodePreferringAlpha8(data); 230 231 // If needed convert to ALPHA_8. 232 if (!image.getConfig().equals(Bitmap.Config.ALPHA_8)) { 233 Bitmap alpha8Bitmap = 234 Bitmap.createBitmap( 235 image.getWidth(), 236 image.getHeight(), 237 Bitmap.Config.ALPHA_8); 238 Canvas canvas = new Canvas(alpha8Bitmap); 239 Paint paint = new Paint(); 240 paint.setXfermode( 241 new android.graphics.PorterDuffXfermode( 242 android.graphics.PorterDuff.Mode.SRC)); 243 canvas.drawBitmap(image, 0, 0, paint); 244 image.recycle(); // Release resources 245 246 image = alpha8Bitmap; 247 } 248 break; 249 case BitmapData.TYPE_RAW8888: 250 image = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); 251 int[] idata = new int[data.length / 4]; 252 for (int i = 0; i < idata.length; i++) { 253 int p = i * 4; 254 idata[i] = 255 (data[p] << 24) 256 | (data[p + 1] << 16) 257 | (data[p + 2] << 8) 258 | data[p + 3]; 259 } 260 image.setPixels(idata, 0, width, 0, 0, width, height); 261 break; 262 case BitmapData.TYPE_RAW8: 263 image = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); 264 int[] bdata = new int[data.length / 4]; 265 for (int i = 0; i < bdata.length; i++) { 266 267 bdata[i] = 0x1010101 * data[i]; 268 } 269 image.setPixels(bdata, 0, width, 0, 0, width, height); 270 break; 271 } 272 break; 273 case BitmapData.ENCODING_FILE: 274 image = BitmapFactory.decodeFile(new String(data)); 275 break; 276 case BitmapData.ENCODING_URL: 277 try { 278 image = BitmapFactory.decodeStream(new URL(new String(data)).openStream()); 279 } catch (MalformedURLException e) { 280 throw new RuntimeException(e); 281 } catch (IOException e) { 282 throw new RuntimeException(e); 283 } 284 } 285 mRemoteComposeState.cacheData(imageId, image); 286 } 287 } 288 decodePreferringAlpha8(@onNull byte[] data)289 private Bitmap decodePreferringAlpha8(@NonNull byte[] data) { 290 BitmapFactory.Options options = new BitmapFactory.Options(); 291 options.inPreferredConfig = Bitmap.Config.ALPHA_8; 292 return BitmapFactory.decodeByteArray(data, 0, data.length, options); 293 } 294 295 @Override loadText(int id, @NonNull String text)296 public void loadText(int id, @NonNull String text) { 297 if (!mRemoteComposeState.containsId(id)) { 298 mRemoteComposeState.cacheData(id, text); 299 } else { 300 mRemoteComposeState.updateData(id, text); 301 } 302 } 303 overrideText(int id, String text)304 public void overrideText(int id, String text) { 305 mRemoteComposeState.overrideData(id, text); 306 } 307 overrideInt(int id, int value)308 public void overrideInt(int id, int value) { 309 mRemoteComposeState.overrideInteger(id, value); 310 } 311 overrideData(int id, Object value)312 public void overrideData(int id, Object value) { 313 mRemoteComposeState.overrideData(id, value); 314 } 315 clearDataOverride(int id)316 public void clearDataOverride(int id) { 317 mRemoteComposeState.clearDataOverride(id); 318 } 319 clearIntegerOverride(int id)320 public void clearIntegerOverride(int id) { 321 mRemoteComposeState.clearIntegerOverride(id); 322 } 323 clearFloatOverride(int id)324 public void clearFloatOverride(int id) { 325 mRemoteComposeState.clearFloatOverride(id); 326 } 327 328 @Override getText(int id)329 public String getText(int id) { 330 return (String) mRemoteComposeState.getFromId(id); 331 } 332 333 @Override loadFloat(int id, float value)334 public void loadFloat(int id, float value) { 335 mRemoteComposeState.updateFloat(id, value); 336 } 337 338 @Override overrideFloat(int id, float value)339 public void overrideFloat(int id, float value) { 340 mRemoteComposeState.overrideFloat(id, value); 341 } 342 343 @Override loadInteger(int id, int value)344 public void loadInteger(int id, int value) { 345 mRemoteComposeState.updateInteger(id, value); 346 } 347 overrideInteger(int id, int value)348 public void overrideInteger(int id, int value) { 349 mRemoteComposeState.overrideInteger(id, value); 350 } 351 overrideText(int id, int valueId)352 public void overrideText(int id, int valueId) { 353 String text = getText(valueId); 354 overrideText(id, text); 355 } 356 357 @Override loadColor(int id, int color)358 public void loadColor(int id, int color) { 359 mRemoteComposeState.updateColor(id, color); 360 } 361 362 @Override loadAnimatedFloat(int id, @NonNull FloatExpression animatedFloat)363 public void loadAnimatedFloat(int id, @NonNull FloatExpression animatedFloat) { 364 mRemoteComposeState.cacheData(id, animatedFloat); 365 } 366 367 @Override loadShader(int id, @NonNull ShaderData value)368 public void loadShader(int id, @NonNull ShaderData value) { 369 mRemoteComposeState.cacheData(id, value); 370 } 371 372 @Override getFloat(int id)373 public float getFloat(int id) { 374 return (float) mRemoteComposeState.getFloat(id); 375 } 376 377 @Override putObject(int id, @NonNull Object value)378 public void putObject(int id, @NonNull Object value) { 379 mRemoteComposeState.updateObject(id, value); 380 } 381 382 @Override getObject(int id)383 public Object getObject(int id) { 384 return mRemoteComposeState.getObject(id); 385 } 386 387 @Override getInteger(int id)388 public int getInteger(int id) { 389 return mRemoteComposeState.getInteger(id); 390 } 391 392 @Override getLong(int id)393 public long getLong(int id) { 394 return ((LongConstant) mRemoteComposeState.getObject(id)).getValue(); 395 } 396 397 @Override getColor(int id)398 public int getColor(int id) { 399 return mRemoteComposeState.getColor(id); 400 } 401 402 @Override listensTo(int id, @NonNull VariableSupport variableSupport)403 public void listensTo(int id, @NonNull VariableSupport variableSupport) { 404 mRemoteComposeState.listenToVar(id, variableSupport); 405 } 406 407 @Override updateOps()408 public int updateOps() { 409 return mRemoteComposeState.getOpsToUpdate(this); 410 } 411 412 @Override 413 @Nullable getShader(int id)414 public ShaderData getShader(int id) { 415 return (ShaderData) mRemoteComposeState.getFromId(id); 416 } 417 418 @Override addTouchListener(TouchListener touchExpression)419 public void addTouchListener(TouchListener touchExpression) { 420 mDocument.addTouchListener(touchExpression); 421 } 422 423 /////////////////////////////////////////////////////////////////////////////////////////////// 424 // Click handling 425 /////////////////////////////////////////////////////////////////////////////////////////////// 426 427 @Override addClickArea( int id, int contentDescriptionId, float left, float top, float right, float bottom, int metadataId)428 public void addClickArea( 429 int id, 430 int contentDescriptionId, 431 float left, 432 float top, 433 float right, 434 float bottom, 435 int metadataId) { 436 String contentDescription = (String) mRemoteComposeState.getFromId(contentDescriptionId); 437 String metadata = (String) mRemoteComposeState.getFromId(metadataId); 438 mDocument.addClickArea(id, contentDescription, left, top, right, bottom, metadata); 439 } 440 hapticEffect(int type)441 public void hapticEffect(int type) { 442 mDocument.haptic(type); 443 } 444 } 445