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 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 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