Android: Detect when a headset/headphones is connected/unconnected and determine if it has a microphone

Here's a handy little snippet if you're working on an app which requires headphones. It allows you to detect when users connect/remove a headset but also determine if there is a microphone or not, as not all hands free kits are created equal.

On your activity, add this little snippet to your onCreate().

public HeadsetPlugReceiver headsetPlugReceiver;

protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

headsetPlugReceiver = new HeadsetPlugReceiver();
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction("android.intent.action.HEADSET_PLUG");
registerReceiver(headsetPlugReceiver, intentFilter);
}

By registering the listener/filter we bypass the need to specify it in the manifest file.

And then onDestroy(), remember to unregister it.

@Override
protected void onDestroy() {
if (headsetPlugReceiver != null) {
unregisterReceiver(headsetPlugReceiver);
headsetPlugReceiver = null;
}

super.onDestroy();
}

That should be enough to manage the handler during the activity lifecycle.

Lastly, implement the receiver logic.

public class HeadsetPlugReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if (!intent.getAction().equals(Intent.ACTION_HEADSET_PLUG)) {
return;
}

boolean connectedHeadphones = (intent.getIntExtra("state", 0) == 1);
boolean connectedMicrophone = (intent.getIntExtra("microphone", 0) == 1) && connectedHeadphones;
String headsetName = intent.getStringExtra("name");

// ...
}
}

Now that we've got headphone and microphone detection working ...

gLkSz
SINGSTAR BATTAL BITCHES!

Sources

Python/Django: Dealing with UTF8 in URLs/URIs

UTF8 is a nice set of characters to use, but one must remember that the standard for URL encoding has to be in ASCII. You've probably run into it before with the Python urllib and urllib2 libraries when encoding issues are raised. These are valid exceptions!

92JJn
Yeah that's right bitch, urllib ain't taking your UTF8 shit.

"But it just works in my browser!" you may be thinking. Yes, most modern browsers will automatically convert the UTF8 URL into an ASCII request behind the scenes without showing it to you.

So as a developer, if you have a URL containing UTF8 characters then you'll have to convert it to ASCII before you can send a request.

If you're using Django, then you have some nice helper functions to deal with this. By using django.utils.encoding.iri_to_uri(), you can simply convert the UTF8 portions of the URL into ASCII and keeping everything else unmodified.

If you're not using Django... I guess you can try to incorporate their madness from their encoding module source file.

Examples

First make sure your URLs are properly encoded in Unicode (note the little "u" in front of the string when defining "url").

Take for example this URL which contains UTF8 characters. A quick test shows: http://twigstechtips.blogspot.com/seârch/labél/pythön

>>> url = 'http://twigstechtips.blogspot.com/seârch/labél/pythön'
>>> print url
# http://twigstechtips.blogspot.com/se├órch/lab├®l/pyth├Ân


>>> url = u'http://twigstechtips.blogspot.com/seârch/labél/pythön'
>>> print url
# http://twigstechtips.blogspot.com/seârch/labél/pythön

But what about GET args? Don't worry, iri_to_url() deals with them too. For example: http://twigstechtips.blogspot.com/seârch/labél/pythön?query=djångõ

>>> url = u'http://twigstechtips.blogspot.com/seârch/labél/pythön?query=djångõ'
>>> print iri_to_uri(url)
# http://twigstechtips.blogspot.com/se%C3%A2rch/lab%C3%A9l/pyth%C3%B6n?query=dj%C3%A5ng%C3%B5

And it also works for UTF8 domains too (yes, they exist!). In this instance, we'll use http://camtasia教程网.com:

>>> url = u'http://camtasia教程网.com'
>>> print url
# http://camtasia教程网.com


>>> print iri_to_uri(url)
# http://camtasia%E6%95%99%E7%A8%8B%E7%BD%91.com

I'll be honest, I'm not quite sure what the heck an IRI is but this magic function works as advertised.

