Sunday, May 29, 2016

How to pick multiple files from Android's gallery via API? - UNDOCUMENTED

Google "How to pick multiple files from Android's gallery" and you'll get multiple Stack Overflow answers on how to do it, and just as many complaints that the "answer" does not work. At least not on many Samsung devices.

I implemented the standard answer:
         intent = new Intent(Intent.ACTION_GET_CONTENT);  
         intent.addCategory(Intent.CATEGORY_OPENABLE);  
         intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);  
         intent.setType("image/*");  

and on my Samsung device, I still could only select ONE photo!

Opening up the default email program though, I could use Gallery to select multiple photos. WTF!

Multiple Googling and still no luck!

Finally, out of desperation I decompiled SecGallery2013.apk from my phone. Low and behold there was a undocumented Action in the manifest:

"android.intent.action.MULTIPLE_PICK"

Using this action in my Intent I managed to get Android Gallery to go into multiple selection mode. Yay!

Now, onActivityResult returns a Intent with new extras: "selectedCount" and "selectedItems".

"selectedItems" returns to us a string array list of Uri's to the pictures!

So my code now looks like this:

To call the Gallery:

     findViewById(R.id.button2).setOnClickListener(new View.OnClickListener() {  
       @Override  
       public void onClick(View v) {  
         // Undocumented way to get multiple photo selections from Android Gallery ( on Samsung )  
         Intent intent = new Intent("android.intent.action.MULTIPLE_PICK");//("Intent.ACTION_GET_CONTENT);  
         intent.addCategory(Intent.CATEGORY_OPENABLE);  
         intent.setType("image/*"); 
         // Check to see if it can be handled...
         PackageManager manager = getApplicationContext().getPackageManager();  
         List<ResolveInfo> infos = manager.queryIntentActivities(intent, 0);  
         if (infos.size() > 0) {  
           // Ok, "android.intent.action.MULTIPLE_PICK" can be handled 
           action = "android.intent.action.MULTIPLE_PICK"; 
         } else {  
           action = Intent.ACTION_GET_CONTENT;
         /* This is the documented way you are to get multiple images from a gallery BUT IT DOES NOT WORK with Android Gallery! (at least on Samsung )  
           But the Android Email client WORKS! What the f'k!  
               */  
           intent.setAction(Intent.ACTION_GET_CONTENT);  
           intent.addCategory(Intent.CATEGORY_OPENABLE);  
           intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true); // Note: only supported after Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT, harmless if used below 19, but no mutliple selection supported
         }  
         startActivityForResult(intent, 0xdead);  
       }  
     });  

And in the onActivityResult:

   @Override  
   protected void onActivityResult(int requestCode, int resultCode, Intent data) {  
     if(action.equals("android.intent.action.MULTIPLE_PICK")){  
       final Bundle extras = data.getExtras();  
       int count = extras.getInt("selectedCount");  
       Object items = extras.getStringArrayList("selectedItems");  
       // do somthing  
     }else {  
       if (data != null && data.getData() != null) {  
         Uri uri = data.getData();  
         // do somthing  
       } else {  
         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {  
           ClipData clipData = data.getClipData();  
           if (clipData != null) {  
             ArrayList<Uri> uris = new ArrayList<>();  
             for (int i = 0; i < clipData.getItemCount(); i++) {  
               ClipData.Item item = clipData.getItemAt(i);  
               Uri uri = item.getUri();  
               uris.add(uri);  
             }  
             // Do someting  
           }  
         }  
       }  
     }  
     super.onActivityResult(requestCode, resultCode, data);  
   }  

Wednesday, May 11, 2016

Sample App - Automatic Call Recorder for Android

I thought I'd post a not quite trivial sample app for perusal, so here it is:
Automatic Phone Call Recorder for Android
An automatic phone call recorder, records ALL calls your phone gets. You can choose to not record some of your contacts, or you can record every call. Schedule recording cleanup and mark recordings to never be deleted (by the cleanup schedule).
It's up to you to determine if this is legal in your jurisdiction. Not every phone supports call recording, so your mileage may very.

Sunday, April 17, 2016

A blast from my past...

An old colleague of mine (he is in the video) posted this to FB.

Many moons ago, I was the Technical Product Manager for MKS Internet Anywhere...



Thanks Sean!

Monday, April 11, 2016

So, ever since moving from Eclipse to Android Studio, I've been annoyed by the fact that importing an old project copies all the libraries under the project directory. This makes sharing libraries a bit more difficult, since you now need to manage them in two locations.

