1 /*
2  * Copyright (C) 2017 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 
17 package com.android.dialer.simulator.impl;
18 
19 import android.graphics.Canvas;
20 import android.graphics.Color;
21 import android.graphics.Paint;
22 import android.os.Handler;
23 import android.os.HandlerThread;
24 import android.support.annotation.NonNull;
25 import android.support.annotation.VisibleForTesting;
26 import android.support.annotation.WorkerThread;
27 import android.view.Surface;
28 import com.android.dialer.common.Assert;
29 import com.android.dialer.common.LogUtil;
30 
31 /**
32  * Used by the video provider to draw the remote party's video. The in-call UI is responsible for
33  * setting the view to draw to. Since the simulator doesn't have a remote party we simply draw a
34  * green screen with a ball bouncing around.
35  */
36 final class SimulatorRemoteVideo {
37   @NonNull private final RenderThread thread;
38   private boolean isStopped;
39 
SimulatorRemoteVideo(@onNull Surface surface)40   SimulatorRemoteVideo(@NonNull Surface surface) {
41     thread = new RenderThread(new Renderer(surface));
42   }
43 
startVideo()44   void startVideo() {
45     LogUtil.enterBlock("SimulatorRemoteVideo.startVideo");
46     Assert.checkState(!isStopped);
47     thread.start();
48   }
49 
stopVideo()50   void stopVideo() {
51     LogUtil.enterBlock("SimulatorRemoteVideo.stopVideo");
52     isStopped = true;
53     thread.quitSafely();
54   }
55 
56   @VisibleForTesting
getRenderer()57   Runnable getRenderer() {
58     return thread.getRenderer();
59   }
60 
61   private static class Renderer implements Runnable {
62     private static final int FRAME_DELAY_MILLIS = 33;
63     private static final float CIRCLE_STEP = 16.0f;
64 
65     @NonNull private final Surface surface;
66     private float circleX;
67     private float circleY;
68     private float radius;
69     private double angle;
70 
Renderer(@onNull Surface surface)71     Renderer(@NonNull Surface surface) {
72       this.surface = Assert.isNotNull(surface);
73     }
74 
75     @Override
run()76     public void run() {
77       drawFrame();
78       schedule();
79     }
80 
81     @WorkerThread
schedule()82     void schedule() {
83       Assert.isWorkerThread();
84       new Handler().postDelayed(this, FRAME_DELAY_MILLIS);
85     }
86 
87     @WorkerThread
drawFrame()88     private void drawFrame() {
89       Assert.isWorkerThread();
90       Canvas canvas;
91       try {
92         canvas = surface.lockCanvas(null /* dirtyRect */);
93       } catch (IllegalArgumentException e) {
94         // This can happen when the video fragment tears down.
95         LogUtil.e("SimulatorRemoteVideo.RenderThread.drawFrame", "unable to lock canvas", e);
96         return;
97       }
98 
99       LogUtil.i(
100           "SimulatorRemoteVideo.RenderThread.drawFrame",
101           "size; %d x %d",
102           canvas.getWidth(),
103           canvas.getHeight());
104       canvas.drawColor(Color.GREEN);
105       moveCircle(canvas);
106       drawCircle(canvas);
107       surface.unlockCanvasAndPost(canvas);
108     }
109 
110     @WorkerThread
moveCircle(Canvas canvas)111     private void moveCircle(Canvas canvas) {
112       Assert.isWorkerThread();
113       int width = canvas.getWidth();
114       int height = canvas.getHeight();
115       if (circleX == 0 && circleY == 0) {
116         circleX = width / 2.0f;
117         circleY = height / 2.0f;
118         angle = Math.PI / 4.0;
119         radius = Math.min(canvas.getWidth(), canvas.getHeight()) * 0.15f;
120       } else {
121         circleX += (float) Math.cos(angle) * CIRCLE_STEP;
122         circleY += (float) Math.sin(angle) * CIRCLE_STEP;
123         // Bounce the circle off the edge.
124         if (circleX + radius >= width
125             || circleX - radius <= 0
126             || circleY + radius >= height
127             || circleY - radius <= 0) {
128           angle += Math.PI / 2.0;
129         }
130       }
131     }
132 
133     @WorkerThread
drawCircle(Canvas canvas)134     private void drawCircle(Canvas canvas) {
135       Assert.isWorkerThread();
136       Paint paint = new Paint();
137       paint.setColor(Color.MAGENTA);
138       paint.setStyle(Paint.Style.FILL);
139       canvas.drawCircle(circleX, circleY, radius, paint);
140     }
141   }
142 
143   private static class RenderThread extends HandlerThread {
144     @NonNull private final Renderer renderer;
145 
RenderThread(@onNull Renderer renderer)146     RenderThread(@NonNull Renderer renderer) {
147       super("SimulatorRemoteVideo");
148       this.renderer = Assert.isNotNull(renderer);
149     }
150 
151     @Override
152     @WorkerThread
onLooperPrepared()153     protected void onLooperPrepared() {
154       Assert.isWorkerThread();
155       renderer.schedule();
156     }
157 
158     @VisibleForTesting
getRenderer()159     Runnable getRenderer() {
160       return renderer;
161     }
162   }
163 }
164