Category Archives: Android

Disable Android Back Button in InAppBrowser

21 Apr 2014

This is a quick hit – if you’re using the InAppBrowser plugin in your PhoneGap app on Android you may want the child browser to display without the location bar and the included “close” button. The PhoneGap docs explain how to do this well enough, however Android’s back button is still functional. If you tap it enough you will eventually shutdown the child web view which may not be what you wanted to happen.

You could try to over-ride the default behavior by creating an event listener for the backbutton event but once InAppBrowser launches it handles the back button on its own. The backbutton event won’t fire until **after** the InAppBrowser child view has closed which of course is too late for our purposes.

An easy way to solve this is to just comment out the InAppBrowser function that handles the backbutton behavior. Locate InAppBrowser.java in your project and search for a function called closeDialog. Once you locate it just wrap a c-style comment around the body of the function and save.

If in addition to preventing the web view from shutting down you also want to have the back button function like a web browser’s back button then you need to be a little more precise with your edit. As before, locate the closeDialog. It should look like the code sample below – note the changes:

  • Line 16: Comment out this line
  • Line 23: Comment out this line
  • Line 24: add the following: childView.goBack()
    public void closeDialog() {

        final WebView childView = this.inAppWebView;
        // The JS protects against multiple calls, so this should happen only when
        // closeDialog() is called by other native code.
        if (childView == null) {
            return;
        }
        this.cordova.getActivity().runOnUiThread(new Runnable() {
            @Override
            public void run() {
                childView.setWebViewClient(new WebViewClient() {
                    // NB: wait for about:blank before dismissing
                    public void onPageFinished(WebView view, String url) {
                if (dialog != null) {
                    //dialog.dismiss(); // COMMENT OUT THIS LINE
                }
            }
        });
                // NB: From SDK 19: "If you call methods on WebView from any thread 
                // other than your app's UI thread, it can cause unexpected results."
                // http://developer.android.com/guide/webapps/migrating.html#Threads
                //childView.loadUrl("about:blank"); // <<< COMMENT OUT THIS LINE
	        childView.goBack(); // <<< ADD THIS LINE
            }
        });

        try {
            JSONObject obj = new JSONObject();
            obj.put("type", EXIT_EVENT);
            sendUpdate(obj, false);
        } catch (JSONException ex) {
            Log.d(LOG_TAG, "Should never happen");
        }/**/
    }

This worked for me on my HTC One M8 - but, I'm not a Java Developer so please look at the above with a critical eye. Let me know in the comment section below if you have changes to whats here!

Android 4.1.2/PhoneGap 3 AdjustPan with Fullscreen Theme

17 Mar 2014

If you’re reading this you’ve been stung by the AdjustPan bug in PhoneGap – this is where the softkeyboard appears when one of your text fields gets focus but the view does not shift up to reveal the field that you’re attempting to type into. Setting the windowSoftInputMode in your application manifest to “adjustPan” should in theory accomplish this. Apparently no amount of XML / main activity settings will create the desired result so the average PhoneGap dev – one who is not a Java Developer – has to rely on whatever work-around the community decides to share.

Go here to read about the expected behavior of windowSoftInputMode’s “AdjustPan” setting. You may also want to read about the bug. Various posts I have read say that this bug seems to manifest itself because PhoneGap is running fullscreen (using a Fullscreen Theme).

To be honest, I *have* seen this work in an older project so it must be something that was introduced to Android after my last viewing of it.

In any event the community at large has provided a fix – after some Googling I came across this StackOverflow post:

http://stackoverflow.com/questions/19849462/phonegap-android-how-to-adjust-layout-in-full-screen-mode-when-softkeyboard-is

The above gives enough directions to get started though some small points are left out. The missing step-by-step is why I’m posting today – I’m going to walk through the entire process and to touch briefly on the variant of the fix also posted in the above link.

app_adjust_pan_compressed

The “AndroidBug5497Workaround” & “AdjustInputHeight” Java Classes

“AndroidBug5497Workaround” is the name of the class that defines the first fix. It works as advertised and in fact performs better than the variation of it on my Samsung Galaxy S2 (Android 4.1.2). This fix will bump up the display immediately with no delay or animation. The view stays shifted upwards revealing the element with focus until the keyboard is put away where the view then returns to normal.