39gyh1taajden89mwf8v331ug
BOOM! It just works.

On a final note, if you're after UTF8 friendly versions of urllib.quote() and urllib.quote_plus(), then there are also:

The functions django.utils.http.urlquote() and django.utils.http.urlquote_plus() are versions of Python’s standard urllib.quote() and urllib.quote_plus() that work with non-ASCII characters. (The data is converted to UTF-8 prior to encoding.)

Source

Python: lxml "Unicode strings with encoding declaration are not supported" error

ValueError: Unicode strings with encoding declaration are not supported. Please use bytes input or XML fragments without declaration.

Ran into this interesting quirk when fixing some UTF8 issues. Actually it's not really a quirk but by design, since encoding gives most people a headache.

The reason behind this is lxml just doesn't trust people to give it properly encoded strings, and rightly so.

So simply just give them the raw input or a file handle and it'll handle the encoding itself.

Don't do things like:

str = u"%s" % input
# or
str = file_content.encode("utf-8")

Things like this will work much better:

from lxml import etree
file_content = urlopen(link).read()

parser = etree.XMLParser(recover=True)
xml = etree.fromstring(file_content, parser)

# or

from StringIO import StringIO
parser = etree.XMLParser(recover=True, encoding='utf-8')
xml = etree.parse(StringIO(file_content), parser)

If the XML string already declares an encoding type then you don't need to provide any encoding. It's smart like that.

78
So kick back and relax, lxml's got this.

Source

Android: Resize AlertDialog when keyboard is shown

There are times you have to show some text fields on the dialog, and when the keyboard pops up it becomes an absolute nuisance.

image

As you can see, the keyboard is covering the OK/Cancel buttons. In other cases it covers auto-complete or spinner items.

Thankfully, there's a quick and easy one liner that'll fix you right up. The last line is the one of particular interest to you.

AlertDialog d;
d = new AlertDialog.Builder(this);
...
d.create();

d.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);

Make sure you have some sort of resizable element in the layout to accommodate for the reduction of screen space (such as a ScrollView).

p9wnQ
Don't worry Android keyboard, we still love you!

Source

How to clear up oxidisation from a cloudy car headlight

First of all, this isn't a computer related post. I just thought this was cool and wanted to post up the photos :)

I was highly sceptical and didn't think this would work, but I've tried washing the damn thing so many times already and thought "what the hell, why not?"

IMG_6368
As you can see, the left headlight isn't in the best condition.

Here's a closer look.

IMG_6370 IMG_6369

Introducing my toolkit for today; a bucket of warm water, a sponge and the last bits of an almost finished toothpaste tube.

IMG_6375

After rubbing a bit of toothpaste into the headlight with a wet sponge, feel free to apply a liberal amount of elbow grease and rub the oxidation away.

It might help to check the headlight every once in a while to see how you're going. No point rubbing over and area that's already been fixed.

Wash off all the excess toothpaste and bubbles once you're done. I couldn't believe it, but here are my before/after shots!

IMG_6370 IMG_6374

It's not as clear as I'd like it to be, but I'll probably give it another whirl next weekend when my arms aren't so dead tired after washing + RainX'ing two cars.

If you're happy with your results, get some wax to lock in the shine and protect the plastic from oxidising again.

Source

Android: Override the displayed text in an AutoCompleteTextView dropdown list

In my app I have an AutoCompleteTextView which allowed users to select suburbs by typing a few characters and then selecting from a pre-defined list.

The issue was I wanted the option to also filter by postcode, but this required the postcode to be part of the Suburb.toString() function, making it rather ugly upon display.

There are two obvious ways to skin this cat; customise the filtering in Adapter.getFilter() or override the text shown in Adapter.getView().

Just for reference, my Suburb.toString() returns "Name of Suburb : #" where # is the postcode.

Option A - Subclass the adapter

