Tuesday, November 21, 2017

Implementing SearchView in Toolbar with Fragments managed by your MainActivity

Google the above and you will get multiple ways that people have taken to implement search results as a fragment. One of the other pickles is passing the "results" of the search, back to the main activity.

Android wants you to use an Activity to handle the search results, but in my current app, I am using the "navigation drawer" pattern and to me it made sense to have my main activity manage the search and use fragments to display the results.

Now, I could have done this by taking control of the Search Widget and SearchView's setOnQueryTextListener. Although this works well and in some ways would be easier,  I wanted to take advantage of some of the more interesting possibilities provided by using a ContentProvider and letting the Android do the search management.

So, to implement Fragment support for the results, I did the following....


in the AndriodManifest.xml I added the following to my MainActivity:

<activity    
  android:name=".MainActivity"    
  android:launchMode="singleTask"

 <intent-filter>
        ...
        <action android:name="android.intent.action.SEARCH" />
    </intent-filter>
    <meta-data android:name="android.app.searchable"        
              android:resource="@xml/searchconfig"/>
</activity>

The android:launchMode="singleTask" causes the activity to remain the root task and more importantly the system does not recreate it if it already exists. The System routes the intent to existing instance through a call to its onNewIntent() method. The MainActivity can now handle the creating the fragment and displaying the search results

Now, inside MainActivity:

@Overrideprotected void onNewIntent(Intent intent) {
    if (Intent.ACTION_SEARCH.equals(intent.getAction())) {
        String query = intent.getStringExtra(SearchManager.QUERY);
         if(null!=query&&query.length()>0){
            // Manage search fragment here...        }
    }
}

And to set up the search bar:

 @Override   public boolean onCreateOptionsMenu(Menu menu) {
       // Inflate the menu; this adds items to the action bar if it is present.      
       getMenuInflater().inflate(R.menu.main, menu);
       MenuItem searchItem = menu.findItem(R.id.action_search);
       SearchManager searchManager = (SearchManager) getSystemService(Context.SEARCH_SERVICE);
       final SearchView searchView = (SearchView) MenuItemCompat.getActionView(searchItem);
       searchView.setSearchableInfo(searchManager.getSearchableInfo(getComponentName())); // We are the searchable activity

       return true;
   }

Hope this helps!

Friday, December 30, 2016

Customizing an Android Button - Changing Font Color on Click?

Do you need to change the color of the text when a button is clicked? 
To do so, create a selector resource in res/color directory for the text color 
custom_text_color.xml
<?xml version="1.0" encoding="utf-8"?><selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:color="@color/colorTextIcons" android:state_pressed="true" />
    <item android:color="@color/colorPrimaryDark" android:state_pressed="false" />
</selector>
Then add the color to the button layout:
 <Button
    android:id="@+id/button1"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="Button"
    android:textColor="@color/custom_text_color"
    />

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