The second example, a variation of the first, animates the view up but has a bug that shifts the view down to its default position after the first keystroke. Thereafter, it works fine. Again, I found the first example to be more reliable and in fact preferable to animating the view.

If you’re curious you can implement the second one and play with the animation time setting – look for the number “500” in the source. I assume it represents milliseconds.

How To Implement

Navigate to the root of your project

Locate your “src” folder. In a PhoneGap 3 Project it would be here (substitute the bracketed text for your own project-specific values):

  • projects\[APP NAME]\platforms\android\src\com\[NAMESPACE]\[APP NAME]

Or in Eclipse you might find the “src” folder in the Package Explorer.

Once you locate your “src” folder you should be looking at your main activity java file – we’ll be giving it a friend…

Create a new text file to hold the fix

Create a new text file. Then, go to the link given above and copy the source for the first AndroidBug5497Workaround example and paste it into your new text file – save and close.

Rename the file

Next, rename the file so that it matches the class that it holds and give it the java extension. It should look like this:

  • AndroidBug5497Workaround.java
Open your Main Activity

With a text editor open your main activity Java file – it was the first file you saw in your “src” folder and probably is named after the name of your app. Look at the top of the file – you are looking for line a that looks like this:

  • package com.[NAMESPACE].[APP Name];

Its the only line that starts with the word “package” – copy that entire line and paste it at the very top of the other file – the one you’ve just created.

Add the fix to your main activity

Go back to your main activity Java file – you will need to paste this line after the super.loadUrl line:

  • AndroidBug5497Workaround.assistActivity(this);

It should look like this when you’re done:

adjustPanFix

Thats it – If in Eclipse clean your project and try it.

If you want to try the other example (again at the StackOverflow link given at the top of this page) then just follow the same procedure but be sure to use its class name (AdjustInputHeight) as the name of the file and when adding to your main activity.

Accessing External Storage in Android & PhoneGap 3.3

02 Jan 2014

While playing with PhoneGap’s filesystem api I noticed that window.requestFileSystem() would only give me a path to the on-device file storage area (sdcard0) but not allow me to gain access to the external sd card (extSdCard).

[edit 11/29/2014] Please review this article: Browsing Filesystems in PhoneGap as it has details about the setup used to accomplish the subject of this article.

The following illustrates the above:

window.requestFileSystem(LocalFileSystem.PERSISTENT, 0, fsSuccess, fsFail);

function fsSuccess(fs){
  console.log(fs);
  // the above prints this to the console:
  // {"name":"persistent",
  //  "root:{"isFile":false,
  //     "isDirectory":true,
  //     "name":"sdcard0",
  //     "fullPath":"file:///storage/sdcard0",
  //     "filesystem":null
  //  }
  // }
}

As you can see simply getting the filesystem returns the location at /storage/sdcard0 which is a couple of levels below the root. You would think that its not a big deal because you could use the getParent() method to go higher up the directory chain. In practice however getParent() on file:///storage/sdcard0 returns the same location.

What you can do then is change the value of the fileSystem’s “fullPath” property to the desired path and thus get the location one directory level above sdcard0 without using getParent(). This one edit can be used with a directoryReader() to reveal the external sd card.

window.requestFileSystem(LocalFileSystem.PERSISTENT, 0, fsSuccess, fail);

function fsSuccess(fs){
  if (fs.root.fullPath === 'file:///storage/sdcard0'){
    fs.root.fullPath = 'file:///storage'; // change the path
  }
  // create directory reader
  var directoryReader = fs.root.createReader()
  // get a list of all entries in the directory
  directoryReader.readEntries(dirSuccess,fail);
}

function dirSuccess(entries){
  console.log(entries);
  // will print something like the following to the console
  // [{"isFile":false,"isDirectory":true,"name":"extSdCard",
  //    "fullPath":"file:///storage/extSdCard","filesystem":null},
  //  {"isFile":false,"isDirectory":true,"name":"sdcard0",
  //    "fullPath":"file:///storage/sdcard0","filesystem":null}
  // ]
}

And that’s all there is to it – from there you have an entry for the internal storage area and and the external sdcard storage to do with as your app requires.