The former is relatively easy, just subclass an ArrayAdapter (or whatever adapter you're using), copy the code from ArrayAdapter.ArrayFilter and change the text source "valueText = value.toString().toLowerCase()" to suburb/postcode.

Change Suburb.toString() to only display the suburb name.

Sadly, I didn't choose this option first.

Option B - Override the view text

The latter is to keep Suburb.toString() but replace the item TextView text after it's been rendered. I chose this solution, thinking it'd be easier. It wasn't.

final AutoCompleteTextView tvSuburbs;
ArrayAdapter<Suburb> adapterSuburbs;

adapterSuburbs = new ArrayAdapter<Suburb>(this.getSupportActionBar().getThemedContext(), android.R.layout.select_dialog_item, suburbs) {
// Hide the postcode from the display
@Override
public View getView(int position, View convertView, ViewGroup parent) {
TextView tv = (TextView) super.getView(position, convertView, parent);
Suburb s = getItem(position);
tv.setText(s.name);

return tv;
}
};

tvSuburbs = (AutoCompleteTextView) layout.findViewById(R.id.actvSuburb);
tvSuburbs.setAdapter(adapterSuburbs);


Now that's all fine and dandy. Simply override the getView() of the adapter and change the text after it's been returned. The filtering still works as the value string is still the same.

There's only one problem left. Now that your autocompletion works, selecting an item from the dropdown will set the text to "Name of Suburb : #".

That's simple enough right? Override AutoCompleteTextView.setOnItemSelectedListener() and call setText(), can't be that hard.

Yes. Yes it is. Starting from ICS, that function only sets the listener. Whether or not it actually does anything is another story!

Taking a look at the AutoCompleteTextView source code, I realised it actually did not do anything! You can set/get the listener but it's never used in the code!

Instead, use setOnItemClickListener() and call setText() from there.

Now it's working.

  • User types in a few search characters
  • Allows user to filter by suburb name AND/OR postcode
  • Dropdown showing only suburb name
  • User clicks on the item
  • Auto-completes the text using only the suburb name

Sweet, it works! So... what's our problem now? I'm sure you'll notice once you run the app once.

3szfjiniofzhx249usa06wzw1

The behaviour of the dropdown is to show the dropdown whenever you call setText(). That means it'll display the dropdown after you click an item in the autocomplete.

That behaviour is pretty stupid in my opinion, but there's a workaround!

Source

Android: Prevent AutoCompleteTextView.setText() from showing dropdown

If you're going to use AutoCompleteTextView.setText(), you'll soon notice that the standard dropdown behaviour will annoy you since it shows the dropdown after the call is finished.

To prevent it, you'll have to:

  • remove the adapter
  • set the text
  • set the adapter back

Since there is no adapter information to autocomplete, there will be no dropdown.

Here's some working sample code.

tvSuburbs.setOnItemClickListener(new OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Suburb s = (Suburb) tvSuburbs.getAdapter().getItem(position);
ArrayAdapter<Suburb> a = (ArrayAdapter<Suburb>) tvSuburbs.getAdapter();
tvSuburbs.setAdapter(null); // Remove the adapter so we don't get a dropdown when
tvSuburbs.setText(s.name); // text is set programmatically.
tvSuburbs.setAdapter(a); // Restore adapter
}
});

And there you have it!

Sources

Android: Adding Google Maps (API v2) to your app activity

Now this was a cluster-fuck of issues. I experienced error after error (after error) following the OFFICIAL documentation trying to get this to work.

Since starting development since Android SDK 1.0, the dev tools have really advanced at an amazing rate. However, this one is a huge setback in terms of productivity.

3eudaphxm5glmxwu38wpriax0

Searching for tutorials is another matter. They might be written for Maps API v2, but skim through little details which cause big errors. Either that or written for the deprecated v1 API.

I tinkered with MapActivity and MapView. I felt this was a necessary post as I've hit run many roadblocks, seen many errors;

Caused by: java.lang.RuntimeException: stub

java.lang.NoClassDefFoundError: com.google.android.maps.MapView

