/* MonkeyTalk - a cross-platform functional testing tool
Copyright (C) 2012 Gorilla Logic, Inc.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
package com.gorillalogic.fonemonkey.automators;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.WeakHashMap;
import android.app.Activity;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.OrientationEventListener;
import android.view.View;
import android.view.Window;
import android.view.Window.Callback;
import android.view.WindowManager;
import android.view.WindowManager.LayoutParams;
import android.view.accessibility.AccessibilityEvent;
import com.gorillalogic.fonemonkey.ActivityManager;
import com.gorillalogic.fonemonkey.Log;
import com.gorillalogic.monkeytalk.automators.AutomatorConstants;
/**
*
* Provides recording for Window.Callback stuff, including Menu handling, and Back button handling.
*/
public class WindowAutomator extends AutomatorBase implements Callback {
private static WeakHashMap<Window, Object> windows = new WeakHashMap<Window, Object>();
static {
Log.log("Initializing WindowAutomator");
}
private class MonkeyWindowHandler implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// Log.log("invoking " + method.getName());
Object retVal = null;
try {
retVal = method.invoke(WindowAutomator.this, args);
} catch (AbstractMethodError e) {
// Additional methods were added to Window.Callback and these methods are sometimes
// called by Android Monkey Exerciser.
// It's not clear that in real life these methods would actually ever be called, so
// for now we just eat the error.
Log.log("Window.Callback method called that is only supported in later version of Android SDK than one used for building MonkeyTalk");
}
if (listener == null) {
return retVal;
}
return method.invoke(listener, args);
}
}
private Window.Callback listener = null;
static OrientationEventListener oListener;
public WindowAutomator(Window w) {
w.addFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);
w.addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
w.addFlags(WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON);
w.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
setComponent(w);
try {
Field f = Window.class.getDeclaredField("mCallback");
f.setAccessible(true);
listener = (Callback) f.get(getComponent());
if (listener instanceof Activity) {
Window.Callback proxy = (Callback) Proxy.newProxyInstance(this.getClass()
.getClassLoader(), new Class<?>[] { Window.Callback.class },
new MonkeyWindowHandler());
w.setCallback(proxy);
}
} catch (Exception e) {
throw new IllegalStateException("Unable to access mCallback field on window");
}
// if (isTopLevelWindow()) {
// //Doesn't work on emulator! Might work on physical device.
// oListener = new OrientationEventListener(w.getContext(),
// SensorManager.SENSOR_DELAY_NORMAL) {
//
// @Override
// public void onOrientationChanged(int orientation) {
// Log.log("Orientation changed: " + orientation);
//
// }
// };
// oListener.enable();
// }
}
public Window getWindow() {
return (Window) getComponent();
}
private boolean isTopLevelWindow() {
return getWindow().getContainer() == null;
}
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
if (event.getAction() == KeyEvent.ACTION_UP && event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
AutomationManager.record(AutomatorConstants.ACTION_BACK, null);
} else if (event.getAction() == KeyEvent.ACTION_UP
&& event.getKeyCode() == KeyEvent.KEYCODE_MENU) {
if (isTopLevelWindow()) {
AutomationManager.record(AutomatorConstants.ACTION_MENU, null);
}
}
if (listener == null) {
return getWindow().superDispatchKeyEvent(event);
}
return false;
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
if (listener == null) {
return getWindow().superDispatchTouchEvent(event);
}
return false;
}
@Override
public boolean dispatchTrackballEvent(MotionEvent event) {
if (listener == null) {
return getWindow().superDispatchTrackballEvent(event);
}
return false;
}
@Override
public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
// TODO Auto-generated method stub
return false;
}
@Override
public View onCreatePanelView(int featureId) {
// TODO Auto-generated method stub
return null;
}
@Override
public boolean onCreatePanelMenu(int featureId, Menu menu) {
ActivityManager.setCurrentMenu(menu);
return false;
}
@Override
public boolean onPreparePanel(int featureId, View view, Menu menu) {
// TODO Auto-generated method stub
return false;
}
@Override
public boolean onMenuOpened(int featureId, Menu menu) {
ActivityManager.setCurrentMenu(menu);
return false;
}
@Override
public boolean onMenuItemSelected(int featureId, MenuItem item) {
AutomationManager.record(AutomatorConstants.ACTION_SELECT, item, new String[0]);
return false;
}
@Override
public void onWindowAttributesChanged(LayoutParams attrs) {
}
@Override
public void onContentChanged() {
}
@Override
public void onWindowFocusChanged(boolean hasFocus) {
}
@Override
public void onAttachedToWindow() {
}
@Override
public void onDetachedFromWindow() {
}
@Override
public void onPanelClosed(int featureId, Menu menu) {
}
// Presumably would be where we'd handle Search button recording.
@Override
public boolean onSearchRequested() {
return false;
}
@Override
public String getComponentType() {
return null;
}
/**
* @param window
*/
public static void automate(Window window) {
if (windows.containsKey(window)) {
return;
}
windows.put(window, (Object) null);
// Wrap with an event handler
new WindowAutomator(window);
}
}