diff --git a/AndroidAnnotations/androidannotations-api/src/main/java/org/androidannotations/annotations/HierarchyViewerSupport.java b/AndroidAnnotations/androidannotations-api/src/main/java/org/androidannotations/annotations/HierarchyViewerSupport.java
new file mode 100644
index 0000000000..d001712d79
--- /dev/null
+++ b/AndroidAnnotations/androidannotations-api/src/main/java/org/androidannotations/annotations/HierarchyViewerSupport.java
@@ -0,0 +1,17 @@
+package org.androidannotations.annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Use this annotation to enable the use of HierarchyViewer inside the
+ * application.
+ *
+ * @author Thomas Fondrillon
+ */
+@Retention(RetentionPolicy.CLASS)
+@Target(ElementType.TYPE)
+public @interface HierarchyViewerSupport {
+}
diff --git a/AndroidAnnotations/androidannotations/src/main/java/org/androidannotations/AndroidAnnotationProcessor.java b/AndroidAnnotations/androidannotations/src/main/java/org/androidannotations/AndroidAnnotationProcessor.java
index ee2e3eee84..c875bc750d 100644
--- a/AndroidAnnotations/androidannotations/src/main/java/org/androidannotations/AndroidAnnotationProcessor.java
+++ b/AndroidAnnotations/androidannotations/src/main/java/org/androidannotations/AndroidAnnotationProcessor.java
@@ -55,6 +55,7 @@
import org.androidannotations.annotations.FragmentByTag;
import org.androidannotations.annotations.FromHtml;
import org.androidannotations.annotations.Fullscreen;
+import org.androidannotations.annotations.HierarchyViewerSupport;
import org.androidannotations.annotations.HttpsClient;
import org.androidannotations.annotations.InstanceState;
import org.androidannotations.annotations.ItemClick;
@@ -125,6 +126,7 @@
import org.androidannotations.processing.BeanProcessor;
import org.androidannotations.processing.BeforeTextChangeProcessor;
import org.androidannotations.processing.ClickProcessor;
+import org.androidannotations.processing.HierarchyViewerSupportProcessor;
import org.androidannotations.processing.EActivityProcessor;
import org.androidannotations.processing.EApplicationProcessor;
import org.androidannotations.processing.EBeanProcessor;
@@ -189,6 +191,7 @@
import org.androidannotations.validation.BeanValidator;
import org.androidannotations.validation.BeforeTextChangeValidator;
import org.androidannotations.validation.ClickValidator;
+import org.androidannotations.validation.HierarchyViewerSupportValidator;
import org.androidannotations.validation.EActivityValidator;
import org.androidannotations.validation.EApplicationValidator;
import org.androidannotations.validation.EBeanValidator;
@@ -316,7 +319,8 @@
OrmLiteDao.class, //
HttpsClient.class, //
FragmentArg.class, //
- OnActivityResult.class //
+ OnActivityResult.class, //
+ HierarchyViewerSupport.class //
})
@SupportedSourceVersion(SourceVersion.RELEASE_6)
public class AndroidAnnotationProcessor extends AnnotatedAbstractProcessor {
@@ -497,6 +501,7 @@ private ModelValidator buildModelValidator(IRClass rClass, AndroidSystemServices
modelValidator.register(new OrmLiteDaoValidator(processingEnv, rClass));
modelValidator.register(new HttpsClientValidator(processingEnv, rClass));
modelValidator.register(new OnActivityResultValidator(processingEnv, rClass));
+ modelValidator.register(new HierarchyViewerSupportValidator(processingEnv, androidManifest));
return modelValidator;
}
@@ -587,6 +592,7 @@ private ModelProcessor buildModelProcessor(IRClass rClass, AndroidSystemServices
modelProcessor.register(new InstanceStateProcessor(processingEnv));
modelProcessor.register(new HttpsClientProcessor(rClass));
modelProcessor.register(new OnActivityResultProcessor(processingEnv, rClass));
+ modelProcessor.register(new HierarchyViewerSupportProcessor());
return modelProcessor;
}
diff --git a/AndroidAnnotations/androidannotations/src/main/java/org/androidannotations/api/ViewServer.java b/AndroidAnnotations/androidannotations/src/main/java/org/androidannotations/api/ViewServer.java
new file mode 100644
index 0000000000..07d123bb20
--- /dev/null
+++ b/AndroidAnnotations/androidannotations/src/main/java/org/androidannotations/api/ViewServer.java
@@ -0,0 +1,864 @@
+/*
+ * Copyright (C) 2011 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 org.androidannotations.api;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.lang.reflect.Method;
+import java.net.InetAddress;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map.Entry;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.os.Build;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewDebug;
+
+/**
+ *
+ * This class can be used to enable the use of HierarchyViewer inside an
+ * application. HierarchyViewer is an Android SDK tool that can be used to
+ * inspect and debug the user interface of running applications. For security
+ * reasons, HierarchyViewer does not work on production builds (for instance
+ * phones bought in store.) By using this class, you can make HierarchyViewer
+ * work on any device. You must be very careful however to only enable
+ * HierarchyViewer when debugging your application.
+ *
+ *
+ *
+ * To use this view server, your application must require the INTERNET
+ * permission.
+ *
+ *
+ *
+ * The recommended way to use this API is to register activities when they are
+ * created, and to unregister them when they get destroyed:
+ *
+ *
+ *
+ * public class MyActivity extends Activity {
+ * public void onCreate(Bundle savedInstanceState) {
+ * super.onCreate(savedInstanceState);
+ * // Set content view, etc.
+ * ViewServer.get(this).addWindow(this);
+ * }
+ *
+ * public void onDestroy() {
+ * super.onDestroy();
+ * ViewServer.get(this).removeWindow(this);
+ * }
+ *
+ * public void onResume() {
+ * super.onResume();
+ * ViewServer.get(this).setFocusedWindow(this);
+ * }
+ * }
+ *
+ *
+ *
+ * In a similar fashion, you can use this API with an InputMethodService:
+ *
+ *
+ *
+ * public class MyInputMethodService extends InputMethodService {
+ * public void onCreate() {
+ * super.onCreate();
+ * View decorView = getWindow().getWindow().getDecorView();
+ * String name = "MyInputMethodService";
+ * ViewServer.get(this).addWindow(decorView, name);
+ * }
+ *
+ * public void onDestroy() {
+ * super.onDestroy();
+ * View decorView = getWindow().getWindow().getDecorView();
+ * ViewServer.get(this).removeWindow(decorView);
+ * }
+ *
+ * public void onStartInput(EditorInfo attribute, boolean restarting) {
+ * super.onStartInput(attribute, restarting);
+ * View decorView = getWindow().getWindow().getDecorView();
+ * ViewServer.get(this).setFocusedWindow(decorView);
+ * }
+ * }
+ *
+ */
+public class ViewServer implements Runnable {
+ /**
+ * The default port used to start view servers.
+ */
+ private static final int VIEW_SERVER_DEFAULT_PORT = 4939;
+ private static final int VIEW_SERVER_MAX_CONNECTIONS = 10;
+ private static final String BUILD_TYPE_USER = "user";
+
+ // Debug facility
+ private static final String LOG_TAG = "ViewServer";
+
+ private static final String VALUE_PROTOCOL_VERSION = "4";
+ private static final String VALUE_SERVER_VERSION = "4";
+
+ // Protocol commands
+ // Returns the protocol version
+ private static final String COMMAND_PROTOCOL_VERSION = "PROTOCOL";
+ // Returns the server version
+ private static final String COMMAND_SERVER_VERSION = "SERVER";
+ // Lists all of the available windows in the system
+ private static final String COMMAND_WINDOW_MANAGER_LIST = "LIST";
+ // Keeps a connection open and notifies when the list of windows changes
+ private static final String COMMAND_WINDOW_MANAGER_AUTOLIST = "AUTOLIST";
+ // Returns the focused window
+ private static final String COMMAND_WINDOW_MANAGER_GET_FOCUS = "GET_FOCUS";
+
+ private ServerSocket mServer;
+ private final int mPort;
+
+ private Thread mThread;
+ private ExecutorService mThreadPool;
+
+ private final List mListeners = new CopyOnWriteArrayList();
+
+ private final HashMap mWindows = new HashMap();
+ private final ReentrantReadWriteLock mWindowsLock = new ReentrantReadWriteLock();
+
+ private View mFocusedWindow;
+ private final ReentrantReadWriteLock mFocusLock = new ReentrantReadWriteLock();
+
+ private static ViewServer sServer;
+
+ /**
+ * Returns a unique instance of the ViewServer. This method should only be
+ * called from the main thread of your application. The server will have the
+ * same lifetime as your process.
+ *
+ * If your application does not have the android:debuggable
+ * flag set in its manifest, the server returned by this method will be a
+ * dummy object that does not do anything. This allows you to use the same
+ * code in debug and release versions of your application.
+ *
+ * @param context
+ * A Context used to check whether the application is debuggable,
+ * this can be the application context
+ */
+ public static ViewServer get(Context context) {
+ ApplicationInfo info = context.getApplicationInfo();
+ if (BUILD_TYPE_USER.equals(Build.TYPE) && (info.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0) {
+ if (sServer == null) {
+ sServer = new ViewServer(ViewServer.VIEW_SERVER_DEFAULT_PORT);
+ }
+
+ if (!sServer.isRunning()) {
+ try {
+ sServer.start();
+ } catch (IOException e) {
+ Log.d(LOG_TAG, "Error:", e);
+ }
+ }
+ } else {
+ sServer = new NoopViewServer();
+ }
+
+ return sServer;
+ }
+
+ private ViewServer() {
+ mPort = -1;
+ }
+
+ /**
+ * Creates a new ViewServer associated with the specified window manager on
+ * the specified local port. The server is not started by default.
+ *
+ * @param port
+ * The port for the server to listen to.
+ *
+ * @see #start()
+ */
+ private ViewServer(int port) {
+ mPort = port;
+ }
+
+ /**
+ * Starts the server.
+ *
+ * @return True if the server was successfully created, or false if it
+ * already exists.
+ * @throws IOException
+ * If the server cannot be created.
+ *
+ * @see #stop()
+ * @see #isRunning()
+ * @see WindowManagerService#startViewServer(int)
+ */
+ public boolean start() throws IOException {
+ if (mThread != null) {
+ return false;
+ }
+
+ mThread = new Thread(this, "Local View Server [port=" + mPort + "]");
+ mThreadPool = Executors.newFixedThreadPool(VIEW_SERVER_MAX_CONNECTIONS);
+ mThread.start();
+
+ return true;
+ }
+
+ /**
+ * Stops the server.
+ *
+ * @return True if the server was stopped, false if an error occurred or if
+ * the server wasn't started.
+ *
+ * @see #start()
+ * @see #isRunning()
+ * @see WindowManagerService#stopViewServer()
+ */
+ public boolean stop() {
+ if (mThread != null) {
+ mThread.interrupt();
+ if (mThreadPool != null) {
+ try {
+ mThreadPool.shutdownNow();
+ } catch (SecurityException e) {
+ Log.w(LOG_TAG, "Could not stop all view server threads");
+ }
+ }
+
+ mThreadPool = null;
+ mThread = null;
+
+ try {
+ mServer.close();
+ mServer = null;
+ return true;
+ } catch (IOException e) {
+ Log.w(LOG_TAG, "Could not close the view server");
+ }
+ }
+
+ mWindowsLock.writeLock().lock();
+ try {
+ mWindows.clear();
+ } finally {
+ mWindowsLock.writeLock().unlock();
+ }
+
+ mFocusLock.writeLock().lock();
+ try {
+ mFocusedWindow = null;
+ } finally {
+ mFocusLock.writeLock().unlock();
+ }
+
+ return false;
+ }
+
+ /**
+ * Indicates whether the server is currently running.
+ *
+ * @return True if the server is running, false otherwise.
+ *
+ * @see #start()
+ * @see #stop()
+ * @see WindowManagerService#isViewServerRunning()
+ */
+ public boolean isRunning() {
+ return mThread != null && mThread.isAlive();
+ }
+
+ /**
+ * Invoke this method to register a new view hierarchy.
+ *
+ * @param activity
+ * The activity whose view hierarchy/window to register
+ *
+ * @see #addWindow(View, String)
+ * @see #removeWindow(Activity)
+ */
+ public void addWindow(Activity activity) {
+ String name = activity.getTitle().toString();
+ if (TextUtils.isEmpty(name)) {
+ name = activity.getClass().getCanonicalName() + "/0x" + System.identityHashCode(activity);
+ } else {
+ name += "(" + activity.getClass().getCanonicalName() + ")";
+ }
+ addWindow(activity.getWindow().getDecorView(), name);
+ }
+
+ /**
+ * Invoke this method to unregister a view hierarchy.
+ *
+ * @param activity
+ * The activity whose view hierarchy/window to unregister
+ *
+ * @see #addWindow(Activity)
+ * @see #removeWindow(View)
+ */
+ public void removeWindow(Activity activity) {
+ removeWindow(activity.getWindow().getDecorView());
+ }
+
+ /**
+ * Invoke this method to register a new view hierarchy.
+ *
+ * @param view
+ * A view that belongs to the view hierarchy/window to register
+ * @name name The name of the view hierarchy/window to register
+ *
+ * @see #removeWindow(View)
+ */
+ public void addWindow(View view, String name) {
+ mWindowsLock.writeLock().lock();
+ try {
+ mWindows.put(view.getRootView(), name);
+ } finally {
+ mWindowsLock.writeLock().unlock();
+ }
+ fireWindowsChangedEvent();
+ }
+
+ /**
+ * Invoke this method to unregister a view hierarchy.
+ *
+ * @param view
+ * A view that belongs to the view hierarchy/window to unregister
+ *
+ * @see #addWindow(View, String)
+ */
+ public void removeWindow(View view) {
+ mWindowsLock.writeLock().lock();
+ try {
+ mWindows.remove(view.getRootView());
+ } finally {
+ mWindowsLock.writeLock().unlock();
+ }
+ fireWindowsChangedEvent();
+ }
+
+ /**
+ * Invoke this method to change the currently focused window.
+ *
+ * @param activity
+ * The activity whose view hierarchy/window hasfocus, or null to
+ * remove focus
+ */
+ public void setFocusedWindow(Activity activity) {
+ setFocusedWindow(activity.getWindow().getDecorView());
+ }
+
+ /**
+ * Invoke this method to change the currently focused window.
+ *
+ * @param view
+ * A view that belongs to the view hierarchy/window that has
+ * focus, or null to remove focus
+ */
+ public void setFocusedWindow(View view) {
+ mFocusLock.writeLock().lock();
+ try {
+ mFocusedWindow = view == null ? null : view.getRootView();
+ } finally {
+ mFocusLock.writeLock().unlock();
+ }
+ fireFocusChangedEvent();
+ }
+
+ /**
+ * Main server loop.
+ */
+ @Override
+ public void run() {
+ try {
+ mServer = new ServerSocket(mPort, VIEW_SERVER_MAX_CONNECTIONS, InetAddress.getLocalHost());
+ } catch (Exception e) {
+ Log.w(LOG_TAG, "Starting ServerSocket error: ", e);
+ }
+
+ while (mServer != null && Thread.currentThread() == mThread) {
+ // Any uncaught exception will crash the system process
+ try {
+ Socket client = mServer.accept();
+ if (mThreadPool != null) {
+ mThreadPool.submit(new ViewServerWorker(client));
+ } else {
+ try {
+ client.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ } catch (Exception e) {
+ Log.w(LOG_TAG, "Connection error: ", e);
+ }
+ }
+ }
+
+ private static boolean writeValue(Socket client, String value) {
+ boolean result;
+ BufferedWriter out = null;
+ try {
+ OutputStream clientStream = client.getOutputStream();
+ out = new BufferedWriter(new OutputStreamWriter(clientStream), 8 * 1024);
+ out.write(value);
+ out.write("\n");
+ out.flush();
+ result = true;
+ } catch (Exception e) {
+ result = false;
+ } finally {
+ if (out != null) {
+ try {
+ out.close();
+ } catch (IOException e) {
+ result = false;
+ }
+ }
+ }
+ return result;
+ }
+
+ private void fireWindowsChangedEvent() {
+ for (WindowListener listener : mListeners) {
+ listener.windowsChanged();
+ }
+ }
+
+ private void fireFocusChangedEvent() {
+ for (WindowListener listener : mListeners) {
+ listener.focusChanged();
+ }
+ }
+
+ private void addWindowListener(WindowListener listener) {
+ if (!mListeners.contains(listener)) {
+ mListeners.add(listener);
+ }
+ }
+
+ private void removeWindowListener(WindowListener listener) {
+ mListeners.remove(listener);
+ }
+
+ private interface WindowListener {
+ void windowsChanged();
+
+ void focusChanged();
+ }
+
+ private static class UncloseableOuputStream extends OutputStream {
+ private final OutputStream mStream;
+
+ UncloseableOuputStream(OutputStream stream) {
+ mStream = stream;
+ }
+
+ @Override
+ public void close() throws IOException {
+ // Don't close the stream
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ return mStream.equals(o);
+ }
+
+ @Override
+ public void flush() throws IOException {
+ mStream.flush();
+ }
+
+ @Override
+ public int hashCode() {
+ return mStream.hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return mStream.toString();
+ }
+
+ @Override
+ public void write(byte[] buffer, int offset, int count) throws IOException {
+ mStream.write(buffer, offset, count);
+ }
+
+ @Override
+ public void write(byte[] buffer) throws IOException {
+ mStream.write(buffer);
+ }
+
+ @Override
+ public void write(int oneByte) throws IOException {
+ mStream.write(oneByte);
+ }
+ }
+
+ private static class NoopViewServer extends ViewServer {
+ private NoopViewServer() {
+ }
+
+ @Override
+ public boolean start() throws IOException {
+ return false;
+ }
+
+ @Override
+ public boolean stop() {
+ return false;
+ }
+
+ @Override
+ public boolean isRunning() {
+ return false;
+ }
+
+ @Override
+ public void addWindow(Activity activity) {
+ }
+
+ @Override
+ public void removeWindow(Activity activity) {
+ }
+
+ @Override
+ public void addWindow(View view, String name) {
+ }
+
+ @Override
+ public void removeWindow(View view) {
+ }
+
+ @Override
+ public void setFocusedWindow(Activity activity) {
+ }
+
+ @Override
+ public void setFocusedWindow(View view) {
+ }
+
+ @Override
+ public void run() {
+ }
+ }
+
+ private class ViewServerWorker implements Runnable, WindowListener {
+ private Socket mClient;
+ private boolean mNeedWindowListUpdate;
+ private boolean mNeedFocusedWindowUpdate;
+
+ private final Object[] mLock = new Object[0];
+
+ public ViewServerWorker(Socket client) {
+ mClient = client;
+ mNeedWindowListUpdate = false;
+ mNeedFocusedWindowUpdate = false;
+ }
+
+ @Override
+ public void run() {
+ BufferedReader in = null;
+ try {
+ in = new BufferedReader(new InputStreamReader(mClient.getInputStream()), 1024);
+
+ final String request = in.readLine();
+
+ String command;
+ String parameters;
+
+ int index = request.indexOf(' ');
+ if (index == -1) {
+ command = request;
+ parameters = "";
+ } else {
+ command = request.substring(0, index);
+ parameters = request.substring(index + 1);
+ }
+
+ boolean result;
+ if (COMMAND_PROTOCOL_VERSION.equalsIgnoreCase(command)) {
+ result = writeValue(mClient, VALUE_PROTOCOL_VERSION);
+ } else if (COMMAND_SERVER_VERSION.equalsIgnoreCase(command)) {
+ result = writeValue(mClient, VALUE_SERVER_VERSION);
+ } else if (COMMAND_WINDOW_MANAGER_LIST.equalsIgnoreCase(command)) {
+ result = listWindows(mClient);
+ } else if (COMMAND_WINDOW_MANAGER_GET_FOCUS.equalsIgnoreCase(command)) {
+ result = getFocusedWindow(mClient);
+ } else if (COMMAND_WINDOW_MANAGER_AUTOLIST.equalsIgnoreCase(command)) {
+ result = windowManagerAutolistLoop();
+ } else {
+ result = windowCommand(mClient, command, parameters);
+ }
+
+ if (!result) {
+ Log.w(LOG_TAG, "An error occurred with the command: " + command);
+ }
+ } catch (IOException e) {
+ Log.w(LOG_TAG, "Connection error: ", e);
+ } finally {
+ if (in != null) {
+ try {
+ in.close();
+
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ if (mClient != null) {
+ try {
+ mClient.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ }
+
+ private boolean windowCommand(Socket client, String command, String parameters) {
+ boolean success = true;
+ BufferedWriter out = null;
+
+ try {
+ // Find the hash code of the window
+ int index = parameters.indexOf(' ');
+ if (index == -1) {
+ index = parameters.length();
+ }
+ final String code = parameters.substring(0, index);
+ int hashCode = (int) Long.parseLong(code, 16);
+
+ // Extract the command's parameter after the window description
+ if (index < parameters.length()) {
+ parameters = parameters.substring(index + 1);
+ } else {
+ parameters = "";
+ }
+
+ final View window = findWindow(hashCode);
+ if (window == null) {
+ return false;
+ }
+
+ // call stuff
+ final Method dispatch = ViewDebug.class.getDeclaredMethod("dispatchCommand", View.class, String.class, String.class, OutputStream.class);
+ dispatch.setAccessible(true);
+ dispatch.invoke(null, window, command, parameters, new UncloseableOuputStream(client.getOutputStream()));
+
+ if (!client.isOutputShutdown()) {
+ out = new BufferedWriter(new OutputStreamWriter(client.getOutputStream()));
+ out.write("DONE\n");
+ out.flush();
+ }
+
+ } catch (Exception e) {
+ Log.w(LOG_TAG, "Could not send command " + command + " with parameters " + parameters, e);
+ success = false;
+ } finally {
+ if (out != null) {
+ try {
+ out.close();
+ } catch (IOException e) {
+ success = false;
+ }
+ }
+ }
+
+ return success;
+ }
+
+ private View findWindow(int hashCode) {
+ if (hashCode == -1) {
+ View window = null;
+ mWindowsLock.readLock().lock();
+ try {
+ window = mFocusedWindow;
+ } finally {
+ mWindowsLock.readLock().unlock();
+ }
+ return window;
+ }
+
+ mWindowsLock.readLock().lock();
+ try {
+ for (Entry entry : mWindows.entrySet()) {
+ if (System.identityHashCode(entry.getKey()) == hashCode) {
+ return entry.getKey();
+ }
+ }
+ } finally {
+ mWindowsLock.readLock().unlock();
+ }
+
+ return null;
+ }
+
+ private boolean listWindows(Socket client) {
+ boolean result = true;
+ BufferedWriter out = null;
+
+ try {
+ mWindowsLock.readLock().lock();
+
+ OutputStream clientStream = client.getOutputStream();
+ out = new BufferedWriter(new OutputStreamWriter(clientStream), 8 * 1024);
+
+ for (Entry entry : mWindows.entrySet()) {
+ out.write(Integer.toHexString(System.identityHashCode(entry.getKey())));
+ out.write(' ');
+ out.append(entry.getValue());
+ out.write('\n');
+ }
+
+ out.write("DONE.\n");
+ out.flush();
+ } catch (Exception e) {
+ result = false;
+ } finally {
+ mWindowsLock.readLock().unlock();
+
+ if (out != null) {
+ try {
+ out.close();
+ } catch (IOException e) {
+ result = false;
+ }
+ }
+ }
+
+ return result;
+ }
+
+ private boolean getFocusedWindow(Socket client) {
+ boolean result = true;
+ String focusName = null;
+
+ BufferedWriter out = null;
+ try {
+ OutputStream clientStream = client.getOutputStream();
+ out = new BufferedWriter(new OutputStreamWriter(clientStream), 8 * 1024);
+
+ View focusedWindow = null;
+
+ mFocusLock.readLock().lock();
+ try {
+ focusedWindow = mFocusedWindow;
+ } finally {
+ mFocusLock.readLock().unlock();
+ }
+
+ if (focusedWindow != null) {
+ mWindowsLock.readLock().lock();
+ try {
+ focusName = mWindows.get(mFocusedWindow);
+ } finally {
+ mWindowsLock.readLock().unlock();
+ }
+
+ out.write(Integer.toHexString(System.identityHashCode(focusedWindow)));
+ out.write(' ');
+ out.append(focusName);
+ }
+ out.write('\n');
+ out.flush();
+ } catch (Exception e) {
+ result = false;
+ } finally {
+ if (out != null) {
+ try {
+ out.close();
+ } catch (IOException e) {
+ result = false;
+ }
+ }
+ }
+
+ return result;
+ }
+
+ @Override
+ public void windowsChanged() {
+ synchronized (mLock) {
+ mNeedWindowListUpdate = true;
+ mLock.notifyAll();
+ }
+ }
+
+ @Override
+ public void focusChanged() {
+ synchronized (mLock) {
+ mNeedFocusedWindowUpdate = true;
+ mLock.notifyAll();
+ }
+ }
+
+ private boolean windowManagerAutolistLoop() {
+ addWindowListener(this);
+ BufferedWriter out = null;
+ try {
+ out = new BufferedWriter(new OutputStreamWriter(mClient.getOutputStream()));
+ while (!Thread.interrupted()) {
+ boolean needWindowListUpdate = false;
+ boolean needFocusedWindowUpdate = false;
+ synchronized (mLock) {
+ while (!mNeedWindowListUpdate && !mNeedFocusedWindowUpdate) {
+ mLock.wait();
+ }
+ if (mNeedWindowListUpdate) {
+ mNeedWindowListUpdate = false;
+ needWindowListUpdate = true;
+ }
+ if (mNeedFocusedWindowUpdate) {
+ mNeedFocusedWindowUpdate = false;
+ needFocusedWindowUpdate = true;
+ }
+ }
+ if (needWindowListUpdate) {
+ out.write("LIST UPDATE\n");
+ out.flush();
+ }
+ if (needFocusedWindowUpdate) {
+ out.write("FOCUS UPDATE\n");
+ out.flush();
+ }
+ }
+ } catch (Exception e) {
+ Log.w(LOG_TAG, "Connection error: ", e);
+ } finally {
+ if (out != null) {
+ try {
+ out.close();
+ } catch (IOException e) {
+ // Ignore
+ }
+ }
+ removeWindowListener(this);
+ }
+ return true;
+ }
+ }
+}
\ No newline at end of file
diff --git a/AndroidAnnotations/androidannotations/src/main/java/org/androidannotations/helper/AndroidManifest.java b/AndroidAnnotations/androidannotations/src/main/java/org/androidannotations/helper/AndroidManifest.java
index e0526ff5d7..03edb4ea29 100644
--- a/AndroidAnnotations/androidannotations/src/main/java/org/androidannotations/helper/AndroidManifest.java
+++ b/AndroidAnnotations/androidannotations/src/main/java/org/androidannotations/helper/AndroidManifest.java
@@ -22,22 +22,26 @@ public class AndroidManifest {
private final String applicationPackage;
private final List componentQualifiedNames;
+ private final List permissionQualifiedNames;
private final String applicationClassName;
private final boolean libraryProject;
+ private final boolean debugabble;
- public static AndroidManifest createManifest(String applicationPackage, String applicationClassName, List componentQualifiedNames) {
- return new AndroidManifest(false, applicationPackage, applicationClassName, componentQualifiedNames);
+ public static AndroidManifest createManifest(String applicationPackage, String applicationClassName, List componentQualifiedNames, List permissionQualifiedNames, boolean debugabble) {
+ return new AndroidManifest(false, applicationPackage, applicationClassName, componentQualifiedNames, permissionQualifiedNames, debugabble);
}
public static AndroidManifest createLibraryManifest(String applicationPackage) {
- return new AndroidManifest(true, applicationPackage, "", Collections. emptyList());
+ return new AndroidManifest(true, applicationPackage, "", Collections. emptyList(), Collections. emptyList(), false);
}
- private AndroidManifest(boolean libraryProject, String applicationPackage, String applicationClassName, List componentQualifiedNames) {
+ private AndroidManifest(boolean libraryProject, String applicationPackage, String applicationClassName, List componentQualifiedNames, List permissionQualifiedNames, boolean debuggable) {
this.libraryProject = libraryProject;
this.applicationPackage = applicationPackage;
this.applicationClassName = applicationClassName;
this.componentQualifiedNames = componentQualifiedNames;
+ this.permissionQualifiedNames = permissionQualifiedNames;
+ this.debugabble = debuggable;
}
public String getApplicationPackage() {
@@ -48,6 +52,10 @@ public List getComponentQualifiedNames() {
return Collections.unmodifiableList(componentQualifiedNames);
}
+ public List getPermissionQualifiedNames() {
+ return Collections.unmodifiableList(permissionQualifiedNames);
+ }
+
public String getApplicationClassName() {
return applicationClassName;
}
@@ -56,4 +64,8 @@ public boolean isLibraryProject() {
return libraryProject;
}
+ public boolean isDebuggable() {
+ return debugabble;
+ }
+
}
diff --git a/AndroidAnnotations/androidannotations/src/main/java/org/androidannotations/helper/AndroidManifestFinder.java b/AndroidAnnotations/androidannotations/src/main/java/org/androidannotations/helper/AndroidManifestFinder.java
index c4649c191f..6f2cb89f68 100644
--- a/AndroidAnnotations/androidannotations/src/main/java/org/androidannotations/helper/AndroidManifestFinder.java
+++ b/AndroidAnnotations/androidannotations/src/main/java/org/androidannotations/helper/AndroidManifestFinder.java
@@ -196,6 +196,7 @@ private Option parse(File androidManifestFile, boolean libraryP
NodeList applicationNodes = documentElement.getElementsByTagName("application");
String applicationQualifiedName = null;
+ boolean applicationDebuggableMode = false;
if (applicationNodes.getLength() > 0) {
Node applicationNode = applicationNodes.item(0);
@@ -211,6 +212,11 @@ private Option parse(File androidManifestFile, boolean libraryP
messager.printMessage(Kind.NOTE, String.format("The class application declared in the AndroidManifest.xml cannot be found in the compile path: [%s]", nameAttribute.getNodeValue()));
}
}
+
+ Node debuggableAttribute = applicationNode.getAttributes().getNamedItem("android:debuggable");
+ if (debuggableAttribute != null) {
+ applicationDebuggableMode = debuggableAttribute.getNodeValue().equalsIgnoreCase("true") ? true : false;
+ }
}
NodeList activityNodes = documentElement.getElementsByTagName("activity");
@@ -231,7 +237,13 @@ private Option parse(File androidManifestFile, boolean libraryP
componentQualifiedNames.addAll(receiverQualifiedNames);
componentQualifiedNames.addAll(providerQualifiedNames);
- return Option.of(AndroidManifest.createManifest(applicationPackage, applicationQualifiedName, componentQualifiedNames));
+ NodeList usesPermissionNodes = documentElement.getElementsByTagName("uses-permission");
+ List usesPermissionQualifiedNames = extractUsesPermissionNames(applicationPackage, usesPermissionNodes);
+
+ List permissionQualifiedNames = new ArrayList();
+ permissionQualifiedNames.addAll(usesPermissionQualifiedNames);
+
+ return Option.of(AndroidManifest.createManifest(applicationPackage, applicationQualifiedName, componentQualifiedNames, permissionQualifiedNames, applicationDebuggableMode));
}
private List extractComponentNames(String applicationPackage, NodeList componentNodes) {
@@ -295,4 +307,20 @@ private String returnClassIfExistsOrNull(String className) {
}
}
+ private List extractUsesPermissionNames(String applicationPackage, NodeList usesPermissionNodes) {
+ List usesPermissionQualifiedNames = new ArrayList();
+
+ for (int i = 0; i < usesPermissionNodes.getLength(); i++) {
+ Node usesPermissionNode = usesPermissionNodes.item(i);
+ Node nameAttribute = usesPermissionNode.getAttributes().getNamedItem("android:name");
+
+ if (nameAttribute == null) {
+ return null;
+ }
+
+ usesPermissionQualifiedNames.add(nameAttribute.getNodeValue());
+ }
+ return usesPermissionQualifiedNames;
+ }
+
}
diff --git a/AndroidAnnotations/androidannotations/src/main/java/org/androidannotations/helper/CanonicalNameConstants.java b/AndroidAnnotations/androidannotations/src/main/java/org/androidannotations/helper/CanonicalNameConstants.java
index df038444fb..b30efbb233 100644
--- a/AndroidAnnotations/androidannotations/src/main/java/org/androidannotations/helper/CanonicalNameConstants.java
+++ b/AndroidAnnotations/androidannotations/src/main/java/org/androidannotations/helper/CanonicalNameConstants.java
@@ -82,6 +82,12 @@ public final class CanonicalNameConstants {
public static final String SQLITE_DATABASE = "android.database.sqlite.SQLiteDatabase";
public static final String KEY_STORE = "java.security.KeyStore";
public static final String SQLLITE_OPEN_HELPER = "android.database.sqlite.SQLiteOpenHelper";
+ public static final String VIEW_SERVER = "org.androidannotations.api.ViewServer";
+
+ /*
+ * Android permission
+ */
+ public static final String INTERNET_PERMISSION = "android.permission.INTERNET";
/*
* Sherlock
diff --git a/AndroidAnnotations/androidannotations/src/main/java/org/androidannotations/helper/ValidatorHelper.java b/AndroidAnnotations/androidannotations/src/main/java/org/androidannotations/helper/ValidatorHelper.java
index a4e2a665c5..8882cead0f 100644
--- a/AndroidAnnotations/androidannotations/src/main/java/org/androidannotations/helper/ValidatorHelper.java
+++ b/AndroidAnnotations/androidannotations/src/main/java/org/androidannotations/helper/ValidatorHelper.java
@@ -22,6 +22,7 @@
import static org.androidannotations.helper.AndroidConstants.LOG_VERBOSE;
import static org.androidannotations.helper.AndroidConstants.LOG_WARN;
import static org.androidannotations.helper.CanonicalNameConstants.HTTP_MESSAGE_CONVERTER;
+import static org.androidannotations.helper.CanonicalNameConstants.INTERNET_PERMISSION;
import static org.androidannotations.helper.ModelConstants.GENERATION_SUFFIX;
import java.lang.annotation.Annotation;
@@ -1157,4 +1158,21 @@ public void validateConverters(Element element, IsValid valid) {
}
}
+
+ public void isDebuggable(Element element, AndroidManifest androidManifest, IsValid valid) {
+ if (!androidManifest.isDebuggable()) {
+ valid.invalidate();
+ annotationHelper.printAnnotationError(element, "The application must be in debuggable mode. Please set android:debuggable to true in your AndroidManifest.xml file.");
+ }
+ }
+
+ public void hasInternetPermission(Element element, AndroidManifest androidManifest, IsValid valid) {
+ String internetPermissionQualifiedName = INTERNET_PERMISSION;
+
+ List permissionQualifiedNames = androidManifest.getPermissionQualifiedNames();
+ if (!permissionQualifiedNames.contains(internetPermissionQualifiedName)) {
+ valid.invalidate();
+ annotationHelper.printAnnotationError(element, "Your application must require the INTERNET permission.");
+ }
+ }
}
diff --git a/AndroidAnnotations/androidannotations/src/main/java/org/androidannotations/processing/EBeanHolder.java b/AndroidAnnotations/androidannotations/src/main/java/org/androidannotations/processing/EBeanHolder.java
index 895012d286..9ce89fcf3b 100644
--- a/AndroidAnnotations/androidannotations/src/main/java/org/androidannotations/processing/EBeanHolder.java
+++ b/AndroidAnnotations/androidannotations/src/main/java/org/androidannotations/processing/EBeanHolder.java
@@ -62,6 +62,9 @@ public class EBeanHolder {
public JMethod restoreSavedInstanceStateMethod;
public JBlock saveInstanceStateBlock;
+ public JBlock onResumeBlock;
+ public JBlock onDestroyBlock;
+
public JExpression contextRef;
/**
* Should not be used by inner annotations that target services, broadcast
diff --git a/AndroidAnnotations/androidannotations/src/main/java/org/androidannotations/processing/EBeansHolder.java b/AndroidAnnotations/androidannotations/src/main/java/org/androidannotations/processing/EBeansHolder.java
index 090d001e82..42bdadb41a 100644
--- a/AndroidAnnotations/androidannotations/src/main/java/org/androidannotations/processing/EBeansHolder.java
+++ b/AndroidAnnotations/androidannotations/src/main/java/org/androidannotations/processing/EBeansHolder.java
@@ -91,6 +91,7 @@ public class Classes {
public final JClass ON_TOUCH_LISTENER = refClass(CanonicalNameConstants.ON_TOUCH_LISTENER);
public final JClass HANDLER = refClass(CanonicalNameConstants.HANDLER);
public final JClass KEY_STORE = refClass(CanonicalNameConstants.KEY_STORE);
+ public final JClass VIEW_SERVER = refClass(CanonicalNameConstants.VIEW_SERVER);
/*
* Sherlock
diff --git a/AndroidAnnotations/androidannotations/src/main/java/org/androidannotations/processing/HierarchyViewerSupportProcessor.java b/AndroidAnnotations/androidannotations/src/main/java/org/androidannotations/processing/HierarchyViewerSupportProcessor.java
new file mode 100644
index 0000000000..80d92c86f2
--- /dev/null
+++ b/AndroidAnnotations/androidannotations/src/main/java/org/androidannotations/processing/HierarchyViewerSupportProcessor.java
@@ -0,0 +1,87 @@
+/**
+ * Copyright (C) 2010-2012 eBusiness Information, Excilys Group
+ *
+ * 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 org.androidannotations.processing;
+
+import static com.sun.codemodel.JExpr._super;
+import static com.sun.codemodel.JExpr._this;
+
+import java.lang.annotation.Annotation;
+
+import javax.lang.model.element.Element;
+
+import org.androidannotations.annotations.HierarchyViewerSupport;
+import org.androidannotations.api.ViewServer;
+
+import com.sun.codemodel.JBlock;
+import com.sun.codemodel.JCodeModel;
+import com.sun.codemodel.JInvocation;
+import com.sun.codemodel.JMethod;
+import com.sun.codemodel.JMod;
+
+public class HierarchyViewerSupportProcessor implements DecoratingElementProcessor {
+
+ @Override
+ public Class extends Annotation> getTarget() {
+ return HierarchyViewerSupport.class;
+ }
+
+ @Override
+ public void process(Element element, JCodeModel codeModel, EBeanHolder holder) {
+
+ holder.generateApiClass(element, ViewServer.class);
+
+ // Methods
+ afterSetContentView(codeModel, holder);
+ onDestroyMethod(codeModel, holder);
+ onResumeMethod(codeModel, holder);
+ }
+
+ private void afterSetContentView(JCodeModel codeModel, EBeanHolder holder) {
+ JBlock afterSetContentViewBody = holder.afterSetContentView.body();
+
+ JInvocation viewServerInvocation = holder.classes().VIEW_SERVER.staticInvoke("get").arg(_this());
+ afterSetContentViewBody.invoke(viewServerInvocation, "addWindow").arg(_this());
+ }
+
+ private void onDestroyMethod(JCodeModel codeModel, EBeanHolder holder) {
+ JBlock onDestroyBlock = holder.onDestroyBlock;
+
+ if (onDestroyBlock == null) {
+ JMethod method = holder.generatedClass.method(JMod.PUBLIC, codeModel.VOID, "onDestroy");
+ method.annotate(Override.class);
+ holder.onDestroyBlock = method.body();
+ holder.onDestroyBlock.invoke(_super(), method);
+ }
+
+ JInvocation viewServerInvocation = holder.classes().VIEW_SERVER.staticInvoke("get").arg(_this());
+ holder.onDestroyBlock.invoke(viewServerInvocation, "removeWindow").arg(_this());
+ }
+
+ private void onResumeMethod(JCodeModel codeModel, EBeanHolder holder) {
+ JBlock onResumeBlock = holder.onResumeBlock;
+
+ if (onResumeBlock == null) {
+ JMethod method = holder.generatedClass.method(JMod.PUBLIC, codeModel.VOID, "onResume");
+ method.annotate(Override.class);
+ holder.onResumeBlock = method.body();
+ holder.onResumeBlock.invoke(_super(), method);
+ }
+
+ JInvocation viewServerInvocation = holder.classes().VIEW_SERVER.staticInvoke("get").arg(_this());
+ holder.onResumeBlock.invoke(viewServerInvocation, "setFocusedWindow").arg(_this());
+ }
+
+}
diff --git a/AndroidAnnotations/androidannotations/src/main/java/org/androidannotations/processing/RoboGuiceProcessor.java b/AndroidAnnotations/androidannotations/src/main/java/org/androidannotations/processing/RoboGuiceProcessor.java
index 284457795c..8dd8f9d335 100644
--- a/AndroidAnnotations/androidannotations/src/main/java/org/androidannotations/processing/RoboGuiceProcessor.java
+++ b/AndroidAnnotations/androidannotations/src/main/java/org/androidannotations/processing/RoboGuiceProcessor.java
@@ -33,6 +33,7 @@
import org.androidannotations.annotations.RoboGuice;
import org.androidannotations.processing.EBeansHolder.Classes;
+
import com.sun.codemodel.JBlock;
import com.sun.codemodel.JClass;
import com.sun.codemodel.JCodeModel;
@@ -110,10 +111,10 @@ private void onStartMethod(JCodeModel codeModel, EBeanHolder holder, JFieldVar s
private void onResumeMethod(JCodeModel codeModel, EBeanHolder holder, JFieldVar scope, JFieldVar eventManager) {
JMethod method = holder.generatedClass.method(JMod.PUBLIC, codeModel.VOID, "onResume");
method.annotate(Override.class);
- JBlock body = method.body();
- body.invoke(scope, "enter").arg(_this());
- body.invoke(_super(), method);
- fireEvent(holder, eventManager, body, holder.classes().ON_RESUME_EVENT);
+ holder.onResumeBlock = method.body();
+ holder.onResumeBlock.invoke(scope, "enter").arg(_this());
+ holder.onResumeBlock.invoke(_super(), method);
+ fireEvent(holder, eventManager, holder.onResumeBlock, holder.classes().ON_RESUME_EVENT);
}
private void onPauseMethod(JCodeModel codeModel, EBeanHolder holder, JFieldVar scope, JFieldVar eventManager) {
@@ -159,10 +160,10 @@ private void onStopMethod(JCodeModel codeModel, EBeanHolder holder, JFieldVar sc
private void onDestroyMethod(JCodeModel codeModel, EBeanHolder holder, JFieldVar scope, JFieldVar eventManager) {
JMethod method = holder.generatedClass.method(JMod.PUBLIC, codeModel.VOID, "onDestroy");
method.annotate(Override.class);
- JBlock body = method.body();
- body.invoke(scope, "enter").arg(_this());
+ holder.onDestroyBlock = method.body();
+ holder.onDestroyBlock.invoke(scope, "enter").arg(_this());
- JTryBlock tryBlock = body._try();
+ JTryBlock tryBlock = holder.onDestroyBlock._try();
fireEvent(holder, eventManager, tryBlock.body(), holder.classes().ON_DESTROY_EVENT);
JBlock finallyBody = tryBlock._finally();
diff --git a/AndroidAnnotations/androidannotations/src/main/java/org/androidannotations/validation/HierarchyViewerSupportValidator.java b/AndroidAnnotations/androidannotations/src/main/java/org/androidannotations/validation/HierarchyViewerSupportValidator.java
new file mode 100644
index 0000000000..df01bb90e5
--- /dev/null
+++ b/AndroidAnnotations/androidannotations/src/main/java/org/androidannotations/validation/HierarchyViewerSupportValidator.java
@@ -0,0 +1,59 @@
+/**
+ * Copyright (C) 2010-2012 eBusiness Information, Excilys Group
+ *
+ * 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 org.androidannotations.validation;
+
+import java.lang.annotation.Annotation;
+
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.lang.model.element.Element;
+
+import org.androidannotations.annotations.HierarchyViewerSupport;
+import org.androidannotations.helper.AndroidManifest;
+import org.androidannotations.helper.TargetAnnotationHelper;
+import org.androidannotations.helper.ValidatorHelper;
+import org.androidannotations.model.AnnotationElements;
+
+public class HierarchyViewerSupportValidator implements ElementValidator {
+
+ private final ValidatorHelper validatorHelper;
+ private final AndroidManifest androidManifest;
+
+ public HierarchyViewerSupportValidator(ProcessingEnvironment processingEnv, AndroidManifest androidManifest) {
+ this.androidManifest = androidManifest;
+ TargetAnnotationHelper annotationHelper = new TargetAnnotationHelper(processingEnv, getTarget());
+ validatorHelper = new ValidatorHelper(annotationHelper);
+ }
+
+ @Override
+ public Class extends Annotation> getTarget() {
+ return HierarchyViewerSupport.class;
+ }
+
+ @Override
+ public boolean validate(Element element, AnnotationElements validatedElements) {
+
+ IsValid valid = new IsValid();
+
+ validatorHelper.hasEActivity(element, validatedElements, valid);
+
+ validatorHelper.isDebuggable(element, androidManifest, valid);
+
+ validatorHelper.hasInternetPermission(element, androidManifest, valid);
+
+ return valid.isValid();
+ }
+
+}
diff --git a/AndroidAnnotations/androidannotations/src/test/java/org/androidannotations/hierarchyviewer/HierarchyViewerActivity.java b/AndroidAnnotations/androidannotations/src/test/java/org/androidannotations/hierarchyviewer/HierarchyViewerActivity.java
new file mode 100644
index 0000000000..c9689de27d
--- /dev/null
+++ b/AndroidAnnotations/androidannotations/src/test/java/org/androidannotations/hierarchyviewer/HierarchyViewerActivity.java
@@ -0,0 +1,27 @@
+/**
+ * Copyright (C) 2010-2012 eBusiness Information, Excilys Group
+ *
+ * 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 org.androidannotations.hierarchyviewer;
+
+import org.androidannotations.annotations.EActivity;
+import org.androidannotations.annotations.HierarchyViewerSupport;
+
+import android.app.Activity;
+
+@HierarchyViewerSupport
+@EActivity
+public class HierarchyViewerActivity extends Activity {
+
+}
diff --git a/AndroidAnnotations/androidannotations/src/test/java/org/androidannotations/hierarchyviewer/HierarchyViewerActivityTest.java b/AndroidAnnotations/androidannotations/src/test/java/org/androidannotations/hierarchyviewer/HierarchyViewerActivityTest.java
new file mode 100644
index 0000000000..2330d9e306
--- /dev/null
+++ b/AndroidAnnotations/androidannotations/src/test/java/org/androidannotations/hierarchyviewer/HierarchyViewerActivityTest.java
@@ -0,0 +1,53 @@
+/**
+ * Copyright (C) 2010-2012 eBusiness Information, Excilys Group
+ *
+ * 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 org.androidannotations.hierarchyviewer;
+
+import java.io.IOException;
+
+import org.androidannotations.AndroidAnnotationProcessor;
+import org.androidannotations.utils.AAProcessorTestHelper;
+import org.junit.Before;
+import org.junit.Test;
+
+public class HierarchyViewerActivityTest extends AAProcessorTestHelper {
+
+ @Before
+ public void setup() {
+ addManifestProcessorParameter(HierarchyViewerActivityTest.class);
+ addProcessor(AndroidAnnotationProcessor.class);
+ }
+
+ @Test
+ public void activity_subclass_in_manifest_compiles() {
+ addManifestProcessorParameter(HierarchyViewerActivityTest.class, "AndroidManifest.xml");
+ CompileResult result = compileFiles(HierarchyViewerActivity.class);
+ assertCompilationSuccessful(result);
+ }
+
+ @Test
+ public void activity_in_manifest_does_not_compile() throws IOException {
+ addManifestProcessorParameter(HierarchyViewerActivityTest.class, "NoDebbugableManifest.xml");
+ CompileResult result = compileFiles(HierarchyViewerActivity.class);
+ assertCompilationErrorOn(HierarchyViewerActivity.class, "@HierarchyViewerSupport", result);
+ }
+
+ @Test
+ public void activity_not_in_manifest_compiles_with_warning() throws IOException {
+ addManifestProcessorParameter(HierarchyViewerActivityTest.class, "NoInternetPermissionManifest.xml");
+ CompileResult result = compileFiles(HierarchyViewerActivity.class);
+ assertCompilationErrorOn(HierarchyViewerActivity.class, "@HierarchyViewerSupport", result);
+ }
+}
diff --git a/AndroidAnnotations/androidannotations/src/test/resources/org/androidannotations/hierarchyviewer/AndroidManifest.xml b/AndroidAnnotations/androidannotations/src/test/resources/org/androidannotations/hierarchyviewer/AndroidManifest.xml
new file mode 100644
index 0000000000..f358c07d83
--- /dev/null
+++ b/AndroidAnnotations/androidannotations/src/test/resources/org/androidannotations/hierarchyviewer/AndroidManifest.xml
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/AndroidAnnotations/androidannotations/src/test/resources/org/androidannotations/hierarchyviewer/NoDebbugableManifest.xml b/AndroidAnnotations/androidannotations/src/test/resources/org/androidannotations/hierarchyviewer/NoDebbugableManifest.xml
new file mode 100644
index 0000000000..b083deed50
--- /dev/null
+++ b/AndroidAnnotations/androidannotations/src/test/resources/org/androidannotations/hierarchyviewer/NoDebbugableManifest.xml
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/AndroidAnnotations/androidannotations/src/test/resources/org/androidannotations/hierarchyviewer/NoInternetPermissionManifest.xml b/AndroidAnnotations/androidannotations/src/test/resources/org/androidannotations/hierarchyviewer/NoInternetPermissionManifest.xml
new file mode 100644
index 0000000000..a104ccba54
--- /dev/null
+++ b/AndroidAnnotations/androidannotations/src/test/resources/org/androidannotations/hierarchyviewer/NoInternetPermissionManifest.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/AndroidAnnotations/functional-test-1-5/AndroidManifest.xml b/AndroidAnnotations/functional-test-1-5/AndroidManifest.xml
index 9a17c03c25..c9048224dc 100644
--- a/AndroidAnnotations/functional-test-1-5/AndroidManifest.xml
+++ b/AndroidAnnotations/functional-test-1-5/AndroidManifest.xml
@@ -24,10 +24,13 @@
+
+
@@ -74,6 +77,7 @@
+
diff --git a/AndroidAnnotations/functional-test-1-5/src/main/java/org/androidannotations/test15/HierarchyViewerActivity.java b/AndroidAnnotations/functional-test-1-5/src/main/java/org/androidannotations/test15/HierarchyViewerActivity.java
new file mode 100644
index 0000000000..5eb91bc10d
--- /dev/null
+++ b/AndroidAnnotations/functional-test-1-5/src/main/java/org/androidannotations/test15/HierarchyViewerActivity.java
@@ -0,0 +1,25 @@
+/**
+ * Copyright (C) 2010-2012 eBusiness Information, Excilys Group
+ *
+ * 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 org.androidannotations.test15;
+
+import org.androidannotations.annotations.EActivity;
+import org.androidannotations.annotations.HierarchyViewerSupport;
+
+@HierarchyViewerSupport
+@EActivity(R.layout.clickable_widgets)
+public class HierarchyViewerActivity extends AbstractActivity {
+
+}
\ No newline at end of file