Showing posts with label TelephonyManager. Show all posts
Showing posts with label TelephonyManager. Show all posts

Saturday, July 7, 2012

whoami

Quite often you application must generate unique identifier for current instance of application to create server account linked with current device. Ideally, this identifier should persists on application reinstallation, so even after application being deleted and installed again the same account will be used for application on current device.

The simplest (and, probably, the most correct) approach is to use TelephonyManager#getDeviceId.
 mDeviceId = ((TelephonyManager)context.getSystemService(Context.TELEPHONY_SERVICE)).getDeviceId()  
Unfortunately, deviceId may be null for devices w.o. GSM or CDMA module. Another way is to use Serial Number. This identifier is available via Build#SERIAL from android api 9. So, to cover older os versions let's use reflection:
 Class<?> c = Class.forName("android.os.SystemProperties");  
 Class<?>[] paramTypes= new Class[1];  
 paramTypes[0]= String.class;  
 Method get = c.getMethod("get", paramTypes);  
 Object[] params= new Object[1];  
 params[0]= new String("ro.serialno");  
 mDeviceId = (String) get.invoke(c, params);  
Serial identifier is quite 'unpredictable' and may be absent on Samsung devices, so we have to have more options to cover much more devices. Next step will be Secure#ANDROID_ID - randomly generated 64-bit number on the device's first boot:
 mDeviceId = Secure.getString(context.getContentResolver(), Secure.ANDROID_ID);  
But even this value sometimes is null (unfortunately, I don't remember devices on which this problem can be reproduced). Furthermore, this values will be changed upon factory reset.
It is also possible to use wi-fi mac address as identifier for application instance:
 mDeviceId = ((WifiManager)context.getSystemService(Context.WIFI_SERVICE)).getConnectionInfo().getMacAddress();  
But not all devices have wi-fi. Also, if the wi-fi is not turned on, the hardware may not report the Mac address (null will be returned). As a last resort identifier can be generated using UUID#randomUUID. This is mutable value, but it can be stored somewhere on sdcard to persist it's value after reinstallation. Probably, it's all available options to get unique id. Hopefully, order of presentation provides correct 'brute-force' algorithm of searching identifier.

Thursday, April 12, 2012

Where am I?

When your mobile application requires registration quite often user's location become a relevant question. It's very convenient when registration form contains your location (at least country) preselected. I know at least 3 ways to do this in Android (each with its own advantages and disadvantages):
  1. using Locale:
  2.      Locale myLocale = Locale.getDefault();      
    
    This is the easiest and probably most incorrect way. Actually, locale represents not current user's language/country combination, but values selected in Android Settings. For example, when selected locale is English (South Africa) your locale object will tell you, that your country code (Locale#getCountry) is ZA and your country name (Locale#getDisplayCountry) is South Africa. In principle, all is correct. But it's possible to select not your current locale. Moreover, your locale may not be available on your device. In this case it's not possible to determine your country using this approach. So that the only advantage of this approach is ease of implementation. Also no need for internet connection in this case but it is unlikely that this is a great advantage.
  3. using TelephonyManager:
  4.      TelephonyManager telephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);  
         String iso = telephonyManager.getNetworkCountryIso();   
    
    TelephonyManager#getNetworkCountryIso due to documentation:
    Returns the ISO country code equivalent of the current registered operator's MCC (Mobile Country Code). Availability: Only when user is registered to a network. Result may be unreliable on CDMA networks (use getPhoneType() to determine if on a CDMA network).
    So, it's good technique, but requirement to be registered in the mobile network is a very serious limitation especially for tablets.
  5. using LocationManager:
  6. Imho, the most accurate solution which based on real geographical location. It's requires internet connection and permission to collect your location data:


    It also requires permission in manifest:
     <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>  
    

    And finally:
         LocationManager mlocManager = (LocationManager)getSystemService(Context.LOCATION_SERVICE);  
         LocationListener mlocListener = new LocationListener() {  
           @Override  
           public void onLocationChanged(Location location) {  
             final Geocoder geocoder = new Geocoder(GApp.sInstance.getApplicationContext());  
             try {  
               List<Address> addresses = geocoder.getFromLocation(location.getLatitude(), location.getLongitude(), 1);  
               if (addresses != null) {  
                 Address address = addresses.get(0);  
               }  
             } catch (Exception e) {  
               e.printStackTrace();  
             }  
           }  
           @Override  
           public void onStatusChanged(String s, int i, Bundle bundle) {  
           }  
           @Override  
           public void onProviderEnabled(String s) {  
           }  
           @Override  
           public void onProviderDisabled(String s) {  
           }  
         };  
         mlocManager.requestLocationUpdates( LocationManager.NETWORK_PROVIDER, 0, 0, mlocListener);  
    
    It is a little bit more complicated than the previous two methods, but using Address we are able to identify not just current country, but also city (Address#getLocality) and even some thoroughfare (Address#getThoroughfare).