Showing posts with label Android. Show all posts
Showing posts with label Android. Show all posts

Thursday, May 24, 2012

Android get Wifi Router Name


If you ever wonder how to get the WiFi Router name your Android device is connected to, this is what you are required:

WifiManager wifiMgr = (WifiManager) getActivity().getSystemService(Context.WIFI_SERVICE);
WifiInfo wifiInfo = wifiMgr.getConnectionInfo
();
String name = wifiInfo.getSSID
();

Also add the following permissions to your manifest:

    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />

Have fun.

Friday, April 20, 2012

The Journey to Android Automation

Guys,

This was ANNOYING... I spent 4 hours trying to configure this damn Robotium project to work with my Cyborg framework... damn this was tough, and I'm not done yet.

Some conclusions...

  1. You cannot add same apklib as a dependency to both the tester project and target project!
  2. If you have a dependency you would like in both the tester project, and the  target project, be sure, that the tester project depends on the  target project/jar (Not apklib!!!), and while at it, make sure the dependency is been imported by the  target project.
  3. Sometimes while trying to debug the tester project, I placed some breakpoints, at the test case constructor, and the running halt, but without any stacktrace, as if the debugger failed to connect to the UI, although the program halt at the break point, Solution: restart Eclipse.
  4. The tests are not been mapped for execution, Solution: These guys are not serious, in order to define a test, the name of the method must start with the word 'test' explicitly, oh yeah, and an annotation to define some properties for the test... you see because an annotation cannot be extended, and because there are different implementation for a 'Test' then the commonality they have came up with is to start the name of the frigging method with the word 'test', here is an alternative Another annotation called @Test, it would be a revolution.
  5. The class loading process is different. In any Android app developed, the classes of the project itself, and the classes of its dependencies, are loaded lazily, while the Robotium app, loads all the project, and its dependencies classes at launch.
After long 10 hours of work I finally managed to debug the automation project, and hit a break point... from now on the process should be as simple as developing anything else.

Update:


My Cyborg framework, now fully supports scenario recording, and scenario automation executor, together giving me an invaluable power to turn my app users, together with a crash report server, to be my private QA, lets say that this should cover 80% of the cases...

Thursday, April 19, 2012

Android and Singleton pattern

In the past year or so, I've been assigned to work on multiple pretty complex Android projects. Also in the past year, I have developed my own Android projects, while learning how to avoid Android development issues, here are some of the conclusions.

Due to fact that Android does not allow direct communication between activities/broadcast receivers/services, which I would refer, as Model Access Limitation (MAL), causes most development teams, to develop their application, based mainly on the Singleton pattern. There is no discussion as for how to declare these Singletons or if one declaration way is better then another, I just think this design pattern is used widely, in the most ignorant way possible and I'll elaborate.

What is a Singleton? 
  • Some developer say that a Singleton is a class type, that would have a single instance of that class type existing in the entire JVM. (This is somewhat reasonable)
    To achieve this developers have too many ways and theories on how to instantiate a Singleton: Enum, LazyLoader, double sync, and other.
  • Some developer ask it to be impenetrable, that anything anyone would do, would not, by any chance create two instances of the same Singleton type. 
  • In the case of Android, developers (without saying it out laud) turn to the Singleton to overcome the Android MAL, because a Singleton provides access to data from everywhere in the code  (NOT necessarily a good thing!) , and gives the impression that it is encapsulated. (It is!) 
Let me picture it for you: 

If someone would like to abuse the Singleton he can, and the fact that something can be accessed from everywhere in the code, together with Android MAL, can turn into something really really ugly, and believe me I've seen more then one, very UGLY "Singleton"s!

Which now brings us to the actual fact, it is not that we would want to have a single instance in the JVM, but more of a:

"Single instance that would be shared and used by other objects and threads in the JVM"

After assimilating this(hopefully you have), and taking the previous facts about Singletons into consideration, I've concluded:
(mean while it is my own conclusion which serves me so very well for the past years)

 "ONLY Real* Singletons will be used as Singletons in an Android projects!!"

I say Real, because there are developers, that don't understand when something is really a Singleton, and when another pattern is available and even preferable, they turn to the quickest, dirtiest, most comfortable way they find first, to overcome a Android MAL, and what can you do, Singleton just pops first to mind.

I've developed an entire framework 'Cyborg', based on this, it is not that I don't use Singletons, I have very few REAL Singletons, but I have many Single(Instance)ton, which are managed by a single-instance manager, that can only be instantiated once as well. (unless of course you would like to abuse it)

