1/*
2 * Copyright (C) 2016 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 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and limitations under the
13 * License.
14 *
15 */
16
17package com.android.benchmark.ui.automation;
18
19import android.annotation.TargetApi;
20import android.app.Instrumentation;
21import android.os.Handler;
22import android.os.HandlerThread;
23import android.os.Looper;
24import android.os.Message;
25import android.view.FrameMetrics;
26import android.view.MotionEvent;
27import android.view.ViewTreeObserver;
28import android.view.Window;
29
30import com.android.benchmark.results.GlobalResultsStore;
31import com.android.benchmark.results.UiBenchmarkResult;
32
33import java.util.LinkedList;
34import java.util.List;
35import java.util.concurrent.atomic.AtomicInteger;
36
37@TargetApi(24)
38public class Automator extends HandlerThread
39        implements ViewTreeObserver.OnGlobalLayoutListener, CollectorThread.CollectorListener {
40    public static final long FRAME_PERIOD_MILLIS = 16;
41
42    private static final int PRE_READY_STATE_COUNT = 3;
43    private static final String TAG = "Benchmark.Automator";
44    private final AtomicInteger mReadyState;
45
46    private AutomateCallback mCallback;
47    private Window mWindow;
48    private AutomatorHandler mHandler;
49    private CollectorThread mCollectorThread;
50    private int mRunId;
51    private int mIteration;
52    private String mTestName;
53
54    public static class AutomateCallback {
55        public void onAutomate() {}
56        public void onPostInteraction(List<FrameMetrics> metrics) {}
57        public void onPostAutomate() {}
58
59        protected final void addInteraction(Interaction interaction) {
60            if (mInteractions == null) {
61                return;
62            }
63
64            mInteractions.add(interaction);
65        }
66
67        protected final void setInteractions(List<Interaction> interactions) {
68            mInteractions = interactions;
69        }
70
71        private List<Interaction> mInteractions;
72    }
73
74    private static final class AutomatorHandler extends Handler {
75        public static final int MSG_NEXT_INTERACTION = 0;
76        public static final int MSG_ON_AUTOMATE = 1;
77        public static final int MSG_ON_POST_INTERACTION = 2;
78        private final String mTestName;
79        private final int mRunId;
80        private final int mIteration;
81
82        private Instrumentation mInstrumentation;
83        private volatile boolean mCancelled;
84        private CollectorThread mCollectorThread;
85        private AutomateCallback mCallback;
86        private Window mWindow;
87
88        LinkedList<Interaction> mInteractions;
89        private UiBenchmarkResult mResults;
90
91        AutomatorHandler(Looper looper, Window window, CollectorThread collectorThread,
92                         AutomateCallback callback, String testName, int runId, int iteration) {
93            super(looper);
94
95            mInstrumentation = new Instrumentation();
96
97            mCallback = callback;
98            mWindow = window;
99            mCollectorThread = collectorThread;
100            mInteractions = new LinkedList<>();
101            mTestName = testName;
102            mRunId = runId;
103            mIteration = iteration;
104        }
105
106        @Override
107        public void handleMessage(Message msg) {
108            if (mCancelled) {
109                return;
110            }
111
112            switch (msg.what) {
113                case MSG_NEXT_INTERACTION:
114                    if (!nextInteraction()) {
115                        stopCollector();
116                        writeResults();
117                        mCallback.onPostAutomate();
118                    }
119                    break;
120                case MSG_ON_AUTOMATE:
121                    mCollectorThread.attachToWindow(mWindow);
122                    mCallback.setInteractions(mInteractions);
123                    mCallback.onAutomate();
124                    postNextInteraction();
125                    break;
126                case MSG_ON_POST_INTERACTION:
127                    List<FrameMetrics> collectedStats = (List<FrameMetrics>)msg.obj;
128                    persistResults(collectedStats);
129                    mCallback.onPostInteraction(collectedStats);
130                    postNextInteraction();
131                    break;
132            }
133        }
134
135        public void cancel() {
136            mCancelled = true;
137            stopCollector();
138        }
139
140        private void stopCollector() {
141            mCollectorThread.quitCollector();
142        }
143
144        private boolean nextInteraction() {
145
146            Interaction interaction = mInteractions.poll();
147            if (interaction != null) {
148                doInteraction(interaction);
149                return true;
150            }
151            return false;
152        }
153
154        private void doInteraction(Interaction interaction) {
155            if (mCancelled) {
156                return;
157            }
158
159            mCollectorThread.markInteractionStart();
160
161            if (interaction.getType() == Interaction.Type.KEY_EVENT) {
162                for (int code : interaction.getKeyCodes()) {
163                    if (!mCancelled) {
164                        mInstrumentation.sendKeyDownUpSync(code);
165                    } else {
166                        break;
167                    }
168                }
169            } else {
170                for (MotionEvent event : interaction.getEvents()) {
171                    if (!mCancelled) {
172                        mInstrumentation.sendPointerSync(event);
173                    } else {
174                        break;
175                    }
176                }
177            }
178        }
179
180        protected void postNextInteraction() {
181            final Message msg = obtainMessage(AutomatorHandler.MSG_NEXT_INTERACTION);
182            sendMessage(msg);
183        }
184
185        private void persistResults(List<FrameMetrics> stats) {
186            if (stats.isEmpty()) {
187                return;
188            }
189
190            if (mResults == null) {
191                mResults = new UiBenchmarkResult(stats);
192            } else {
193                mResults.update(stats);
194            }
195        }
196
197        private void writeResults() {
198            GlobalResultsStore.getInstance(mWindow.getContext())
199                    .storeRunResults(mTestName, mRunId, mIteration, mResults);
200        }
201    }
202
203    private void initHandler() {
204        mHandler = new AutomatorHandler(getLooper(), mWindow, mCollectorThread, mCallback,
205                mTestName, mRunId, mIteration);
206        mWindow = null;
207        mCallback = null;
208        mCollectorThread = null;
209        mTestName = null;
210        mRunId = 0;
211        mIteration = 0;
212    }
213
214    @Override
215    public final void onGlobalLayout() {
216        if (!mCollectorThread.isAlive()) {
217            mCollectorThread.start();
218            mWindow.getDecorView().getViewTreeObserver().removeOnGlobalLayoutListener(this);
219            mReadyState.decrementAndGet();
220        }
221    }
222
223    @Override
224    public void onCollectorThreadReady() {
225        if (mReadyState.decrementAndGet() == 0) {
226            initHandler();
227            postOnAutomate();
228        }
229    }
230
231    @Override
232    protected void onLooperPrepared() {
233        if (mReadyState.decrementAndGet() == 0) {
234            initHandler();
235            postOnAutomate();
236        }
237    }
238
239    @Override
240    public void onPostInteraction(List<FrameMetrics> stats) {
241        Message m = mHandler.obtainMessage(AutomatorHandler.MSG_ON_POST_INTERACTION, stats);
242        mHandler.sendMessage(m);
243    }
244
245    protected void postOnAutomate() {
246        final Message msg = mHandler.obtainMessage(AutomatorHandler.MSG_ON_AUTOMATE);
247        mHandler.sendMessage(msg);
248    }
249
250    public void cancel() {
251        mHandler.removeMessages(AutomatorHandler.MSG_NEXT_INTERACTION);
252        mHandler.cancel();
253        mHandler = null;
254    }
255
256    public Automator(String testName, int runId, int iteration,
257                     Window window, AutomateCallback callback) {
258        super("AutomatorThread");
259
260        mTestName = testName;
261        mRunId = runId;
262        mIteration = iteration;
263        mCallback = callback;
264        mWindow = window;
265        mWindow.getDecorView().getViewTreeObserver().addOnGlobalLayoutListener(this);
266        mCollectorThread = new CollectorThread(this);
267        mReadyState = new AtomicInteger(PRE_READY_STATE_COUNT);
268    }
269}
270