By modifying the fullPath you can also get all the way to file:/// if you so desired though at that point you’d need to be careful what you enable your users to do.

As a side note I have noticed that the path to file://storage is reported as having a trailing slash contrary to other paths.

Browsing Filesystems in PhoneGap

24 Dec 2013

I recently came up with an idea for an app that required the ability to browse the device’s filesystem. The first thing that I did was review the PhoneGap API and sure enough everything that I needed was there. So, after an afternoon of tinkering I have my own HTML 5 File Browser – browsing-only at this early stage but it wouldn’t be too much extra effort to be able to delete and move files around.

There is no framework in use here – as you’ll see its just basic JavaScript. Performance is very good with fluid stutter-free scrolling. Even with inserting hundreds of files into my view webkit’s native scrolling function doesn’t skip a beat.

[edit 9/13/2015] Updated this edit with instructions for installing the plugin and verified that the app would build against a target android version of 4.1.1 (API level 16) using PhoneGap 5.2.1 with the file plugin version 0.2.4. Tested on an HTC One M8 Android 5.0.2 and a Dell Venue 7840 running Android 5.1 (and as mentioned in the original text found below, a Galaxy S2/Android 4.0.1).

Here’s what you’re probably looking for: Download the project.

Here’s a screen cap of the app running in the aforementioned HTC One M8 via Chrome’s dev tools – again, PG 5.2.1, Android API level 16, File plugin version 0.2.4:

*** This was originally built when PG 1.9 was the current version and 0.2.4 was current for the file plugin. BUT, people still ask me for the code, so I’m providing it here and showing how to install the old version of the file plugin so you can run it in PG 5.2.1. You should really use the current file plugin but… here you go… ***

To add Android 4.1.1 to your PG project:

phonegap platform add android@4.1.1

To install file plugin version 0.2.4:

  1. Download the 0.2.4 snapshot to your computer (View the plugin’s summary here)
  2. Extract the files
  3. Install the plugin locally via:
    $ phonegap plugin add PATH_TO_PLUGIN

    Where the text “PATH_TO_PLUGIN” is the path to the folder containing the plugin’s “plugin.xml” file. For example, on my computer the complete command was:

    phonegap plugin add c:\projects\file_plugin_old\cordova-plugin-file-8a29d64

(And now, back to the original post….)

Anyway, I’ve been tinkering with this on my Galaxy SII, here are some screen shots:

phonegap_filebrowser_sgs2
phonegap_file_browser_2
phonegap_file_browser_3

As you can see its quite light in the UI department. I googled some icons, dropped them in, then used a little flexbox action to align things and finally styled the unordered list to my liking. No ground-breaking design here, just function over form.

One thing to note about the process described below is that everything is asynchronous. Therefore, there are a number of callbacks for success / failure and the result is a chain of functions. Not too messy though, just something to be aware of.

Ok then…. lets peek under the hood.

Step 1: Request File System

The foundation of all of this is being able to get a reference to the device’s file system.

PhoneGap’s window.requestFileSystem() returns a file system object with this structure:

// This example contains real values relative to my personal phone
{
  name:'persistent',
  root:{
    isFile:false,
    isDirectory:true,
    name:'sdcard0',
    fullPath:'file:///storage/sdcard0',
    filesystem:null
  }
}

Here’s the function that gives us the above:

...
    function beginBrowseForFiles(e){
        // check subscription type
        if (e.target.attributes['data-action'].nodeValue != 'beginBrowseForFiles'){
            return;
        }
		
        if (!e.target.attributes['data-path']){
            window.requestFileSystem(LocalFileSystem.PERSISTENT, 0, requestFileSystemSuccess, null);
        } else {
            // this is used to get subdirectories
            var path = e.target.attributes['data-path'].nodeValue;
            window.resolveLocalFileSystemURI(path, 
                function(filesystem){
		    // we must pass what the PhoneGap API doc examples call an "entry" to the reader
		    // which appears to take the form constructed below.
		    requestFileSystemSuccess({root:filesystem});
		},
		function(err){
		    // Eclipse doesn't let you inspect objects like Chrome does, thus the stringify
		    console.log('### ERR: filesystem.beginBrowseForFiles() -' + (JSON.stringify(err)));
		}
	    );
        }
    }
