Monday, June 9, 2014

Android - Static Access to Context outside of an Activity or Service

I recently got a crash report in my Android app (Megalarm, you should check it out) because the Context object in one of my singleton classes was stale and unexpectedly null. This is a Context object that was cached when the application was first launched at bootup.


To get around this, I started with a trick learned from this question on Stack Overflow.


(decorate your application node in AndroidManifest.xml as such)
<application android:name="com.seandroid.megalarm.MegalarmApplication">
    ...
</application>


(then create the matching application class)
public class MegalarmApplication extends Application
{
 private static Context initialContext;
 
 @Override
 public void onCreate()
 {
  super.onCreate();
  initialContext = getApplicationContext();
 }
 
 public static Context getAppContext()
 {
  return initialContext;
 }
}

This method of getting a Context object statically is good as it works most of the time but doesn't guarantee it won't be null (should getAppContext() be called before it has been created). Nonetheless this works in most cases.


However it doesn't fix my problem where the value was stale and null at one point much later. That's why I supplemented this with another class:

public class ContextHelper
{
 private static final ContextHelper instance;
 private Context latestContext;
 
 static
 {
  instance = new ContextHelper();
 }
 private ContextHelper()
 {}
 
 public static ContextHelper getInstance()
 {
  return instance;
 }
 
 public Context getContext()
 {
  if (latestContext != null)
   return latestContext;
  
  return MegalarmApplication.getAppContext();
 }
 public void setContext(Context ctxt)
 {
  latestContext = ctxt;
 }
}


This class will returned the latest Context object and if that is missing, the Context in the other singleton: MegalarmApplication.

Lastly, I modified my Activity classes on add a call to track the Context in each onResume() implementation.

@Override
protected void onResume()
{
 ...
 super.onResume();
 ContextHelper.getInstance().setContext(this);
}

I hope this helps you out if you've come across a similar problem.

Saturday, October 20, 2012

Android MusicPlayer getVolume()


Bonjour,

The android.media.MediaPlayer is useful to play a song and control its volume independently of the AudioManager.STREAM_MUSIC volume. However there is no way to get the current volume of the MediaPlayer.

Hence the class below... It provides us with access to the current volume of the player.

import android.media.MediaPlayer;

public class ImprovedMediaPlayer extends MediaPlayer
{
 private float currentVol = 1.0f;
 
 public float getVolume()
 {
  return currentVol;
 }
 
 public boolean setVolume(float newVol)
 {
  if (newVol <= 0)
   newVol = 0f;
  else if (newVol >= 1.0f)
   newVol = 1.0f;
  
  boolean volChanged = (Math.abs(newVol - currentVol) > 0.0001f);
  this.setVolume(newVol, newVol);
  return volChanged;
 }

 
 @Override
 public void setVolume(float leftVolume, float rightVolume)
 {
  currentVol = leftVolume;
  super.setVolume(leftVolume, rightVolume);
 }
}

Enjoy,
Sean

Wednesday, September 26, 2012

Refactored WPF Auto-Complete ComboBox

Proper WPF Auto-Complete ComboBox

I had to use the Auto-Complete ComboBox included in the WPF Extended Toolkit recently. Now before I say anything else, I amongst many others appreciate the work done on this toolkit and am very greatful for it. I know that it is the culmination of work done by many competent individuals. However, the AC (Auto-Complete) ComboBox was a nightmare for my use-case.

I needed a ComboBox that:

- was Editable
- Sorts the items following each key stroke and keeps them up-to-date
- affords Data Binding to the selected item

Why was it a nightmare?

