diff --git a/.idea/misc.xml b/.idea/misc.xml index dfd2c79..37a7509 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,6 +1,6 @@ - + diff --git a/README.md b/README.md index 783819c..94a5fab 100644 --- a/README.md +++ b/README.md @@ -1,21 +1,123 @@ # android-library +## ADDING MOQUALITY LIBRARY TO YOUR PROJECT'S TEST CLASSES +------------------------------------------------------- +- in your main build.gradle (Project) file, add: -### Run single test +``` +allprojects { + repositories { + ... + + // for MoQuality library + maven {url "https://jitpack.io" } + ... + ] +] ``` -./gradlew cAT -Pandroid.testInstrumentationRunnerArguments.class=com.moquality.android.AppTest + +- under the "dependencies {" section of your build.gradle (Module) file, add: ``` + // MoQuality dependencies + implementation 'com.github.moquality:android-library:master-SNAPSHOT' + testImplementation 'com.github.moquality:android-library:master-SNAPSHOT' + androidTestImplementation 'com.github.moquality:android-library:master-SNAPSHOT' +``` + +- in your test class, declare a private instance of the MoQuality library: -Create screenshots folder ``` -adb shell -cd /sdcard/Pictures -mkdir screenshots +private MoQuality moQBot = new MoQuality(); ``` -Screenshots are in the Pictures/screenshots folder. Command to pull. +Add an initialize method at the top of the same class, + ``` -adb pull /sdcard/Pictures/screenshots . -``` \ No newline at end of file + @Before + public void initialize() { + String deviceId; + + Bundle extras = InstrumentationRegistry.getArguments(); + + deviceId = extras.getString("deviceId"); + + Log.i(TAG, "Arguments = " + extras.toString()); + Log.i(TAG, "deviceId" + deviceId); + + moQBot.register(this, deviceId); + } +``` + +Now add the following MoQuality specific calls, + +``` + @Test + public void runTest() { + moQBot.runSocketIOTest(); + } + + @After + public void shutdown() { + moQBot.shutdown(); + } +``` + +Next write your public test methods as normal. If you wish to capture a screen shot via the library during the test, insert the following code: + +``` + try { + moQBot.takeScreenshot(""); + } catch (IOException e) { + e.printStackTrace(); + } +``` + +Finally create a JSON test script, that calls your test methods. For example, +``` +[ { + "type": "espresso", <--- indicates the type of command, "espresso", "uiautomator", "devices", or "signal" + "msg": + { + "deviceId": "device1", <---- allows you to target a specific device for the test script + "cmd": "openNavDrawer", <---- reference to the exact test method name + "args": [] <--- optional cmd arguments that get passed back to the test class via the library + } + }, + { + "type": "signal", <--- signal allows you to run internal commands on the device outside of the test class + "msg": + { + "deviceId": "device1", + "cmd": "sleep", <--- puts the specified device in a wait state + "args": [ + 3000 <--- number of milliseconds to wait + ] + } + } +] +``` + +----------------------------------------------------------------------------------- + +## Run demo tests with the library and Google I/O scheduler sample project. + +Grab the "iosched-modified" folder from the sample project on Github at +https://github.com/moquality/multidevice +and open the folder in Android Studio. Start up an instance of an emulated device (should list as "emulator-5554" when the adb devices command is run. +Compile the the project and under the Gradle tab window >install options, click the "installDebug", "installStaging", and "installStagingAndroidTest" options. +Once installed, open the I/O scheduler app to check the it is ready to run and then background the app. + +In one terminal window type the following adb command: +``` +> adb -s emulator-5554 shell am instrument -w -e class com.google.samples.apps.iosched.tests.pages.HomePage -e deviceId device1 com.google.samples.apps.iosched.test/androidx.test.runner.AndroidJUnitRunner +``` +to spool up the I/O scheduler app in test mode referencing the HomePage test class + +Now in a second terminal window, cd into the "server" folder of the sample project and type: +``` +> yarn runner +``` + +The Google I/O app on the emulator should now run through the sample tests outlined in the HomePage class while also outputting testing status in the secon terminal window as it proceeds through each step. \ No newline at end of file diff --git a/androidtest-library/build.gradle b/androidtest-library/build.gradle index 33b7e91..667b5a8 100644 --- a/androidtest-library/build.gradle +++ b/androidtest-library/build.gradle @@ -36,7 +36,9 @@ dependencies { testImplementation 'junit:junit:4.12' implementation 'androidx.test:runner:1.2.0' - + implementation ('io.socket:socket.io-client:1.0.0') { + exclude group: 'org.json', module: 'json' + } androidTestImplementation 'androidx.test:runner:1.2.0' androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' diff --git a/androidtest-library/src/androidTest/java/com/moquality/android/ExampleInstrumentedTest.java b/androidtest-library/src/androidTest/java/com/moquality/android/ExampleInstrumentedTest.java deleted file mode 100644 index 0b56536..0000000 --- a/androidtest-library/src/androidTest/java/com/moquality/android/ExampleInstrumentedTest.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.moquality.android; - -import android.content.Context; - -import androidx.test.platform.app.InstrumentationRegistry; -import androidx.test.ext.junit.runners.AndroidJUnit4; - -import org.junit.Test; -import org.junit.runner.RunWith; - -import static org.junit.Assert.*; - -/** - * Instrumented test, which will execute on an Android device. - * - * @see Testing documentation - */ -@RunWith(AndroidJUnit4.class) -public class ExampleInstrumentedTest { - @Test - public void useAppContext() { - // Context of the app under test. - Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); - - assertEquals("com.moquality.anroidtest_library.test", appContext.getPackageName()); - } -} diff --git a/androidtest-library/src/main/java/com/moquality/android/MoQuality.java b/androidtest-library/src/main/java/com/moquality/android/MoQuality.java index 62dda71..6d64bb7 100644 --- a/androidtest-library/src/main/java/com/moquality/android/MoQuality.java +++ b/androidtest-library/src/main/java/com/moquality/android/MoQuality.java @@ -1,22 +1,75 @@ package com.moquality.android; import android.graphics.Bitmap; +import android.os.Looper; import android.util.Log; import androidx.test.runner.screenshot.ScreenCapture; import androidx.test.runner.screenshot.Screenshot; import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; -public class MoQuality { +public class MoQuality implements SocketIOHandlerThread.Callback { - public static String TAG = "MQ"; + private static String TAG = "MQ"; + + private SocketIOHandlerThread mHandlerThread; + + private ArrayList appTests = new ArrayList<>(); public int log(String message) { Log.i(TAG, message); return 0; } + public void init(String deviceId) { + // launch Socket IO chat thread + if (Looper.myLooper() == null) { + Looper.prepare(); + } + mHandlerThread = new SocketIOHandlerThread(this, deviceId); + mHandlerThread.start(); + mHandlerThread.prepareHandler(); + } + + public void register(Object test) { + this.appTests.add(test); + } + + public void unregister(Object test){ + for (Object testClass:appTests) { + if (testClass.getClass().getSimpleName().equalsIgnoreCase(test.getClass().getSimpleName())){ + appTests.remove(testClass); + } + } + } + + public void shutdown() { + if(mHandlerThread != null){ + mHandlerThread.quit(); + mHandlerThread.interrupt(); + } + } + + public void runSocketIOTest() { + mHandlerThread.queueTask(TestConstants.SOCKET_IO_START); + try { + long threadStartTime = System.currentTimeMillis(); + long executionTimeInMillis = 0; + while (mHandlerThread.isThreadAlive()) { + executionTimeInMillis = System.currentTimeMillis() - threadStartTime; + } + + Log.i("SOCKET_IO THREAD", "Execution time = " + executionTimeInMillis/1000 + " seconds"); + } catch (Exception e) { + Log.d("SOCKET IO", "Test interrupted"); + } + } + public void takeScreenshot(String name) throws IOException { log("Saving screenshot "+name); ScreenCapture capture = Screenshot.capture(); @@ -29,5 +82,141 @@ public void takeScreenshot(String name) throws IOException { throw e; } } + + @Override + public void onSocketTaskCompleted(int taskId) { + switch (taskId){ + case TestConstants.SOCKET_IO_MSG_RECEIVED: + Log.i(TAG, "Message received in UI"); + break; + case TestConstants.SOCKET_IO_DISCONNECTED: + break; + } + } + + @Override + public void onSocketEventReceived(String eventName, String method, List classArgs, List stringArgs) { + switch (eventName){ + case TestConstants.SOCKET_EVENT_CALL: + Log.i(TAG, "CALL command received for this device."); + callAppTestMethod(method, classArgs, stringArgs); + break; + case TestConstants.SOCKET_EVENT_STATUS: + Log.i(TAG, "STATUS command received for this device."); + break; + case TestConstants.SOCKET_EVENT_RETURN: + Log.i(TAG, "RETURN command received for this device."); + } + } + + @Override + public void onSocketEventReceived(String eventName, String className, String method, List classArgs, List stringArgs) { + Log.i(TAG, "CALL specific test class for this device."); + callAppTestMethod(className, method, classArgs, stringArgs); + } + + private void setMode(String mode) { + switch (mode) { + case "reflect": + mHandlerThread.queueTask(TestConstants.REFLECT_MODE); + + break; + case "quit": + this.mHandlerThread.quit(); + + break; + default: + Log.i(TAG, "Unhandled mode " + mode); + } + } + + private void callAppTestMethod(String method, List classArgs, List stringArgs){ + if (appTests!=null) { + + if (stringArgs!=null && stringArgs.size()>0) { + Log.i(TAG, "******* METHOD = " + method + " ARGS = " + stringArgs.get(0)); + } else { + Log.i(TAG, "******* METHOD = " + method); + } + if (method.equalsIgnoreCase("setMode")){ + if (stringArgs!=null) { + setMode(stringArgs.get(0)); + } + } else { + for (Object testClass : appTests) { + // this should run all methods that match the specified method on all registered classes + try { + Method m = testClass.getClass().getMethod(method, classArgs.toArray(new Class[0])); + + try { + Log.i(TAG, testClass.getClass().getSimpleName() + " - method called = " + m.toString()); + String data = ""; + if (stringArgs != null && stringArgs.size() > 0) { + data = m.invoke(testClass, stringArgs.toArray(new String[0])).toString(); + } else { + data = m.invoke(testClass).toString(); + } + Log.i(TAG, "return " + data); + } catch (NullPointerException e) { + Log.i(TAG, "Error returning data from method invoke()"); + } catch (IllegalAccessException e) { + Log.i(TAG, method + " invoke error - Illegal Access Exception"); + } catch (InvocationTargetException e) { + Log.i(TAG, method + " invoke error - Invocation Target Exception"); + } + } catch (NoSuchMethodException e) { + Log.i(TAG, "Method (" + method + ") not found in app test class " + testClass.getClass().getSimpleName()); + } catch (NullPointerException e) { + Log.i(TAG, "Null pointer exception on class constructor."); + } + } + } + } + } + + private void callAppTestMethod(String className, String method, List classArgs, List stringArgs){ + if (appTests!=null) { + if (stringArgs!=null && stringArgs.size()>0) { + Log.i(TAG, "******* METHOD = " + method + " ARGS = " + stringArgs.get(0)); + } else { + Log.i(TAG, "******* METHOD = " + method); + } + if (method.equalsIgnoreCase("setMode")){ + if (stringArgs!=null) { + setMode(stringArgs.get(0)); + } + } else { + for (Object testClass : appTests) { + if (className.equalsIgnoreCase((testClass.getClass().getSimpleName()))) { + try { + Method m = testClass.getClass().getMethod(method, classArgs.toArray(new Class[0])); + + try { + Log.i(TAG, testClass.getClass().getSimpleName() + " - method called = " + m.toString()); + String data = ""; + if (stringArgs != null && stringArgs.size() > 0) { + data = m.invoke(testClass, stringArgs.toArray(new String[0])).toString(); + } else { + data = m.invoke(testClass).toString(); + } + Log.i(TAG, "return " + data); + } catch (NullPointerException e) { + Log.i(TAG, "Error returning data from method invoke()"); + } catch (IllegalAccessException e) { + Log.i(TAG, method + " invoke error - Illegal Access Exception"); + } catch (InvocationTargetException e) { + Log.i(TAG, method + " invoke error - Invocation Target Exception"); + } + } catch (NoSuchMethodException e) { + Log.i(TAG, "Method (" + method + ") not found in app test class " + testClass.getClass().getSimpleName()); + } catch (NullPointerException e) { + Log.i(TAG, "Null pointer exception on class constructor."); + } + break; + } + } + } + } + } } diff --git a/androidtest-library/src/main/java/com/moquality/android/SocketIOHandlerThread.java b/androidtest-library/src/main/java/com/moquality/android/SocketIOHandlerThread.java new file mode 100644 index 0000000..ea340f0 --- /dev/null +++ b/androidtest-library/src/main/java/com/moquality/android/SocketIOHandlerThread.java @@ -0,0 +1,312 @@ +package com.moquality.android; + +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Message; +import android.util.Log; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.List; + +import io.socket.client.IO; +import io.socket.client.Socket; +import io.socket.emitter.Emitter; + +class SocketIOHandlerThread extends HandlerThread { + + private Socket socket; + private Handler mWorkerHandler; + + private static final String TAG = SocketIOHandlerThread.class.getSimpleName(); + private Callback mCallback; + private boolean isAlive = true; + private boolean isConnected = false; + private String deviceId; + private int botMode = TestConstants.DEFAULT_MODE; + + public interface Callback { + void onSocketTaskCompleted(int taskId); + void onSocketEventReceived(String eventName, String method, List classArgs, List stringArgs); + void onSocketEventReceived(String eventName, String className, String method, List classArgs, List stringArgs); + } + + SocketIOHandlerThread(Callback callback, String deviceId) { + super(TAG); + mCallback = callback; + this.deviceId = deviceId; + } + + private Emitter.Listener onConnect = new Emitter.Listener() { + @Override + public void call(Object... args) { + Log.i(TAG, "Android bot connected "+ deviceId); + isConnected = true; + JSONObject data = new JSONObject(); + try { + data.put("deviceId", deviceId); + } catch (JSONException e) { + Log.i("EVENT_CONNECT", "JSON error occurred"); + } + socket.emit("status", data.toString()); + } + }; + + private Emitter.Listener onConnecting = new Emitter.Listener() { + @Override + public void call(Object... args) { + Log.i("SOCKET_EVENT", d("connecting")); + } + }; + + private Emitter.Listener onDisconnect = new Emitter.Listener() { + @Override + public void call(Object... args) { + Log.i("SOCKET_EVENT", "disconnected"); + isConnected = false; + } + }; + + private Emitter.Listener onConnectError = new Emitter.Listener() { + @Override + public void call(Object... args) { + Log.i("SOCKET_EVENT", d("connect error", args)); + } + }; + + private Emitter.Listener onMessage = new Emitter.Listener() { + @Override + public void call(Object... args) { + Log.i("SOCKET_EVENT", d("message")); + } + }; + + private Emitter.Listener onPing = new Emitter.Listener() { + @Override + public void call(Object... args) { + Log.i("SOCKET_EVENT", "ping"); + } + }; + + private Emitter.Listener onPong = new Emitter.Listener() { + @Override + public void call(Object... args) { + Log.i("SOCKET_EVENT", "pong"); + } + }; + + private Emitter.Listener onReconnect = new Emitter.Listener() { + @Override + public void call(Object... args) { + Log.i("SOCKET_EVENT", d("reconnecting")); + } + }; + + private Emitter.Listener onReconnectError = new Emitter.Listener() { + @Override + public void call(Object... args) { + Log.i("SOCKET_EVENT", d("reconnect error")); + } + }; + + private Emitter.Listener onReconnecting = new Emitter.Listener() { + @Override + public void call(Object... args) { + Log.i("SOCKET_EVENT", d("reconnecting")); + } + }; + + private Emitter.Listener onStatus = new Emitter.Listener() { + @Override + public void call(Object... args) { + Log.i("SOCKET_EVENT", "received status command"); + } + }; + + private Emitter.Listener onReturn = new Emitter.Listener() { + @Override + public void call(Object... args) { + Log.i("SOCKET_EVENT", "received return command"); + } + }; + + private Emitter.Listener onCall = new Emitter.Listener() { + @Override + public void call(Object... args) { + Log.i("SOCKET_EVENT", "received call command"); + String command = args[0].toString(); + Log.i("call", command + " bot mode = " + botMode); + + try { + JSONObject obj = new JSONObject(command); + String targetDeviceId = obj.getString("deviceId"); + if(deviceId.equals(targetDeviceId)) { + + String className = obj.get("class").toString(); + + String cmd = obj.get("cmd").toString(); + JSONArray cmdArgs = obj.getJSONArray("args"); + + List classArgs = new ArrayList<>(); + List stringArgs = new ArrayList<>(); + for (int i = 0; i < cmdArgs.length(); i++) { + classArgs.add(String.class); + stringArgs.add(cmdArgs.getString(i)); + } + + Log.i(TAG, "cmdArgs" + cmdArgs.toString()); + Log.i(TAG, "Parsed classArgs " + classArgs.toString()); + Log.i(TAG, "Parsed stringArgs " + stringArgs.toString()); + + JSONObject msg = new JSONObject(); + msg.put("id", obj.getInt("id")); + msg.put("type", obj.getString("type")); + msg.put("result", "OK"); + + if (className.length()>0) { + mCallback.onSocketEventReceived(TestConstants.SOCKET_EVENT_CALL, className, cmd, classArgs, stringArgs); + } else { + mCallback.onSocketEventReceived(TestConstants.SOCKET_EVENT_CALL, cmd, classArgs, stringArgs); + } + socket.emit("return", msg.toString()); + } else { + if(botMode == TestConstants.REFLECT_MODE ){ + Class[] cArg = new Class[1]; + cArg[0] = String.class; + JSONArray cmdArgs = obj.getJSONArray("args"); + List classArgs = new ArrayList<>(); + List stringArgs = new ArrayList<>(); + for (int i = 0; i < cmdArgs.length(); i++) { + classArgs.add(String.class); + stringArgs.add(cmdArgs.getString(i)); + } + + String cmd = "reflect"; + + JSONObject msg = new JSONObject(); + msg.put("id", obj.getInt("id")); + msg.put("type", obj.getString("type")); + msg.put("result", "OK"); + + Log.i(TAG, "REFLECTING MESSAGE ***********************"); + mCallback.onSocketEventReceived(TestConstants.SOCKET_EVENT_CALL, cmd, classArgs, stringArgs); + socket.emit("return", msg.toString()); + } else { + + Log.i(TAG, "Ignored message id:" + obj.getInt("id")); + } + } + } catch (JSONException e) { + Log.i("EVENT_CONNECT", "JSON error occured"); + } + } + }; + + void queueTask(int taskId) { + Log.i(TAG, "Task added to the queue"); + mWorkerHandler.obtainMessage(taskId) + .sendToTarget(); + } + + void prepareHandler() { + mWorkerHandler = new Handler(getLooper(), new Handler.Callback() { + @Override + public boolean handleMessage(Message msg) { + handleRequest(msg.what); + return true; + } + }); + } + + boolean isThreadAlive(){return isAlive;} + + private void handleRequest(final int taskId) { + switch(taskId){ + case TestConstants.SOCKET_IO_START: + Log.i("SOCKET_EVENT", "STARTING"); + isAlive = true; + try { + IO.Options opts = new IO.Options(); + opts.forceNew = true; + socket = IO.socket(TestConstants.CHAT_SERVER_URL + TestConstants.SOCKET_IO_NAMESPACE, opts ); + socket.connect(); + } catch (URISyntaxException e) { + Log.i("SOCKET ERROR", String.format("URI %s is not valid", TestConstants.CHAT_SERVER_URL)); + } + + socket.on(Socket.EVENT_CONNECT, onConnect); + socket.on(TestConstants.SOCKET_EVENT_STATUS, onStatus); + socket.on(TestConstants.SOCKET_EVENT_RETURN, onReturn); + socket.on(TestConstants.SOCKET_EVENT_CALL, onCall); + socket.on(Socket.EVENT_DISCONNECT, onDisconnect); + socket.on(Socket.EVENT_CONNECT_ERROR, onConnectError); + socket.on(Socket.EVENT_CONNECT_TIMEOUT, onConnectError); + socket.on(Socket.EVENT_CONNECTING, onConnecting); + socket.on(Socket.EVENT_MESSAGE, onMessage); + socket.on(Socket.EVENT_PING, onPing); + socket.on(Socket.EVENT_PONG, onPong); + socket.on(Socket.EVENT_RECONNECT, onReconnect); + socket.on(Socket.EVENT_RECONNECT_ATTEMPT, onReconnectError); + socket.on(Socket.EVENT_RECONNECTING, onReconnecting); + socket.on(Socket.EVENT_RECONNECT_ERROR,onReconnectError); + socket.on(Socket.EVENT_RECONNECT_FAILED, onReconnectError); + socket.on(Socket.EVENT_RECONNECT_ATTEMPT, onReconnect); + + break; + case TestConstants.DEFAULT_MODE: + Log.i(TAG, "DEFAULT bot mode now set"); + botMode = TestConstants.DEFAULT_MODE; + + break; + case TestConstants.REFLECT_MODE: + botMode = TestConstants.REFLECT_MODE; + Log.i(TAG, "REFLECT bot mode now set"); + + break; + case TestConstants.SOCKET_IO_STOP: + socket.disconnect(); + isAlive= false; + isConnected=false; + socket.off(Socket.EVENT_CONNECT, onConnect); + socket.off(TestConstants.SOCKET_EVENT_STATUS, onStatus); + socket.off(TestConstants.SOCKET_EVENT_RETURN, onReturn); + socket.off(TestConstants.SOCKET_EVENT_CALL, onCall); + socket.off(Socket.EVENT_DISCONNECT, onDisconnect); + socket.off(Socket.EVENT_CONNECT_ERROR, onConnectError); + socket.off(Socket.EVENT_CONNECT_TIMEOUT, onConnectError); + socket.off(Socket.EVENT_CONNECTING, onConnecting); + socket.off(Socket.EVENT_MESSAGE, onMessage); + socket.off(Socket.EVENT_PING, onPing); + socket.off(Socket.EVENT_PONG, onPong); + socket.off(Socket.EVENT_RECONNECT, onReconnect); + socket.off(Socket.EVENT_RECONNECT_ATTEMPT, onReconnectError); + socket.off(Socket.EVENT_RECONNECTING, onReconnecting); + socket.off(Socket.EVENT_RECONNECT_ERROR,onReconnectError); + socket.off(Socket.EVENT_RECONNECT_FAILED, onReconnectError); + socket.off(Socket.EVENT_RECONNECT_ATTEMPT, onReconnect); + break; + default: + break; + } + } + + private String d(String msg, Object... args){ + StringBuilder sb = new StringBuilder(); + sb.append(msg); + sb.append("\n"); + sb.append(args.getClass().getName()); + sb.append("-"); + sb.append(args); + sb.append("\n"); + for(int i=0; iTesting documentation - */ -public class ExampleUnitTest { - @Test - public void addition_isCorrect() { - assertEquals(4, 2 + 2); - } -} \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index 4a1a7e1..ee2b786 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -37,15 +37,15 @@ dependencies { // Core library - androidTestImplementation 'androidx.test:core:1.0.0' + androidTestImplementation 'androidx.test:core:1.2.0' // AndroidJUnitRunner and JUnit Rules androidTestImplementation 'androidx.test:runner:1.2.0' androidTestImplementation 'androidx.test:rules:1.2.0' // Assertions - androidTestImplementation 'androidx.test.ext:junit:1.0.0' - androidTestImplementation 'androidx.test.ext:truth:1.0.0' + androidTestImplementation 'androidx.test.ext:junit:1.1.1' + androidTestImplementation 'androidx.test.ext:truth:1.2.0' androidTestImplementation 'com.google.truth:truth:0.42' androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 2da82d2..76589a1 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,27 +1,3 @@ - - - - - - - - - - - - - - \ No newline at end of file + package="com.moquality.android"/>