Friday, December 16, 2011

Content Providers



 Content providers expose their data as a simple table on a database model, where each row is a record and each column is data of a particular type and meaning. So you can create Content Provider that will work with database stored under /data/data/package_name/databases directory.


Thursday, December 15, 2011

Cancel the widget placement on pressing back key in AppWidgetConfigure Activity


As described in http://developer.android.com/guide/topics/appwidgets/index.html#Configuring, if you would like the user to configure settings when he or she adds a new App Widget, you can create an App Widget configuration Activity.

The configuration Activity should be declared in the Android manifest file in the following way:

<activity android:name=".ExampleAppWidgetConfigure" >
            <intent-filter >
                <action  android:name="android.appwidget.action.APPWIDGET_CONFIGURE" />
            </intent-filter>
</activity>

Also, the Activity must be declared in the AppWidgetProviderInfo XML file, with the android:configure attribute:

<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
          ...
          android:configure="com.example.android.ExampleAppWidgetConfigure" >
          ...
</appwidget-provider>

There is a special tip concerning exiting from Configuration Activity by pressing back key:

Tip: When your configuration Activity first opens, set the Activity result to RESULT_CANCELED. This way, if the user backs-out of the Activity before reaching the end, the App Widget host is notified that the configuration was cancelled and the App Widget will not be added.

And there is also link to the ExampleAppWidgetConfigure.java sample class in ApiDemos.

Let’s look at this example code:

public void onCreate(Bundle icicle) {
        super.onCreate(icicle);

        // Set the result to CANCELED. This will cause the widget host to cancel
        // out of the widget placement if they press the back button.
        setResult(RESULT_CANCELED);

But actually this code doesn’t results in calling AppWidgetProvider.onDeleted. So widget neither was added to home screen completely nor was deleted also.

We could find the explanation of this behavior in Launcher.onActivityResult code:

} else if ((requestCode == REQUEST_PICK_APPWIDGET || requestCode == REQUEST_CREATE_APPWIDGET)
            && resultCode == RESULT_CANCELED && data != null) {
        // Clean up the appWidgetId if we canceled
        int appWidgetId = data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1);
        if (appWidgetId != -1) {
            mAppWidgetHost.deleteAppWidgetId(appWidgetId);
        }
    }

Now it is clear that we should extra data to Intent if we want to delete the widget properly:

Intent resultValue = new Intent();
resultValue.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mAppWidgetId);
setResult(RESULT_CANCELED, resultValue);


There were proposed some solutions like removing widget explicitly in onPause() event and some similar. But actually the right way to remove widget is to pass back a new Intent() that includes the EXTRA_APPWIDGET_ID. (This is so the homescreen can clean up discarded ids when canceled.) See details at: http://code.google.com/p/android/issues/detail?id=2539

The other interesting question is what happens when user presses “Home” key in Configuration Activity, widget can stay in the same state – not added nor deleted. It seems that it is up to developer to work up this case properly. 


Wednesday, December 14, 2011

Media Scanner Applicaton


Sometimes I prefer to use adb push to send some images to my phone. To make new photos visible in Gallery we need to run media scan. One way to do it is to unmount/mount storage, but I specially use adb to avoid remounting for some purposes.

Simple application Media Scanner allows running media scan at any time.

To trigger media scan process the following intent is broadcasted:

new Intent(
                Intent.ACTION_MEDIA_MOUNTED,
                Uri.parse("file://" + Environment.getExternalStorageDirectory()))


I decided to add some animation to make application more attractive and nice-looking.

Animation in application is based on ViewFlipper class. It can automatically flip between each child at a regular interval. So we could control it using startFlipping() and stopFlipping() functions.

Application will look like at screenshots:


















I have prepared several images of circled arrows to make rotation effect.
 
Also I add widget that could be added to Home Screen.

App Widget layouts are based on RemoteViews that results in some restrictions on way to implement animation.
In my case I choose ProgressBar.

<ProgressBar
            android:layout_width="60dp"
            android:layout_height="60dp"
            android:layout_gravity="center"
            android:indeterminateDrawable="@drawable/widget_progress_animation"
            android:indeterminateOnly="true" />

where indeterminateDrawable is specified as 

<animated-rotate xmlns:android="http://schemas.android.com/apk/res/android"
    android:drawable="@drawable/widget_refresh"
    android:pivotX="50%"
    android:pivotY="50%" />
 
and widget_refresh is png image.

RemoteViews allow controlling visibility of ProgressBar in my WidgetProvider.

I’ve faced with some issues during testing Media Scanner:

1.   During testing widget with Froyo version I’ve got the exception:
android.widget.RemoteViews$ActionException: view: android.widget.ProgressBar can't use method with RemoteViews: setVisibility(int)
 
that was fixed.

The deal was that the View.setVisibility() method is marked with the @android.view.RemotableViewMethod annotation, which is what allows it to be called through RemoteViews.  In this case, ProgressBar actually overrides the default View implementation, but without adding that annotation. This is why it throws the exception. (see http://www.mail-archive.com/android-developers@googlegroups.com/msg50097.html )

 
2.      I own Nexus One and Samsung Galaxy S II devices.
Application seems to work fine with Galaxy, but it has some strange behavior on Nexus. I have noticed that widget received broadcast intent ACTION_MEDIA_SCANNER_STARTED with some delay (about 1-2 secs).
 
12-13 13:39:44.643  3189   3189   I      BootReceiver Got intent with action android.intent.action.MEDIA_MOUNTED
12-13 13:39:44.653  3219   3219   I      MediaUploader No need to wake up
12-13 13:39:44.693  3117   3117   I      AppsOrganizerMountReceiver       android.intent.action.MEDIA_MOUNTED
12-13 13:39:44.793  3198   3271   D      MediaScannerService start scanning volume external
12-13 13:39:44.803  3152   3152   D      TEST   ACTIVITY======= onReceive ==========android.intent.action.MEDIA_SCANNER_STARTED
12-13 13:39:45.223  3198   3271   D      MediaScanner prescan time: 90ms
12-13 13:39:45.223  3198   3271   D      MediaScanner     scan time: 291ms
12-13 13:39:45.223  3198   3271   D      MediaScanner postscan time: 2ms
12-13 13:39:45.223  3198   3271   D      MediaScanner    total time: 383ms
12-13 13:39:45.223  3152   3152   D      TEST   ACTIVITY======= onReceive ==========android.intent.action.MEDIA_SCANNER_FINISHED
12-13 13:39:45.233  3198   3271   D      MediaScannerService done scanning volume external
12-13 13:39:46.243  3152   3152   D      TEST   ======= onReceive ==========android.intent.action.MEDIA_SCANNER_STARTED
12-13 13:39:46.253  3189   3189   I      BootReceiver Got intent with action android.intent.action.MEDIA_SCANNER_FINISHED
12-13 13:39:46.253  3152   3152   D      TEST   ======= onReceive ==========android.intent.action.MEDIA_SCANNER_FINISHED

I saw in logs that widget received START intent after activity (when it is in foreground) already received FINISHED. The delay can be seen also when activity is paused and does not listen for broadcast intents.
It looks like somebody in system also registered for media broadcast perform long-running operations in onReceive() method.

First of all I pay attention to the following line in log:
12-13 13:39:44.693  3117   3117   I      AppsOrganizerMountReceiver       android.intent.action.MEDIA_MOUNTED

This is log from AppsOrganizer application that I’ve installed sometimes ago. After removing it from Nexus issue disappeared. So it was the root cause of the delay.

AppsOrganizer is Open Source Project

@Override
    public void onReceive(Context context, Intent intent) {
        Log.i("AppsOrganizerMountReceiver", intent.getAction());
        try {
            // aspetto un secondo, potrebbero non essere ancora disponibili le
            // app su sd
            Thread.sleep(1000);
        } catch (InterruptedException e) {
        }
        DatabaseHelper dbHelper = DatabaseHelper.initOrSingleton(context);
        ApplicationInfoManager.reloadAll(context.getPackageManager(), dbHelper,
                null, false, null);
        // rimetto first time in modo da far ricaricare la lista delle
        // applicazioni
        SplashScreenActivity.firstTime = true;
    }

we could see that there is: Thread.sleep(1000) in onReceive and then some operations with DatabaseHelper that lead to delay in my widget.


Application source code: http://code.google.com/p/mediascanner/   


Android Market: https://market.android.com/details?id=com.jmlinnik.android.mediascanner 

 


Клавиатура для ввода паролей


Очень часто я в качестве основы для паролей использую русские слова в английской раскладке. Например, слово “Привет” при этом превращается в “Ghbdtn”. Но клавиатуры на смартфонах, в отличие от ноубуков,  не имеют дублированных букв на клавишах, что иногда затрудняет ввод пароля. Идею такой клавиатуры я впервые встретила у одного талантливого программиста – Димы Корнилова: http://dimakornilov.ru/blog/?p=1231

За основу была взята SoftKeyboard из примеров Android SDK. Мне хотелось иметь полноценную символьную раскладку, так как иногда использую специфичные символы в паролях, и стандартное поведение клавиши Shift – обычный Shift при одинарном нажатии и поведение Caps Lock при двойном нажатии.

Некоторые детали реализации:

1.    Отображение английских букв на русские клавиши:
В xml/qwerty.xml раскладке русским буквам на клавишах должны соответствовать коды клавиш из английской раскладки:

        <Key android:codes="113" android:keyLabel="й" android:keyEdgeFlags="left"/>
        <Key android:codes="119" android:keyLabel="ц"/>
        <Key android:codes="101" android:keyLabel="у"/>
        <Key android:codes="114" android:keyLabel="к"/>
        <Key android:codes="116" android:keyLabel="е"/>
        <Key android:codes="121" android:keyLabel="н"/>
 
Здесь "113" – символ “q” в Unicode кодировке, "119" – символ “w”

2.    Использование собственного фона для клавиш, чтобы внешне отличать клавиатуру, например от стандарной Android клавиатуры.
Для этого в layout/input.xml у KeyboardView просто указать нужные ресурсы:

android:background="@drawable/keyboard_background
android:keyBackground="@drawable/btn_keyboard_key"
 
3.    Реализация стандартного поведения клавиши Shift

В Java – коде для этого добавлены три состояния клавиши – On, Off и Locked.
Класс android.inputmethodservice.Keyboard.Key содержит поля label и on, которые можно менять в зависимости от состояния клавиши. 
 Доступ к самой Shift клавиши можно получить с помощью следующего кода:
mShiftKey = mQwertyKeyboard.getKeys().get(
                mQwertyKeyboard.getShiftKeyIndex());
Учитывая, что у клавиш должен меняться фон при нажатии, можно использовать следующий selector для этого:
<selector xmlns:android="http://schemas.android.com/apk/res/android" >

    <!-- Toggle keys. Use checkable/checked state. -->

    <item android:state_checkable="true" android:state_checked="true"
          android:state_pressed="true"
          android:drawable="@drawable/key_toggle_pressed_on" />
    <item android:state_checkable="true" android:state_pressed="true"
          android:drawable="@drawable/key_toggle_pressed_off" />
    <item android:state_checkable="true" android:state_checked="true"
          android:drawable="@drawable/key_toggle_normal_on" />
    <item android:state_checkable="true"
          android:drawable="@drawable/key_toggle_normal_off" />

    <!-- Normal keys -->
    <item android:state_pressed="true"
          android:drawable="@drawable/key_background_pressed" />
    <item android:drawable="@drawable/key_background_normal" />

</selector>
В результате получим вот такую клавиатуру:
  












После установки приложения надо включить клавиатуру в настройках:















Теперь она доступна из контекстного меню любого текстового поля (долгое нажатие на текстовом поле):
 










Исходный код можно посмотреть на http://code.google.com/p/passwordkeyboard/



Friday, December 2, 2011

Keystores

------ General notes:  -----------------------------

 - The quick way to sign existing apk:
java -jar signapk.jar platform.x509.pem platform.pk8 app.apk signed_app.apk 
You can find signapk.jar from : out/host/linux-x86/framework/signapk.jar
You can find platform.x509.pem and platform.pk8 : build/target/product/security


- How to generate keystore to sign apk from eclipse:
openssl pkcs8 -inform DER -nocrypt -in platform.pk8 -out platform.pem
openssl pkcs12 -export -in platform.x509.pem -out platform.p12 -inkey platform.pem -password pass:android -name androiddebugkey
keytool -importkeystore -deststorepass android -destkeystore ./platform.jks -srckeystore ./platform.p12 -srcstoretype PKCS12 -srcstorepass android

 ------------------------------------------------------
The Android system requires that all applications must be signed. Detailed description about signing applications could be found at http://developer.android.com/guide/publishing/app-signing.html

Google provide the following standard test keys:
testkey -- a generic key for packages that do not otherwise specify a key.
platform -- a test key for packages that are part of the core platform.
shared -- a test key for things that are shared in the home/contacts process.
media -- a test key for packages that are part of the media/download system.

They are located at: android\build\target\product\security
There is mentioned that these test keys are used strictly in development.
Usually they are used to sign applications when you create “eng” platform build.
To sign application in Eclipse it is necessary to create keystores from keys.

How to create keystores from keys

openssl  (cryptography and SSL/TLS toolkit) and keytool (Key and Certificate Management Tool from JDK) will be used.

1. run keys_cr.sh in android\build\target\product\security to generate *.p12 files
#!/bin/sh

FILES=`find . -name "*.pk8"`

for FILE in $FILES 
do
FILE_NAME=`echo $FILE | awk -F.pk8 '{print $1}'`
if [ -f ${FILE_NAME}.pem ] 
then
    echo "file ${FILE_NAME}.pem exists"
else
`openssl pkcs8 -inform DER -nocrypt -in ${FILE} -out ${FILE_NAME}.pem`
fi

`openssl pkcs12 -export -in ${FILE_NAME}.x509.pem -out ${FILE_NAME}.p12 -inkey ${FILE_NAME}.pem -password pass:android -name androiddebugkey`

done
 
2. use the following commands to create keystores:
keytool -importkeystore -deststorepass android -destkeystore ./shared.jks -srckeystore ./shared.p12 -srcstoretype PKCS12 -srcstorepass android
keytool -importkeystore -deststorepass android -destkeystore ./testkey.jks -srckeystore ./testkey.p12 -srcstoretype PKCS12 -srcstorepass android
keytool -importkeystore -deststorepass android -destkeystore ./media.jks -srckeystore ./media.p12 -srcstoretype PKCS12 -srcstorepass android
keytool -importkeystore -deststorepass android -destkeystore ./platform.jks -srckeystore ./platform.p12 -srcstoretype PKCS12 -srcstorepass android

Using Keytool IUI to create keystore

Keytool IUI is Cryptography GUI tool (http://code.google.com/p/keytool-iui/). It allows create, manage keys and certificates, encrypt and decrypt files.

1.    Keytool IUI -> Create -> Keystore. Select path and set the name of keystore file, set password “android”
2.    Click “Ok”

3.    Keytool IUI -> Import-> Keystore’s entry -> Private key -> PEM file format
Private key file: platform.pem
-    Create platform.pem from platform.pk8 with the following command:
openssl pkcs8 -inform DER -nocrypt -in platform.pk8 -out platform.pem
Certificates chain file: platform.x509.pem
Keystore file: path to keystore created at point 1
Keystore password: android


4.    Enter new private key entry’s alias: androiddebugkey, password: android


View keystores in Keytool IUI

You can open existing keystores in Keytool IUI using “View keystores” menu.


Use mouse right-click on entry’s row to display popup menu



Certificate fingerprints (MD5) for test keys:

To get MD5 from generated keystore (password: android):
keytool -list -keystore platform.jks
The following fingerprints were generated for Gingerbread testkeys:

platform: Certificate fingerprint (MD5): 8D:DB:34:2F:2D:A5:40:84:02:D7:56:8A:F2:1E:29:F9
test: Certificate fingerprint (MD5): E8:9B:15:8E:4B:CF:98:8E:BD:09:EB:83:F5:37:8E:87
media: Certificate fingerprint (MD5): 19:00:BB:FB:A7:56:ED:D3:41:90:22:57:6F:38:14:FF
shared: Certificate fingerprint (MD5): 5D:C8:20:1F:7D:B1:BA:4B:9C:8F:C4:41:46:C5:BC:C2

How to determine which certificate was used to sign apk

There are at least two ways to determine the certificate that was used to sign application:
1.    Certificate is defined in Android.mk from Application Source folder:
LOCAL_CERTIFICATE := platform

2.    Certificate could be known from *.apk file
-    Unzip apk file and extract META-INF\CERT.RSA file
-    Check md5 with the following command: keytool -printcert -file CERT.RSA

Add Keystore to Eclipse

1.    Window -> Preferences, Android -> Build
2.    Browse your keystore in “Custom debug keystore”
3.    Clean the project to apply new keystore


Another way to sign application

java -jar signapk.jar platform.x509.pem platform.pk8 app.apk signed_app.apk

signapk.jar is located at out\host\linux-x86\framework\signapk.jar


Using Google Maps in application

The Google Maps service requires that each MapView identify itself to the service using a Maps API Key. Before providing Maps tiles to a MapView, the service checks the Maps API Key supplied by the MapView to ensure that it:

•    References a certificate/developer registered with the service, and
•    References a certificate that matches the certificate with which the application (containing the MapView) was signed.

Unless both conditions are met, the service does not provide Maps tiles to the MapView.
Detailed information can be found at: http://code.google.com/android/add-ons/google-apis/mapkey.html
So Maps API Key is generated based on MD5 certificate fingerprint. And you should get new API Key in case of certificate is changed.

Note: if application is a system application and it is located in /system/app folder then there is no matter what API Key is used. System applications have some privileges and there is no check for API key during access to Google Maps Service.