Once I've develop the infrastructure to manage Single-Instances, I've learned and adopted a very nice rule while developing, and using 3rd party libraries:

Don't abuse it... Use it!

To conclude, I think the Singleton pattern is an amazing one, but you don't eat with a shovel, nor dig with a spoon...



Friday, April 13, 2012

Stupid Bluetooth behavior of Android phones

It seem that every post I type I feel the need to start it with Google, Google, Google... what am I going to do with you... It is just because I think you are doing good, but not good enough...

I've developed a Bluetooth packet protocol, any application that wants to be used on a wide enough variety of devices, and use Bluetooth on an Android platform, should take the following under consideration:

  • First, my stack overflow question... about an IllegalMonitorStateException, This one was a bugger, I've added a toast to the user to restart the phone.
  • Second, to my surprise there are different buffering implementations, for example, on the same LG phone, in the first few packets arriving, the first ~150 bytes are ZEROs, no data... probably due to the fact that a connection has just been established.
    This problem happens when calling one of the read(...) methods which does NOT block, surprisingly calling repeatably to read(), which DOES block, would result in the correct buffer. 
  • Here is yet another problem I'm facing, hopefully this will be solvable...
  • 08/06/2012, I've encountered another issue with the Bluetooth socket on an other Android device, once the Bluetooth connection is terminated.
    On some devices the next byte read() from the stream returns -1, instead of throwing a IOException.

Will keep it coming as I go...

Sites with good invaluable information:
android-bluetooth-api-connect-to-multiple-devices
connecting-to-a-already-paired-bluetooth-device
this
this
this

Tuesday, March 27, 2012

ADT 17 Build issues

Google, Google, Google... What should I do with you??? Don't you ever think a bit outside the scope, or is this some sort of a grand master plan to muscle us all to work your way?

I can tell you right now that your main issue regarding the build process of the Android projects in Eclipse is due to the fact that there are two class paths for the same project, one is the Android libs, the other is the IDE classpath. I think that as programmers you should know this is BAD PRACTICE!!!

I came across two major issues, the NoClassDefFoundException, and the terribly annoying jarlist.cache file.
  1. In the previous versions of the ADT the dependent jars where automatically exported, which caused the dalvik issues in the build, with the "duplicate files" error. So their solution was to cancel the export completely and leave the export management to Eclipse, which is a step in the right direction... Personally, I would simply resolve the dependencies and not import a duplicate dependency.

    SO, if you experience the NoClassDefFoundException, it is because your .class files from the project dependencies were not imported to the output folder of the final Android application project.

    Maven, Eclipse, Ant could all have a different dependencies resolution, and different hierarchy structure, plus your own projects hierarchy, brings one to the conclusion that to understand the export  dependencies  feature, you must know what the hell you are doing, and understand projects hierarchy, your build tool, and how it resolves its project's dependencies.

    In general it is always best to enable the export on project D, but there are some cases exporting on project A would be preferred. For example:

    I.  Projects A, B, C, uses an XML library, where would you export the library? See Answer 1.
    II. Project A is a framework module project that projects B, C uses? See Answer 2.

    Nuts ha? but there is no other way to be 100% sure you would not export the same .class file twice!
    It may be that Eclipse resolves this issues, but I've started to trust no one! I have an intermittent Android lib project with most of the dependencies, which are also marked as export, and the final project depend on it.

    To enable export for dependencies you go to:  Project -> Right Click -> Properties -> Java Build Path -> Export tab -> check the project you want to export.

    And as for this solution, I think it is a bad idea... very bad idea, why? just because, I don't feel like writing another 100 rows about how you should work, this is my advice, you can take it or leave it :)
  2. The second most annoying issue with ADT 17, is the jarlist.cache file. Brilliantly the ADT team decided they need some file for god knows whatever reason, (it is really beside the point)  and they have decided to place that file hard coded at ${ ProjectFolder}/bin/jarlist.cache, and because some of us Eclipse users, are used to the fact that the default output folder is ${ ProjectFolder}/bin/, we experience this issue. Problem is that this build action does not effect only Android projects builds, but also pure Java projects, which eventually causes the mess. Android default output folder is at ${ProjectFolder}/bin/classes, you can verify it, go to your android project .classpath file and change the output to "/blah/blah" and see what happens once you clean build... like magic the adt nature returns it to "/bin/classes".

    The solution for this mess: point the output of all your projects in the workspace to "/bin/classes", this solves it!

If you encounter more issues with the ADT 17 let me know, I'll add reference to it.