Caused by: android.app.Fragment$InstantiationException: Unable to instantiate fragment com.google.android.gms.maps.SupportMapFragment: make sure class name exists, is public, and has an empty constructor that is public

java.lang.NoClassDefFoundError: com.google.android.gms.R$styleable

Caused by: java.lang.ClassNotFoundException: com.google.android.gms.maps.SupportMapFragment

Well, I'm glad to say I've recently had a breakthrough with getting maps to work and I'm taking the chance to write about it now as the knowledge is still fresh and I still have the 30 tabs of sources open.

As a nice little extra, this solution will even work with API level 4+ as I've been told you need at least 7+ for it to work.

Downloading stuff

To begin, fire up Eclipse and let's start downloading! You can find the "Android SDK Manager" under the "Window" menu.

From the list of items, select:

  • Extras > Google Play Services
  • Android x.x (API #) > Google APIs (where # is the target API level of your project)

Once that's done, you may (or may not) have to restart your Eclipse. Don't worry, it'll prompt you.

Google Play Services project

  • File menu (or right click project explorer) > Import
  • Android > Existing Android code into workspace
  • Click browse and find your Android SDK folder, then dig into "extras\google\google_play_services\libproject\google-play-services_lib". Yeah, I'm not kidding.
  • If no projects appear, click "Refresh"
  • Tick "google-play-services_lib"
  • Select "Copy projects into workspace"
  • Click Finish

image
Mine's disabled because I've already done it

Your project setup

As proof of concept, I've created a project that supports API level 4, compiled with Jelly Bean (API 16).

image

Once you have a project up and running, it's time for the "fun" XML-pasting part.

First up, linking the two projects.

  • Right click on your project and select "Properties".
  • Click on "Android" from the list on the left and then see "Library".
  • Click "Add" and select "google-play-services_lib". If it does not appear, make sure that the Google Play Services library project is opened in Eclipse.

image

Secondly, your activity layout. I replaced the entire contents of the file with this.

<fragment xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/map"
android:name="com.google.android.gms.maps.SupportMapFragment"
android:layout_width="wrap_content"
android:layout_height="match_parent" />

Now for your Activity class.

Change this line:

public class MainActivity extends Activity {

to extend FragmentActivity:

import android.support.v4.app.FragmentActivity;
public class MainActivity extends FragmentActivity {

Add in a GoogleMap instance and some initialisation code.

public class MainActivity extends FragmentActivity {
private GoogleMap gm;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

setContentView(R.layout.activity_map);

int status = GooglePlayServicesUtil.isGooglePlayServicesAvailable(getBaseContext());

if (status == ConnectionResult.SUCCESS) {
SupportMapFragment supportMapFragment = (SupportMapFragment) getSupportFragmentManager().findFragmentById(R.id.map);
gm = supportMapFragment.getMap();
}
else {
int requestCode = 10;
Dialog dialog = GooglePlayServicesUtil.getErrorDialog(status, this, requestCode);
dialog.show();
}
}
}

And now for some messy stuff in AndroidManifest.xml.

Under the <manifest> tag, you'll need to access these permissions:

<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="com.google.android.providers.gsf.permission.READ_GSERVICES"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

<uses-feature android:glEsVersion="0x00020000" android:required="true"/>
  • Internet and network state because, well, it needs the net to access maps.
  • READ_GSERVICES to access Google services
  • WRITE_EXTERNAL_STORAGE is to access the offline map cache
  • glEsVersion isn't a permission but feature, so it'll restrict potential devices a little bit. Used when rendering the maps.

If you're planning on using the GPS to detect user coordinates, you'll also need these permissions:

  • android.permission.ACCESS_COARSE_LOCATION: Allows the API to use WiFi or mobile cell data (or both) to determine the device's location.
  • android.permission.ACCESS_FINE_LOCATION: Allows the API to use the Global Positioning System (GPS) to determine the device's location to within a very small area.

The following two are required custom permissions, so replace "com.example.testmaps" with your own package name.

<permission android:name="com.example.testmaps.permission.MAPS_RECEIVE" android:protectionLevel="signature"/>
<uses-permission android:name="com.example.testmaps.permission.MAPS_RECEIVE"/>

Now for the <application> tag.

<uses-library android:required="true" android:name="com.google.android.maps" />
<meta-data android:name="com.google.android.maps.v2.API_KEY" android:value="YOUR_KEY_HERE"/>

What's "YOUR_KEY_HERE"? We'll find out now =)

Generating an API key

Ugh, more steps. OK here's the plan. Google wants to keep track of your map usage so you don't abuse their services. At the same time, it has to be secure and tied to your app.

Before you go about generating a key, you have to get the SHA1 of your debug certificate first. Steps will be the same when you generate a key for the production certificate anyway.

To do that, open up a command prompt and change the directory to your Java install folder, and then switch to the "bin" directory.

For example, mine was "C:\Program Files (x86)\Java\jre7\bin".

To generate a SHA1 for debugging:

keytool -list -v -keystore %HOMEPATH%\.android\debug.keystore -alias androiddebugkey -storepass android -keypass android

For production, type this and follow the prompts:

keytool -list -v -keystore path_and_filename_of_keystore -alias your_alias_name
  • When you get this screen, copy the SHA1 line to clipboard.

image

  • Visit Google Code's APIs Console and create a new API project.
  • Under "Services", scroll down to find "Google Maps Android API v2" and enable it
  • Click on "API Access"

image

  • Now click on "Create new Android key..."

image

  • Paste in the SHA1 hash, BUT don't create it yet!
  • You'll also need to append the app package name at the end, separated by a semicolon.
  • So for my case, it's

WH:AT:EV:ER:SH:A1;com.example.testmaps

  • NOW you can create the API key.
  • Copy the API key into your app's AndroidManifest.xml file.

Problem is development and production app signatures differ, so you're going to have to generate keys (at least) twice for each app.

Finally, testing stage!

After all that bloody prep work, rebuild your project just to make sure the settings kick in!

Oh by the way, if you're getting these errors...

Installation error: INSTALL_FAILED_MISSING_SHARED_LIBRARY

Package com.example.testmaps requires unavailable shared library com.google.android.maps; failing!

Note that you can test this on the simulator. You have to change the "target" in the AVD settings to "Google APIs - API Level #" and I'm not sure if that involves wiping the device or not.

image

It's easiest to test on a real device. However if you're using Android x86 then you can simply add Google Play Services to your simulator. I have some notes below, but they're not 100% working due to OpenGL support.

Without further adieu, connect up your device and fire away! The first attempt will most likely fail. Maybe even the second.

Immediately I get these errors in logcat.

Google Maps Android API: Authorization failure.  Please see https://developers.google.com/maps/documentation/android/start for how to correctly set up the map.

Google Maps Android API: Ensure that the following correspond to what is in the API Console: Package Name: com.example.testmaps, API Key: XXX, Certificate Fingerprint: ZZZ

Google Maps Android API: Failed to contact Google servers. Another attempt will be made when connectivity is established.

Google Maps Android API: Failed to load map. Error contacting Google servers. This is probably an authentication issue (but could be due to network errors).

As long as you've got the right API key and certificate fingerprint then you'll be fine by the 3rd attempt.

image
Here's a picture to prove that it works on the 3rd attempt without any changes.

And there you have it. Can you believe that it's finally done!?

I really wish this wasn't such a tedious or difficult setup, but that's the way it has to be. For now anyway.

8r53qvmcvp8pbc1wphv95nnn
We've done it! And the Despicable Me Minions go WILD! \o/

Android x86 simulator testing (work in progress)

If you're using Android x86, you may have some hope. Install GApps (Google Apps) from rootzwiki.com. Make sure you grab the version that's relevant to your Android build!

If you don't have system write access, you'll have to dig up the ISO and overwrite Android x86 with a re-install. Don't worry, your data and app stays the same.

Once that's ready:

  • Download and extract gApps to your PC
  • Using adb through the command prompt, push the files from "system" into your simulator /system
adb connect localhost
cd path/to/your/extracted/gapps_files
adb push system /system

image

  • Reset your VM and it should lead you through a setup wizard
  • Run your app and open the activity which displays the map.

image

  • It should show you a link to install Google Play Services (You may need to set up an account for the device or log into your account)
  • Install it via the Play Store which you should now have on the simulator (via gApps)
  • Close up your app and try it again

And then you get an error:

Google Maps Android API: Google Maps Android API v2 only supports devices with OpenGL ES 2.0 and above

image

That's as far as I got. My VirtualBox/Android x86 setup doesn't support OpenGL ES 2.0 so I couldn't get the maps to render. Simulator works, but it's so slow. If you can find a way around it, feel free to let me know!

Sources

Possibly the best guide I've seen so far comes from a StackOverflow reply by Ramz to "NoClassDefFoundError at Google Play Services V2 library"

And in case you wanted some light reading.

Firefox: Remove "Microsoft DRM" from your plugins

Following up on the Adobe Acrobat post about removing unwanted plugins from Firefox I spotted another that I've left disabled for some time, "Microsoft DRM".

DRM is a plague causing more grief than good to consumers. It also serves as a method to restrict content to countries, even though it's the world-wide web and I have no interest in keeping this shit on my computer.

Normally I wouldn't post this up because at least this DRM plugin stayed disabled in Firefox, however I'm posting it because it's a bit trickier to get rid of.

If you delete "npdrmv2.dll" and "npwmsdrm.dll" from "C:\Program Files\Windows Media Player", it'll automatically restore it from "C:\WINDOWS\system32\dllcache".

This is because it's been embedded deep into the Windows operating system and protected by "Windows File Protection".

  • Copy the contents below into "removeDRM.bat" and save it onto your desktop.

del C:\Windows\System32\dllcache\npdrmv2.dll
del C:\Windows\System32\dllcache\npwmsdrm.dll
del "C:\Program Files\Windows Media Player\npdrmv2.dll"
del "C:\Program Files\Windows Media Player\npwmsdrm.dll"

  • Restart your computer into safe mode.
  • Double click "removeDRM.bat"
  • Restart your computer back into normal mode

That should be it! You can quickly check to see if the files are back, but they shouldn't be.

russian-meteorite-explosion-effect-inside-office
Yeah that's right, get out and STAY out!

Source:

Acrobat: Remove Acrobat plugin from Firefox

Alright Adobe, I'm sick of your shit. I don't want to put up with the laggy scrolling and occasional crashes which come with viewing PDFs in a browser window.

I don't mind reading PDFs outside of a browser, but can you quit fucking around with my browser preferences? When I disable your plugin, I expect it to stay disabled. I don't want it to be re-enabled the next fucking time I load up Acrobat/Firefox!

image
Why is it enabled? I've already clicked disable +20 fucking times!

So, clicking on "More" provided me with information on which DLL file it was using to provide me this "service"; nppdf32.dll

Searching my computer gave me:

  • C:\Program Files\Mozilla Firefox\plugins
  • C:\Program Files\Adobe\Acrobat 9.0\Acrobat\Browser

Delete "nppdf32.dll" (and it's DEU/FRA bits) from both places. Hell, you can even just delete off the "Browser" folder in "Acrobat". There's no playing nice with you. I tried being nice and but you won't listen.

153 
An alternative Twig's Tech Tip on how to deal with Adobe.

Firefox did the world a great favour by displaying PDF's natively, and even though it's not perfect I'm still grateful.

 
Copyright © Twig's Tech Tips
Theme by BloggerThemes & TopWPThemes Sponsored by iBlogtoBlog