Apparently Gradle likes dependencies is under its root, for example:
Project
  |--build.gradle
  |--settings.gradle
  |--Dependency
  |    |--build.gradle
Now in most of my cases, I have my libraries as separate projects that I DO NOT want located under the app's root. So today, I finally got off my ass to figure out how to do that. (Yes, I've been very lazy about this)

The fix it turns out is rather simple. To achieve a directory structure like this:
Project
  |--build.gradle
  |--settings.gradle
Dependency
  |--build.gradle

In the project/settings.gradle, add the following:
include ':Dependency'
project(':Dependency').projectDir = new File(settingsDir, '../Dependency/module_build_gradle_location')
If you've just finished an import, you can now manually delete the imported module and rebuild the project.

Thursday, April 7, 2016

ActivityCompat.requestPermissions : Can only use lower 8 bits for requestCode

So, my latest got'cha is that ActivityCompat.requestPermissions forces you to use a value between 0 and 255. The android developers are playing tricks with the result code, thus restricting you to this range. Now, it would be nice if this was documented in the online ActivityCompat documentation, since this is going to be rarely hit in testing.

Documenting the findings for future reference:
The following are code from android.support.v4.app.FragmentActivity
 /**
 * Modifies the standard behavior to allow results to be delivered to fragments.
 * This imposes a restriction that requestCode be <= 0xffff.
 */
@Override
public void startActivityForResult(Intent intent, int requestCode) {
    if (requestCode != -1 && (requestCode&0xffff0000) != 0) {
        throw new IllegalArgumentException("Can only use lower 16 bits for requestCode");
    }
    super.startActivityForResult(intent, requestCode);
}

@Override
public final void validateRequestPermissionsRequestCode(int requestCode) {
    // We use 8 bits of the request code to encode the fragment id when
    // requesting permissions from a fragment. Hence, requestPermissions()
    // should validate the code against that but we cannot override it as
    // we can not then call super and also the ActivityCompat would call
    // back to this override. To handle this we use dependency inversion
    // where we are the validator of request codes when requesting
    // permissions in ActivityCompat.
    if (mRequestedPermissionsFromFragment) {
        mRequestedPermissionsFromFragment = false;
    } else if ((requestCode & 0xffffff00) != 0) {
        throw new IllegalArgumentException("Can only use lower 8 bits for requestCode");
    }
}

RANGE
startActivityForResult() in FragmentActivity requires the requestCode to be of 16 bits, meaning the range is from 0 to 65535.
Also, validateRequestPermissionsRequestCode in FragmentActivity requires requestCode to be of 8 bits, meaning the range is from 0 to 255.

see: http://stackoverflow.com/questions/33331073/android-what-to-choose-for-requestcode-values

Sunday, March 13, 2016

DatePicker, Sony Xperia and dates before 1980

I had a user report that they could not set a birth date before 1980 on their Sony Xperia phone. Bit of a problem if you are older than 36.

To fix this, first add the following to where ever you are inflating your datepicker:

this.datePicker = (DatePicker) view.findViewById(R.id.datePicker);
// hack also added to styleif (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
    Calendar calendar1 = Calendar.getInstance();
    calendar1.set(1900, Calendar.JANUARY, 1, 0, 0, 0);
    datePicker.setMinDate(calendar1.getTimeInMillis());
}


Unfortunately, the DatePicker.setMinDate is not exposed pre HONEYCOMB so to fix those apps, you can try adding this to your base application style

<!-- Base application theme. --><style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
    <!-- Customize your theme here. -->    <item name="android:startYear">1900</item>
</style>

Sunday, March 6, 2016

I failed sharing in Kindergarten, so when asked to do a simple sample app, I had to create myself a GitHub account. Normally I use Bitbucket due to their more commercial friendly approach.

Set up was reasonably painless and my first impressions are positive.

My challenge, as given was as follows:

Framework:

  • Android Studio 2.0+
  • Gradle build system
  • Target SDK Marshmallow
  • Min SDK Ice Cream Sandwich
  • The use of 3rd-party libraries to facilitate development is encouraged

Task


  • Create a new android project
  • The Root Activity should be a Navigation Drawer
  • The application should follow the material design principles
  • The core view of the main activity should be a Recycler View
  • The Recyler should implement some sort of custom view
  • The custom view should download and display images downloaded from the internet (imgur)

To see my sample app, CLICK HERE