In general, it is a step in the right direction, but this is annoying as hell, they change the freaking project management every version... can't they formulate something stable? I really hope the next version would be better, I mean they turn 18 soon... ;)


Answer 1: Project D
Answer 2: The dependencies whom are unique to Project A, would be exported. common used dependencies would not, they would be exported in Project D!!    << == (This is the conclusion!!)

Thursday, March 15, 2012

Notify User about an Upgrade version at Android Play-Store

Well, here is my impression of an upgrade feature for my Android wrapping framework (Cyborg):

It compares the versionCode of the Play-Store apk, and if the version string starts with the letter 'F', the PlayStoreModule would invoke an upgrade dialog, and would open the Play-Store, in the proper application.

I think that all the pieces are here, except for the application id, this one I got by calling on the getPlayStoreAppDetails_Async(activity, true), I've received 10 applications details printed to the log, and one of them was mine, I took the app id and used it hard coded in the top layer application.

I use the Android market api.

Underwent a bit of refactoring at: 24-03-2012
package com.nu.art.software.android.modules.market;


import java.io.IOException;
import java.util.List;

import android.accounts.Account;
import android.accounts.AccountManager;
import android.accounts.AccountManagerCallback;
import android.accounts.AccountManagerFuture;
import android.accounts.AuthenticatorException;
import android.accounts.OperationCanceledException;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;

import com.gc.android.market.api.MarketSession;
import com.gc.android.market.api.MarketSession.Callback;
import com.gc.android.market.api.model.Market.App;
import com.gc.android.market.api.model.Market.AppsRequest;
import com.gc.android.market.api.model.Market.AppsResponse;
import com.gc.android.market.api.model.Market.ResponseContext;
import com.nu.art.software.android.core.AndroidModule;
import com.nu.art.software.android.dialogs.ForceActionDialog;
import com.nu.art.software.android.modules.configuration.ApplicationConfiguration;
import com.nu.art.software.android.utils.IntentFactory;
import com.nu.art.software.android.wrapper.R;