This was not easy because of the way the focus would jump from a list item to the text part of the combobox. Once a partial match was made (typed 'a' and the list contains "Aardvark", the text would be replaced with the first item in the list of matches (Aardvark). So if i was typing Apples, the 'a' would then change the text in the ComboBox to Aardvark and select it all. Then i strike 'p' and now the text in the ComboBox is wiped and replaced with 'p'... But my list also has "Plums". So now my selected text is "Plums".

And so on... (there were many use-cases where it would end up in a royal mess... users HATED this screen and would avoid it like the plague)

The first approach was to add the missing functionality from the out-of-the-box ACCB (Auto-Complete ComboBox) by extending the class and using a mixture of method overriding and event handlers. This helped early on but didn't quite cut it: there was always a case where the focus would get lost or the text was highlighted from an odd point. Thus in the end, the control had very odd behaviour (as described above).

That's when I restarted the control from scratch. I decided to use a ComboBox (non Editable) with a TextBox that sits above the Text part of the ComboBox and do the coupling myself.

However, I needed access to which item was highlighted in the dropdown when the user cycles through the possible matches. So I stood on the shoulders of giants (Christian Moser's WPF Tutorial to be exact) and use the LivePreviewComboBox which was conjured up for just that reason. When the user starts typing and the dropdown opens with matches, hitting the down button will then scroll through the items. Similarly, hitting the up key will bring the focus up the list and finally back into the textbox to type more text.




I have a custom theme so for me this sufficed. If you are not happy with the look of the control, apply a style to it! To do so, you can create a style for the class AutoCompleteComboBox and set the template to whatever you want. I used a LivePreviewComboBox on top of some rectangles to react to IsMouseOver and HasFocus. The choice is yours!

Go ahead and download the source code and sample project!




Monday, September 12, 2011

InteropBitmap in WPF

Ahoy ahoy,

Recently I found myself in a situation where I wanted to modify a bitmap on the fly and see the results immediately in WPF. Now as most WPF peeps know, to display an image in WPF is relatively straightforward. I've included a sample application that displays 2 tabs. One with an image that is just data-bound normally to a BitmapSource and another that displays an InteropBitmap in which we will regularly modify some pixels. You can download the entire VS solution as you wish. I'll only include the relevant sections of the code here for readability's sake.


Model.cs
public class Model : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void BroadcastPropertyChanged(string propName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}

private BitmapSource beforeImage;
public BitmapSource BeforeImage
{
get { return beforeImage; }
set
{
if (beforeImage != value)
{
beforeImage = value;
BroadcastPropertyChanged("BeforeImage");
OnBeforeImageChanged();
}
}
}

private void OnBeforeImageChanged()
{
}
}


MainWindow.xaml.cs
public partial class MainWindow : UserControl
{
public MainWindow()
{
InitializeComponent();

Model m = new Model();
this.DataContext = m;
m.BeforeImage = new BitmapImage(new Uri(@"C:\Users\Coco\Pics\001.jpg"));
}
}


- INotifyPropertyChanged is in the System.ComponentModel namespace (System.dll)
- BitmapSource is in the System.Windows.Media.Imaging namespace (PresentationCore.dll)


If I run the application I see the image in my Before tab displayed properly. Nothing is in the After tab because we have yet to create a property "AfterImage" in the Model class.




Barcelona is great, isn't it? However, it is beside the fact. To display an image we bind the Image.Source property to either a BitmapSource or a string (giving us the path of the image to display). In this case we'll just stick to the BitmapSource.


The BitmapImage (which inherits from BitmapSource) that we set as our BeforeImage does not expose any methods to edit pixels or write into them. There are two implementations (probably amongst others) of BitmapSource that allow us to do just that. Choosing the right one depends on the circumstance.

  • WriteableBitmap
  • InteropBitmap

In my case, I chose InteropBitmap (reasons to follow) and that will be the focus of the article. InteropBitmap was originally created to render Windows Forms Bitmaps in a WPF environment. But that is not why I used it. I required the ability to have direct access to the memory buffer used to store the pixels of the image and wanted the ability to modify these pixels (slots in the memory buffer) from any thread (a normal BitmapImage allows access to it only from within the thread that created it). InteropBitmap provides this and when a BitmapSource is asked for by the Image control, we call Invalidate on the InteropBitmap and return a frozen frame of the current state of the memory buffer. If this isn't clear, just read on, hopefully it will soon make more sense.


Proper use of the InteropBitmap requires us to first expose some Win32 methods to assist us in its creation. We'll use these Win32 methods to create a section of memory that we can later write to directly as our image. The code below belongs in the Model class


Model.cs
#region Extern Stuff
const uint FILE_MAP_ALL_ACCESS = 0xF001F;
const uint PAGE_READWRITE = 0x04;

[DllImport("kernel32.dll", SetLastError = true)]
static extern IntPtr CreateFileMapping(IntPtr hFile,
IntPtr lpFileMappingAttributes,
uint flProtect,
uint dwMaximumSizeHigh,
uint dwMaximumSizeLow,
string lpName);

[DllImport("kernel32.dll", SetLastError = true)]
static extern IntPtr MapViewOfFile(IntPtr hFileMappingObject,
uint dwDesiredAccess,
uint dwFileOffsetHigh,
uint dwFileOffsetLow,
uint dwNumberOfBytesToMap);
#endregion

private int bufferWidth = 0;
private int bufferHeight = 0;
private uint bufferByteCount = 0;

private InteropBitmap outputBitmap;
private IntPtr outputSectionPointer;
private IntPtr outputMapPointer;
private static readonly PixelFormat outputFormat = PixelFormats.Bgra32;

private void OnBeforeImageChanged()
{
if (bufferWidth != BeforeImage.PixelWidth ||
bufferHeight != BeforeImage.PixelHeight)
{
// record the new image dimensions
bufferWidth = BeforeImage.PixelWidth;
bufferHeight = BeforeImage.PixelHeight;
bufferByteCount = (uint)(bufferWidth * bufferHeight * ((outputFormat.BitsPerPixel + 7) / 8));

outputSectionPointer = CreateFileMapping(new IntPtr(-1), IntPtr.Zero, PAGE_READWRITE, 0, bufferByteCount, null);

outputMapPointer = MapViewOfFile(outputSectionPointer, FILE_MAP_ALL_ACCESS, 0, 0, bufferByteCount);
}

// copy the original image into the buffer we created for the after image
BeforeImage.CopyPixels(new Int32Rect(0, 0, bufferWidth, bufferHeight), outputMapPointer, (int)bufferByteCount, bufferWidth * 4);

// build the InteropBitmap from the memory buffer
AfterImage = Imaging.CreateBitmapSourceFromMemorySection( outputSectionPointer, bufferWidth, bufferHeight,
outputFormat, bufferWidth * 4, 0) as InteropBitmap;
}

public BitmapSource AfterImage
{
get
{
if (outputBitmap != null)
{
outputBitmap.Invalidate();
return (BitmapSource)outputBitmap.GetAsFrozen();
}

return null;
}
private set
{
outputBitmap = value as InteropBitmap;
BroadcastPropertyChanged("AfterImage");
}
}


Once we are done creating the InteropBitmap and its memory buffer, we can write to the buffer and broadcast the AfterImage PropertyChanged event so that the (data-bound) Image viewer in the After tab will update itself. The MainWindow now contains buttons to start and stop the writing to random pixels. It also contains a refresh frequency textbox which will indicate how often we want to update the UI. Be careful though, too many UI updates and you'll bog down the UI thread. This is because when we broadcast the PropertyChanged event, the databound Image viewer in the UI listens to the event (internally) and gets the Model.AfterImage property. This forces a render cycle on the InteropBitmap and then a clone of the InteropBitmap's buffer is returned. So if you're dealing with big buffers, you may start to experience lagging. So try to limit your PropertyChanged event broadcasts on the image property exposed in the model (AfterImage in our case).


The code below was used to modify random pixels in the image's memory buffer.


Model.cs
private DateTime lastImgRefreshTime = DateTime.MinValue;
private int imageRefreshFreq = 500;
private bool isWritingPixels;
private ManualResetEvent mre;

public Model()
{
isWritingPixels = false;
mre = new ManualResetEvent(isWritingPixels);
Task.Factory.StartNew(new Action(() =>
{
Random rand = new Random();
while (true)
{
// wait for the user to start the pixel writing
mre.WaitOne();

unsafe
{
Int32* pStart = (Int32*)outputMapPointer;

int x = rand.Next(bufferWidth);
int y = rand.Next(bufferHeight);

pStart[y * this.bufferWidth + x] = unchecked((int)0xFF000000) | rand.Next();
}

DateTime now = DateTime.Now;
if ((now - lastImgRefreshTime).TotalMilliseconds > imageRefreshFreq)
{
BroadcastPropertyChanged("AfterImage");
lastImgRefreshTime = now;
}
}
}));
}

internal void StartPixelWriting()
{
TogglePixelWriting();
}

private void TogglePixelWriting()
{
if (isWritingPixels)
{
mre.Reset();
}
else
{
mre.Set();
}

isWritingPixels = !isWritingPixels;
BroadcastPropertyChanged("CanStartWriting");
BroadcastPropertyChanged("CanStopWriting");
}

internal void StopPixelWriting()
{
TogglePixelWriting();
}


And now you see the end result.



Thursday, June 23, 2011

How to query for music on Android



Within my simple music alarm application, I needed a way to retrieve all the artists, albums and songs on the phone. Unsure of whether there was a service there already to do so, I searched about Google how to do this. The lack of any complete A-Z articles on the subject led me to write this...

In Android, all of our music information (song title, track number, album art, artist name, etc.) is stored in a system music database. There are several tables (genres, artists, playlists, audio and more) so it is important to know what you want to find before you can think of where to look. We can query these tables through the ContextResolver object found in our Context object (of an Activity, Service, BroadcastReceiver, etc.).

I have created a small example to query our SD card for all the songs, artists and albums found on it.

Download the Source Code!!

Not sure how to put music on the SD card in the Android emulator? Check out my previous blog post to learn how to do so.

Below is a test method I wrote to collect the names of every album stored on the phone. Let's look at the code and the comments. I'll explain what's remaining afterwards.

public void collectAllAlbums()
{
// the cursor we will use to iterate over the db results
Cursor cursor = null;

// the list of columns our search relates to
final String[] projection = new String[] {MediaStore.Audio.AlbumColumns.ALBUM };

// how we want to sort our results
final String sortOrder = MediaStore.Audio.AlbumColumns.ALBUM + " COLLATE LOCALIZED ASC";
try
{
// the uri of the table that we want to query
Uri uri = android.provider.MediaStore.Audio.Albums.EXTERNAL_CONTENT_URI;

// we now launch the query (be sure not to do this in the UI thread should it take a while)
cursor = mainContext.getContentResolver().query(uri, projection, null, null, sortOrder);
if (cursor != null)
{
int i = 0;
allAlbums = new String[cursor.getCount()];
cursor.moveToFirst();
while (!cursor.isAfterLast())
{
// get the 1st col in our returned data set (AlbumColumns.ALBUM)
allAlbums[i++] = cursor.getString(0);
cursor.moveToNext();
}
}
}
finally
{
if (cursor != null)
{
cursor.close();
}
}
}

So, what just happened?
  • We query the data table located at the Uri provided for all the albums in the table. All the important stuff is in ContextResolver.query(...). Check out the Android documentation on the subject for details.
  • We use a cursor to iterate over the data set returned. The cursor uses resources so you must always close the cursor when you are done with it (hence the try/finally block). As of API 11 (Android 3.0 - Honeycomb), using a CursorLoader will take care of deactivating the Cursor when the activity closes. However, if not using a CursorLoader, it is wise to close the Cursor when done with it.
  • We call getString(0) on the cursor. This will return the value of a given column in the current data row. You must know which type you are expecting, because there is getString, getLong, getDouble, etc..
Queries may take a while. It 's a pretty good idea to launch them in a background thread and once the query is done, post it back to the UI thread to update what it needs to. I do just that in the example I've included.

You can also check in my example code how long the queries take. You should not use null as your projection string in the query. The String[] projection declares which columns of the table you want returned. Passing null will return all the columns, which is very inefficient (unless that is what you want to do). Play with the example and see for yourself.





The last thing I should add: sometimes you'll have to use other data tables to find the information you're looking for. I know that in the Audio table Columns, you won't find the genre of the song unfortunately. This information is stored in a separate table, but querying its table is just the same as in this example. Use the correct Uri, know which columns you want returned (build the String[] projection), build the selection (sql WHERE) string accordingly and the ordering string (sql ORDER BY).

Happy querying!

Friday, June 17, 2011

How to mount an sdcard in the Android Emulator


Ahoy ahoy,

I've been working on an Android application. The idea was pretty simple enough: an alarm that plays a random song each time it goes off. It being my first venture into Android and developing with Java (no, I was not using Monodroid to do it in .NET... at least not yet), I was bound to run into a few hurdles along the way.

One of these problems I ran into was how to make the emulator think that it has an sdcard installed so that I could query it for all the music that it contains. There are a few articles spread out on blogs and IT sites on the topic but I found nothing that was comprehensive and complete enough to allow me to learn the basics on it and get it done quickly. The goal of this article is just that.

I create a test project you can run in Eclipse to verify that you've indeed got your SD card properly installed on the emulator. It is simple enough: a ListActivity that lists the title of all songs found on the SD card.

You can download it HERE!


1. Create an SD card
The android SDK comes with a tool to create an SD card file to be used with the emulator. The tool is mksdcard. To use the tool:
  • open a command prompt (Windows Key + R, type cmd, hit Enter)
  • go to the directory where the android sdk was installed (mine is C:\Java\android-sdk-windows\tools)
  • mksdcard 512M C:/Users/Sean/Workspace/emulator_sdcard
  • Wait a few seconds (the bigger the sd card size, the much longer you'll wait...)
  • Verify that the file was indeed created in the desired location

2. Assign the SD card to the emulator
This is done inside of Eclipse.
  • In the top menu, go to Run > Debug Configurations...
  • Choose your debug configuration in the treeview on the left. You may have to create one if you have not debugged the project before.
  • Select the Target tab.
  • At the bottom of the options is a field. Additional Emulator Command Line Options
  • Enter -sdcard your_sd_card_location (mine was C:/Users/Sean/Workspace/emulator_sdcard)
  • Hit the Apply button at the bottom. Then close the dialog.
  • If the emulator is already open, close it.
  • Now debug your project. (Right-click on the project > Debug As > Android Application





If we re-run our test application, there is still nothing of interest. We must now fill the SD card with useful data in order for it to be read by our killer app.


3. Push data onto the SD card
To accomplish this we will use a graphical tool included in the android sdk and found in Eclipse. If you stopped the debugger from the previous step, go ahead and relaunch it.
  • In Eclipse from the main menu, go to Window > Show View> File Explorer. Or you could choose the DDMS View already inside Eclipse. It contains the File Explorer.
  • Once inside the File Explorer, you'll find the "Push a file onto the device" button in the menu bar. But beware, you can't add your data files just anywhere on the SD card. Many system directories are read-only.
  • Go into the mnt/sdcard folder and add your data there. You can do so by:
- The "Push a file onto the device" menu item
- Drag and drop the file onto the selected folder
- Through adb (the command-line tool) with the following command:
adb push [C:\music\example.mp3] /sdcard/[example.mp3]
adb push [C:\music\myFavoriteAlbumDirectory] /sdcard/[directoryName]
(adb is located in the android-sdk installation directory. Mine was C:\Java\android- sdk-windows\tools)

You should now see your file on the SD card in the file explorer.

Hey, wait a minute! It's not there, something went wrong.
It's true, we don't live in a perfect world and yes, things can go askew sometimes. If your file is not there, look in the output console in Eclipse, it can help us find out why. Some possible causes/remedies:
  • You tried adding your file in a read-only directory. Push the file into mnt/sdcard to be sure.
  • You may need to restart Eclipse (it worked for me when I got this message in the console:
[2011-06-18 10:30:17] Failed to push 07. That's The Way Love Is.mp3 on emulator-5554: null
  • Your SD card is full. Unfortunately if this is the case, you cannot resize the SD card file you created initially. You'll have to recreate it with mksdcard but with a bigger size this time.
If you want to create a directory structure on the SD card, you cannot use the File Explorer in Eclipse. Instead use a file browser app on the emulator or adb (same command used to push a file but use it on a directory instead... but not an empty directory... that won't work).


4. Relaunch the app
At this point we will see our list of music on the sdcard in our app. Close the emulator and debug the application once more. We should now see a list of the songs (we obtained through legal means...) we added to the SD card.


Enjoy,
Sean