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.content.Context; 19 import android.graphics.Canvas; 20 import android.graphics.Color; 21 import android.graphics.Point; 22 import android.util.AttributeSet; 23 import android.view.MotionEvent; 24 import android.view.View; 25 import android.widget.FrameLayout; 26 27 import com.android.internal.widget.remotecompose.core.CoreDocument; 28 import com.android.internal.widget.remotecompose.core.operations.RootContentBehavior; 29 import com.android.internal.widget.remotecompose.core.operations.Theme; 30 import com.android.internal.widget.remotecompose.player.RemoteComposeDocument; 31 32 import java.util.Set; 33 34 /** 35 * Internal view handling the actual painting / interactions 36 */ 37 public class RemoteComposeCanvas extends FrameLayout implements View.OnAttachStateChangeListener { 38 39 static final boolean USE_VIEW_AREA_CLICK = true; // Use views to represent click areas 40 RemoteComposeDocument mDocument = null; 41 int mTheme = Theme.LIGHT; 42 boolean mInActionDown = false; 43 boolean mDebug = false; 44 Point mActionDownPoint = new Point(0, 0); 45 RemoteComposeCanvas(Context context)46 public RemoteComposeCanvas(Context context) { 47 super(context); 48 if (USE_VIEW_AREA_CLICK) { 49 addOnAttachStateChangeListener(this); 50 } 51 } 52 RemoteComposeCanvas(Context context, AttributeSet attrs)53 public RemoteComposeCanvas(Context context, AttributeSet attrs) { 54 super(context, attrs); 55 if (USE_VIEW_AREA_CLICK) { 56 addOnAttachStateChangeListener(this); 57 } 58 } 59 RemoteComposeCanvas(Context context, AttributeSet attrs, int defStyleAttr)60 public RemoteComposeCanvas(Context context, AttributeSet attrs, int defStyleAttr) { 61 super(context, attrs, defStyleAttr); 62 setBackgroundColor(Color.WHITE); 63 if (USE_VIEW_AREA_CLICK) { 64 addOnAttachStateChangeListener(this); 65 } 66 } 67 setDebug(boolean value)68 public void setDebug(boolean value) { 69 if (mDebug != value) { 70 mDebug = value; 71 if (USE_VIEW_AREA_CLICK) { 72 for (int i = 0; i < getChildCount(); i++) { 73 View child = getChildAt(i); 74 if (child instanceof ClickAreaView) { 75 ((ClickAreaView) child).setDebug(mDebug); 76 } 77 } 78 } 79 invalidate(); 80 } 81 } 82 setDocument(RemoteComposeDocument value)83 public void setDocument(RemoteComposeDocument value) { 84 mDocument = value; 85 mDocument.initializeContext(mARContext); 86 setContentDescription(mDocument.getDocument().getContentDescription()); 87 requestLayout(); 88 invalidate(); 89 } 90 91 AndroidRemoteContext mARContext = new AndroidRemoteContext(); 92 93 @Override onViewAttachedToWindow(View view)94 public void onViewAttachedToWindow(View view) { 95 if (mDocument == null) { 96 return; 97 } 98 Set<CoreDocument.ClickAreaRepresentation> clickAreas = mDocument 99 .getDocument().getClickAreas(); 100 removeAllViews(); 101 for (CoreDocument.ClickAreaRepresentation area : clickAreas) { 102 ClickAreaView viewArea = new ClickAreaView(getContext(), mDebug, 103 area.getId(), area.getContentDescription(), 104 area.getMetadata()); 105 int w = (int) area.width(); 106 int h = (int) area.height(); 107 FrameLayout.LayoutParams param = new FrameLayout.LayoutParams(w, h); 108 param.width = w; 109 param.height = h; 110 param.leftMargin = (int) area.getLeft(); 111 param.topMargin = (int) area.getTop(); 112 viewArea.setOnClickListener(view1 113 -> mDocument.getDocument().performClick(area.getId())); 114 addView(viewArea, param); 115 } 116 } 117 118 @Override onViewDetachedFromWindow(View view)119 public void onViewDetachedFromWindow(View view) { 120 removeAllViews(); 121 } 122 123 public interface ClickCallbacks { click(int id, String metadata)124 void click(int id, String metadata); 125 } 126 addClickListener(ClickCallbacks callback)127 public void addClickListener(ClickCallbacks callback) { 128 if (mDocument == null) { 129 return; 130 } 131 mDocument.getDocument().addClickListener((id, metadata) -> callback.click(id, metadata)); 132 } 133 getTheme()134 public int getTheme() { 135 return mTheme; 136 } 137 setTheme(int theme)138 public void setTheme(int theme) { 139 this.mTheme = theme; 140 } 141 onTouchEvent(MotionEvent event)142 public boolean onTouchEvent(MotionEvent event) { 143 if (USE_VIEW_AREA_CLICK) { 144 return super.onTouchEvent(event); 145 } 146 switch (event.getActionMasked()) { 147 case MotionEvent.ACTION_DOWN: { 148 mActionDownPoint.x = (int) event.getX(); 149 mActionDownPoint.y = (int) event.getY(); 150 mInActionDown = true; 151 return true; 152 } 153 case MotionEvent.ACTION_CANCEL: { 154 mInActionDown = false; 155 return true; 156 } 157 case MotionEvent.ACTION_UP: { 158 mInActionDown = false; 159 performClick(); 160 return true; 161 } 162 case MotionEvent.ACTION_MOVE: { 163 } 164 } 165 return false; 166 } 167 168 @Override performClick()169 public boolean performClick() { 170 if (USE_VIEW_AREA_CLICK) { 171 return super.performClick(); 172 } 173 mDocument.getDocument().onClick((float) mActionDownPoint.x, (float) mActionDownPoint.y); 174 super.performClick(); 175 return true; 176 } 177 measureDimension(int measureSpec, int intrinsicSize)178 public int measureDimension(int measureSpec, int intrinsicSize) { 179 int result = intrinsicSize; 180 int mode = MeasureSpec.getMode(measureSpec); 181 int size = MeasureSpec.getSize(measureSpec); 182 switch (mode) { 183 case MeasureSpec.EXACTLY: 184 result = size; 185 break; 186 case MeasureSpec.AT_MOST: 187 result = Integer.min(size, intrinsicSize); 188 break; 189 case MeasureSpec.UNSPECIFIED: 190 result = intrinsicSize; 191 } 192 return result; 193 } 194 195 private static final float[] sScaleOutput = new float[2]; 196 197 @Override onMeasure(int widthMeasureSpec, int heightMeasureSpec)198 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 199 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 200 if (mDocument == null) { 201 return; 202 } 203 int w = measureDimension(widthMeasureSpec, mDocument.getWidth()); 204 int h = measureDimension(heightMeasureSpec, mDocument.getHeight()); 205 206 if (!USE_VIEW_AREA_CLICK) { 207 if (mDocument.getDocument().getContentSizing() == RootContentBehavior.SIZING_SCALE) { 208 mDocument.getDocument().computeScale(w, h, sScaleOutput); 209 w = (int) (mDocument.getWidth() * sScaleOutput[0]); 210 h = (int) (mDocument.getHeight() * sScaleOutput[1]); 211 } 212 } 213 setMeasuredDimension(w, h); 214 } 215 216 private int mCount; 217 private long mTime = System.nanoTime(); 218 219 @Override onDraw(Canvas canvas)220 protected void onDraw(Canvas canvas) { 221 super.onDraw(canvas); 222 if (mDocument == null) { 223 return; 224 } 225 mARContext.setDebug(mDebug); 226 mARContext.useCanvas(canvas); 227 mARContext.mWidth = getWidth(); 228 mARContext.mHeight = getHeight(); 229 mDocument.paint(mARContext, mTheme); 230 if (mDebug) { 231 mCount++; 232 if (System.nanoTime() - mTime > 1000000000L) { 233 System.out.println(" count " + mCount + " fps"); 234 mCount = 0; 235 mTime = System.nanoTime(); 236 } 237 } 238 if (mDocument.needsRepaint() > 0) { 239 invalidate(); 240 } 241 } 242 243 } 244 245