public final class PlayStoreModule
    extends AndroidModule {

  protected static final String GoogleAccountToken_Key = "A Google Account Token";

  public static final String ApplicationPlaySotreId_Key = "Application Play Store ID";

  private final Object TokenMonitor = new Object();

  private MarketSession session;

  private String sessionToken;

  private Account googleAccount;

  private AccountManager accountManager;

  private ApplicationConfiguration configuration;

  private String deviceId;

  private String playStoreAppId;

  private Thread applicationDetailsThread;

  @Override
  protected void init() {
    deviceId = getGtalkAndroidId(getApplication());
    configuration = getModuleOrThrowException(ApplicationConfiguration.class);
    playStoreAppId = configuration.getValue(false, ApplicationPlaySotreId_Key, null);
    if (playStoreAppId == null)
      throw new IllegalStateException("Must add your Play-Store application id to the configuration with key: "
          + ApplicationPlaySotreId_Key);
    sessionToken = configuration.getValue(true, GoogleAccountToken_Key, null);
    logDebug("Loaded Google AuthToken: " + sessionToken);
    accountManager = AccountManager.get(getApplication().getApplicationContext());
    Thread googlePlayStoreAPI_LoadingThread = new Thread(new Runnable() {

      @Override
      public void run() {
        synchronized (PlayStoreModule.this) {
          session = new MarketSession();
          session.getContext().setAndroidId(deviceId);
          logDebug("Market Session - Initialized ");
        }
      }
    }"Google Play-Store API Initializer");
    googlePlayStoreAPI_LoadingThread.start();
  }

  private static final Uri URI = Uri.parse("content://com.google.android.gsf.gservices");

  private static final String ID_KEY = "android_id";

  public static String getGtalkAndroidId(Context ctx) {
    String params[] {ID_KEY};
    Cursor c = ctx.getContentResolver().query(URI, null, null, params, null);
    if (!c.moveToFirst() || c.getColumnCount() 2)
      return null;
    try {
      return Long.toHexString(Long.parseLong(c.getString(1)));
    catch (NumberFormatException e) {
      return null;
    }
  }

  private boolean setGoogleAccountToken(final Activity activity) {
    logDebug("Getting new AuthToken");
    Account[] accounts = accountManager.getAccountsByType("com.google");
    if (accounts.length == 0) {
      sessionToken = null;
      return false;
    }
    googleAccount = accounts[0];

    AccountManagerCallback<Bundle> callBack = new AccountManagerCallback<Bundle>() {

      @Override
      public void run(AccountManagerFuture<Bundle> accountManagerFuture) {
        Bundle bundle;
        try {
          bundle = accountManagerFuture.getResult();
          sessionToken = bundle.getString(AccountManager.KEY_AUTHTOKEN);
          logDebug("Google New AuthToken: " + sessionToken);
          configuration.putValue(true, GoogleAccountToken_Key, sessionToken);
          synchronized (TokenMonitor) {
            TokenMonitor.notify();
          }
        catch (OperationCanceledException e) {
          logError(e);
        catch (AuthenticatorException e) {
          accountManager.invalidateAuthToken("com.google", sessionToken);
        catch (IOException e) {
          logError(e);
        }
      }

    };
    accountManager.getAuthToken(googleAccount, "android", null, activity, callBack, null);
    return true;
  }

  public final void checkIfForceUpdateIsInOrder(final Activity activity) {
    checkIfForceUpdateIsInOrder(activity, false);
  }

  public final void checkIfForceUpdateIsInOrder(final Activity activity, final boolean list) {
    if (applicationDetailsThread != null)
      return;
    applicationDetailsThread = new Thread(new Runnable() {

      @Override
      public void run() {
        process();
        applicationDetailsThread = null;

      }

      private void process() {
        boolean waitForToken = false;
        // if (sessionToken == null)
        waitForToken = setGoogleAccountToken(activity);

        if (waitForToken)
          synchronized (TokenMonitor) {
            try {
              logDebug("Waiting for google account token");
              TokenMonitor.wait(20000);
            catch (InterruptedException e) {
              logError("Erroe while waiting for Account token", e);
              return;
            }
          }
        // Launch race condition workaround
        if (sessionToken == null)
          return;
        logDebug("Get Application Details");
        synchronized (PlayStoreModule.this) {
          getApplicationDetails(activity, list);
        }
      }

    }"Play-Store Application Details fetcher workaround Thread");
    applicationDetailsThread.start();
  }

  private final void getApplicationDetails(final Activity activity, boolean list) {
    session.setAuthSubToken(sessionToken);

    com.gc.android.market.api.model.Market.AppsRequest.Builder builder = AppsRequest.newBuilder();
    builder.setStartIndex(0);
    if (list) {
      builder.setQuery(getApplication().getName());
      builder.setEntriesCount(10);
    else {
      builder.setAppId(playStoreAppId);
      builder.setEntriesCount(1);
    }
    // builder.setWithExtendedInfo(true);
    logDebug("AppsRequest.Builder: " + builder);

    AppsRequest appsRequest = builder.build();
    session.append(appsRequest, new Callback<AppsResponse>() {

      @Override
      public void onResult(ResponseContext context, AppsResponse response) {
        List<App> apps = response.getAppList();
        logDebug("ResponseContext: " + context);
        logDebug("AppsResponse: " + response);
        App app;
        if (apps.size() != 1)
          return;

        app = apps.get(0);
        int latestVersionCodeFound = app.getVersionCode();
        String latestVersionFound = app.getVersion();
        if (latestVersionCodeFound <= getApplication().getVersionCode()) {
          logDebug("No upgrade in market.");
          return;
        }
        if (!latestVersionFound.startsWith("F")) {
          logDebug("Newer (NOT MANDATORY) version is now available in Google play store (v:" + latestVersionFound + ", vc:" + latestVersionCodeFound + ").");
          return;
        }
        logDebug("Newer (MANDATORY) version is now available in Google play store (v:" + latestVersionFound + ", vc:" + latestVersionCodeFound + ").");
        final ForceActionDialog dialog = getUpgradeDialog(activity);
        String body = dialog.getBody();
        body = body.replace("${version}", latestVersionFound);
        dialog.setBody(body);
        dialog.setOnClickListener(new OnClickListener() {

          @Override
          public void onClick(View arg0) {
            Thread thread = new Thread(new Runnable() {

              @Override
              public void run() {
                dialog.dismiss();
                Intent intent = IntentFactory.openMarketApplicationDetails(activity.getApplicationContext().getPackageName());
                activity.startActivity(intent);
              }
            }"Upgrade APK Installer");
            thread.start();
          }
        });
        dialog.showDialog();
      }
    });
    try {
      session.flush();
    catch (Exception e) {
      logError(e);
      sessionToken = null;
      toast("Error checking for Upgrade", LongToast);
    }
  }

  protected ForceActionDialog getUpgradeDialog(final Activity activity) {
    final ForceActionDialog dialog = new ForceActionDialog(activity, R.string.UpgradeRequired, R.string.NeedToUpgradeApplicationMessage,
        R.string.Upgrade);
    dialog.setCancelableFlag(false);
    return dialog;
  }
}
Java2html






Sunday, December 4, 2011

Android AsyncTask great idea BAD implementation!

I got so tired of Memory leaks in my android application which was caused mainly by the AsyncTask object of the Android framework, so I've implemented my own as part of my "Cyborg" project, this works for me...

I can use the same instance of the task over and over and over, and to publish to the UI on a UI thread...

Shame on you Android and shame on you Google... :(

No warranty... use at your own risk!!!

I've updated this on the 16-03-2012, previous version had a design flaw.



package com.nu.art.software.android.core;

import java.lang.ref.WeakReference;

import android.os.Handler;
import android.os.Message;

import com.nu.art.software.android.log.AndroidLogImpl;
import com.nu.art.software.android.log.Logger;

public abstract class AsyncTaskModel<Model, Progress, Result>
  extends Handler
  implements Logger {


 @Override
 @SuppressWarnings("unchecked")
 public void handleMessage(Message msg) {
  switch (msg.what) {
   case ProgressUpdate :
    onProgressUpdate((Progressmsg.obj);
    break;
   case ExecutionCompleted :
    onExecuteCompleted((Resultmsg.obj);
    break;
   case ExecutionCancelled :
    onExecutionCancelled((Resultmsg.obj);
    break;
   case Dispose :
    model = null;
    threadReference = null;
    break;
  }
  super.handleMessage(msg);
 }

 private static final int ProgressUpdate = 1;

 private static final int ExecutionCompleted = 2;

 private static final int ExecutionCancelled = 3;

 protected static final int Dispose = 4;

 protected Model model;

 private final String name;

 private WeakReference<Thread> threadReference;

 private volatile boolean cancelled;

 protected AsyncTaskModel(String name) {
  super();
  this.name = name;
 }

 public final boolean isRunning() {
  return threadReference != null;
 }

 public final void execute(Model model) {
  if (isRunning())
   throw new TaskInProcessException("Task is already running");
  this.model = model;
  cancelled = false;
  onPreExecute();
  Runnable r = new Runnable() {

   @Override
   public void run() {
    Result result = doInBackgroundImpl();
    Message message;
    if (cancelled)
     message = obtainMessage(ExecutionCancelled);
    else
     message = obtainMessage(ExecutionCompleted);
    message.obj = result;
    message.sendToTarget();
    message = obtainMessage(Dispose);
    message.sendToTarget();
   }
  };
  Thread thread = new Thread(r, name);
  thread.start();
  threadReference = new WeakReference<Thread>(thread);
 }

 protected abstract Result doInBackgroundImpl();

 protected abstract void onProgressUpdate(Progress progress);

 public final void cancel() {
  cancelled = true;
  cancelImpl();
  if (threadReference != null)
   threadReference.get().interrupt();
 }

 @SuppressWarnings("unused")
 protected void onExecutionCancelled(Result result) {}

 protected void cancelImpl() {}

 @SuppressWarnings("unused")
 protected void onExecuteCompleted(Result result) {}

 protected void onPreExecute() {}

 public void publishProgress(Progress progress) {
  if (cancelled)
   return;
  Message message = obtainMessage(ProgressUpdate);
  message.obj = progress;
  message.sendToTarget();
 }

 public boolean wasCancelled() {
  return cancelled;
 }

 @Override
 public void logDebug(String debug) {
  AndroidLogImpl.LogImpl.logDebug(debug);
 }

 @Override
 public void logError(String error) {
  AndroidLogImpl.LogImpl.logError(error);
 }

 @Override
 public void logError(String error, Throwable e) {
  AndroidLogImpl.LogImpl.logError(error, e);
 }

 @Override
 public void logError(Throwable e) {
  AndroidLogImpl.LogImpl.logError(e);
 }

 @Override
 public void logInfo(String info) {
  AndroidLogImpl.LogImpl.logInfo(info);
 }

 @Override
 public void logVerbose(String verbose) {
  AndroidLogImpl.LogImpl.logVerbose(verbose);
 }


 @Override
 public void logWarning(String warning) {
  AndroidLogImpl.LogImpl.logWarning(warning);
 }

 @Override
 public void logWarning(String warning, Throwable e) {
  AndroidLogImpl.LogImpl.logWarning(warning, e);
 }

}
Java2html