...

Getting the local filesystem object is easy enough, to understand what happens next just follow the success callback which is explained in Step 2. The interesting thing above is that I re-use this function to drill down into sub directories (note my comments) and that I’m constructing an object that I pass to the success callback in that case – I’m sure there is something that I’ve missed in the API docs, maybe something that does this for me, but I’ve figured out that the next step (creating a reader) needs to have an object that takes the form constructed in the example.

In any event you can see that the success callback for both the first pass and drilling into subdirectories is the same – requestFileSystemSuccess

Step 2: Directory Reader

The requestFileSystemSuccess callback now takes the filesystem object and uses it to create a reader which allows us to get all the entries (files and folders) for the given location. On success we will pass our entry array to a function that will sort them, construct an unordered list and then insert them into the app.

...
  function requestFileSystemSuccess(fileSystem){
    // lets insert the current path into our UI
    document.getElementById('folderName').innerHTML = fileSystem.root.fullPath;
    // save this location for future use
    _currentFileSystem = fileSystem;
    // create a directory reader
    var directoryReader = fileSystem.root.createReader();
    // Get a list of all the entries in the directory
    directoryReader.readEntries(directoryReaderSuccess,fail);
  }
...

Step 3: Compile Entries into something useful

The last thing we want to do is order the entry list. It appears to me to have a random order though maybe it is ordered by creation date – I’ve honestly not spent the time to investigate.

After making sense of things I then loop through the entry array and create my list from each entry object.

While doing that I investigate each entry object to determine if I’m dealing with a file or directory and thus insert the appropriate icon. Entry objects take the following form:

// a sample entry object
{
  isFile:false,
  isDirectory:true,
  name:'backups',
  fullPath:'file:///storage/sdcard0',
  filesystem:null
}

I also am filtering out system/hidden files/folders as determined by anything that has a leading period in its name. As you look at the code sample you will see that I have implemented the ability to toggle this on or off based on user preferences.

...
  function directoryReaderSuccess(entries){
    // again, Eclipse doesn't allow object inspection, thus the stringify
    console.log(JSON.stringify(entries));
    
    // alphabetically sort the entries based on the entry's name
    entries.sort(function(a,b){return (a.name.toLowerCase() < b.name.toLowerCase() ? -1 : 1)});
    
    //  start constructing the view
    var list = 'lt;ul>';
    var skip = null;
      for (var i=0;i<entries.length;i++){
        // should we hide "system" files and directories?
	if (app.util.getPref('hideSystem')){
  	  skip = entries[i].name.indexOf('.') == 0;
	}
	if (!skip){
 	  list += '<li><div class="rowTitle" data-action="' + (entries[i].isFile ? 'selectFile' : 'beginBrowseForFiles') + '" \
	       data-type="' + (entries[i].isFile ? 'file':'directory') + '" \
	       data-path="' + entries[i].fullPath + '">' + entries[i].name + '</div>\
	       <div class="alginIconInRow"><img src="images/' + (entries[i].isFile ? 'file':'folder') + '.png"></div>\
	       </li>';
	}
  }
  // insert the list into our container
  document.getElementById('body').innerHTML = list + '</ul>';
}
...

You can get a glimpse of how I drill into subdirectories – each DIV row contains information describing the entry it represents, data-type and data-path. Also note my own personal convention for determining what should happen when anything is tapped – data-action. My pub-sub implementation (not shown here) will loop through and fire all subscriptions to the touchend event. Because of this the subscribers need a way to know if they should run or not and data-action provides a mechanism for that.

The beginBrowseForFiles() function found in Step 1 is fired again when any row is tapped and the file path (via the data-path attribute) is passed along and the whole process starts over again.

Going back – viewing the parent directory

What good is drilling down into a file system if you can’t go back up? Enter the following function, as is evident on success we have what we need to jump to Step 2 which then re-populates our view:

