/* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and limitations under the * License. * */ package com.android.benchmark.ui.automation; import android.annotation.TargetApi; import android.app.Instrumentation; import android.os.Handler; import android.os.HandlerThread; import android.os.Looper; import android.os.Message; import android.view.FrameMetrics; import android.view.MotionEvent; import android.view.ViewTreeObserver; import android.view.Window; import com.android.benchmark.results.GlobalResultsStore; import com.android.benchmark.results.UiBenchmarkResult; import java.util.LinkedList; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; @TargetApi(24) public class Automator extends HandlerThread implements ViewTreeObserver.OnGlobalLayoutListener, CollectorThread.CollectorListener { public static final long FRAME_PERIOD_MILLIS = 16; private static final int PRE_READY_STATE_COUNT = 3; private static final String TAG = "Benchmark.Automator"; private final AtomicInteger mReadyState; private AutomateCallback mCallback; private Window mWindow; private AutomatorHandler mHandler; private CollectorThread mCollectorThread; private int mRunId; private int mIteration; private String mTestName; public static class AutomateCallback { public void onAutomate() {} public void onPostInteraction(List metrics) {} public void onPostAutomate() {} protected final void addInteraction(Interaction interaction) { if (mInteractions == null) { return; } mInteractions.add(interaction); } protected final void setInteractions(List interactions) { mInteractions = interactions; } private List mInteractions; } private static final class AutomatorHandler extends Handler { public static final int MSG_NEXT_INTERACTION = 0; public static final int MSG_ON_AUTOMATE = 1; public static final int MSG_ON_POST_INTERACTION = 2; private final String mTestName; private final int mRunId; private final int mIteration; private Instrumentation mInstrumentation; private volatile boolean mCancelled; private CollectorThread mCollectorThread; private AutomateCallback mCallback; private Window mWindow; LinkedList mInteractions; private UiBenchmarkResult mResults; AutomatorHandler(Looper looper, Window window, CollectorThread collectorThread, AutomateCallback callback, String testName, int runId, int iteration) { super(looper); mInstrumentation = new Instrumentation(); mCallback = callback; mWindow = window; mCollectorThread = collectorThread; mInteractions = new LinkedList<>(); mTestName = testName; mRunId = runId; mIteration = iteration; } @Override public void handleMessage(Message msg) { if (mCancelled) { return; } switch (msg.what) { case MSG_NEXT_INTERACTION: if (!nextInteraction()) { stopCollector(); writeResults(); mCallback.onPostAutomate(); } break; case MSG_ON_AUTOMATE: mCollectorThread.attachToWindow(mWindow); mCallback.setInteractions(mInteractions); mCallback.onAutomate(); postNextInteraction(); break; case MSG_ON_POST_INTERACTION: List collectedStats = (List)msg.obj; persistResults(collectedStats); mCallback.onPostInteraction(collectedStats); postNextInteraction(); break; } } public void cancel() { mCancelled = true; stopCollector(); } private void stopCollector() { mCollectorThread.quitCollector(); } private boolean nextInteraction() { Interaction interaction = mInteractions.poll(); if (interaction != null) { doInteraction(interaction); return true; } return false; } private void doInteraction(Interaction interaction) { if (mCancelled) { return; } mCollectorThread.markInteractionStart(); if (interaction.getType() == Interaction.Type.KEY_EVENT) { for (int code : interaction.getKeyCodes()) { if (!mCancelled) { mInstrumentation.sendKeyDownUpSync(code); } else { break; } } } else { for (MotionEvent event : interaction.getEvents()) { if (!mCancelled) { mInstrumentation.sendPointerSync(event); } else { break; } } } } protected void postNextInteraction() { final Message msg = obtainMessage(AutomatorHandler.MSG_NEXT_INTERACTION); sendMessage(msg); } private void persistResults(List stats) { if (stats.isEmpty()) { return; } if (mResults == null) { mResults = new UiBenchmarkResult(stats); } else { mResults.update(stats); } } private void writeResults() { GlobalResultsStore.getInstance(mWindow.getContext()) .storeRunResults(mTestName, mRunId, mIteration, mResults); } } private void initHandler() { mHandler = new AutomatorHandler(getLooper(), mWindow, mCollectorThread, mCallback, mTestName, mRunId, mIteration); mWindow = null; mCallback = null; mCollectorThread = null; mTestName = null; mRunId = 0; mIteration = 0; } @Override public final void onGlobalLayout() { if (!mCollectorThread.isAlive()) { mCollectorThread.start(); mWindow.getDecorView().getViewTreeObserver().removeOnGlobalLayoutListener(this); mReadyState.decrementAndGet(); } } @Override public void onCollectorThreadReady() { if (mReadyState.decrementAndGet() == 0) { initHandler(); postOnAutomate(); } } @Override protected void onLooperPrepared() { if (mReadyState.decrementAndGet() == 0) { initHandler(); postOnAutomate(); } } @Override public void onPostInteraction(List stats) { Message m = mHandler.obtainMessage(AutomatorHandler.MSG_ON_POST_INTERACTION, stats); mHandler.sendMessage(m); } protected void postOnAutomate() { final Message msg = mHandler.obtainMessage(AutomatorHandler.MSG_ON_AUTOMATE); mHandler.sendMessage(msg); } public void cancel() { mHandler.removeMessages(AutomatorHandler.MSG_NEXT_INTERACTION); mHandler.cancel(); mHandler = null; } public Automator(String testName, int runId, int iteration, Window window, AutomateCallback callback) { super("AutomatorThread"); mTestName = testName; mRunId = runId; mIteration = iteration; mCallback = callback; mWindow = window; mWindow.getDecorView().getViewTreeObserver().addOnGlobalLayoutListener(this); mCollectorThread = new CollectorThread(this); mReadyState = new AtomicInteger(PRE_READY_STATE_COUNT); } }