...
  function doDirectoryUp(){
    var path = _currentFileSystem.root.fullPath;
		
    window.resolveLocalFileSystemURI(path,
      function(entry){
	entry.getParent( 
	  function(filesystem){
	    requestFileSystemSuccess({root:filesystem});
	  },
	  function(err){
            // once again Eclipse is not Chrome, so I stringify the objects so I can see them in the console
	    console.log('### ERR: filesystem.directoryUp() getParent -' + (JSON.stringify(err)));
	  }
	);
      },
      function(err){
	console.log('### ERR: filesystem.directoryUp() -' + (JSON.stringify(err)));
      }
    );
  }
...

Conclusion

PhoneGap provides all the tools to find folders and files, its just a matter of putting the pieces together. From here I can do a number of things – as I mentioned delete & move files are on my list as well as reading files. Once I have the first two I’ll put this thing on the app store. After that I’ll continue with my app idea – recall that all this was the first step for something bigger 😉

On a side note in the absence of a framework I did have a brief issue with scrolling where my swipes would naturally cause touchend events to fire. I figured out a little method of dealing with it… I think I’ll save that one for another day…

On-Device Development in Android

23 Oct 2013

Ever dream about using your Android tablet as a development environment? Android’s ability to use bluetooth keyboards and mice – or even wired USB devices depending on your hardware configuration – removes the usability limitations that instantly come to mind. All that is left is to find the necessary software. For my personal development needs I need three things:

  1. The ability to run a local web server
  2. A text editor, preferably one geared towards development
  3. The ability to debug JavaScript on-device

All three are easily had for free, and better yet, the last one already exists on your device!

Install a web server

This is a requirement for many reasons – the primary one is that it is quite difficult to get Android’s stock web browser to load up a file from the local file system. If you’ve tried you know how much of a pain it is. The second reason is that some of your work is likely to only work off of a web server anyway.

My personal needs require only that I have a functional web sever, however if you search Google Play you’ll find many that support PHP/MySQL so if server-side dev is your thing then you have a few options to choose from.

So again, for my needs a basic web server will suffice and I’ve chosen kWS Android Web Server. Search Google Play for “Web Server” and the app will appear.

After installation you only need to set up the home directory of the server. In my case I created a “www_root” folder on my external SD card where I house my project.

Here’s a screen shot of kWS after I’ve started the server – you can see that it is serving up files from the aforementioned “www_root” directory.

Screenshot_2013-10-16-13-13-32

To reach it you need to start up the Android web browser and enter the IP address for the localhost and the port number that kWS is listening to. That complete URL is:

  • 127.0.0.1:8080

Be sure to have a default file in the root of your home directory such as index.html. If your project file is something else you can easily code in a JavaScript redirect within your index.html to the desired file in order to avoid having to type in the file name that you wish to go to.

Text Editor

As before there are some options to choose from but the one that I have settled on is called DroidEdit Free. It offers code coloring, line numbers, multiple levels of undo, search, goto line and some others. It would be great if it had a linter but I’m not complaining at the moment.

Screenshot_2013-10-16-11-08-45

JavaScript Debugger

The stock android web browser has a console that will display errors, console.log statements and even allow for in-console commands to be entered. The trick is how to enable it since by default you cannot see the Debug option within the browser’s settings.

Its really quite simple to enable – once you are looking at your project via your newly installed web server type the following in the address bar:

  • about:debug

Nothing will appear to have changed but you’ve just enabled the JavaScript Console. It will appear if you’ve logged any statements or if there is an error in your code. It appears as a thin black bar – tap the bar to expand the console. In the example below you can see an error being logged at line 21:

Screenshot_2013-10-16-13-24-33

Errors will be logged but unfortunately you cannot inspect objects like is possible with desktop-class browsers and remote debuggers. This behaves much like the Adobe AIR CMD debug console if you’ve ever seen it – very basic. Nevertheless just being able to see errors with their associated line numbers is very useful.

You can configure additional debug options via the new Debug option in the browser settings. The defaults work fine for me.

Screenshot_2013-10-16-10-27-16

Conclusion

As time moves on mobile OS’s are getting more and more capable and have in many respects replaced mainstream “desktop” OS’s. This is just another example of that phenomenon. If you know of any other tools that should replace what I’ve listed above feel free to let me know. The key though is that everything should be **local** to the device.

As an aside, to get more out of your development you may want to download an FTP client and a File Manager. Two great options are Es File Manager and Astro which combine both of these features. I suspect though that you are already using one of these 😉