Thursday, August 16, 2012

Enable HierarchyViewer on production builds

For security reasons HierarchyViewer does NOT work on production builds. It works only with userdebug and engineering builds (this includes the emulator.)

In this case you’ve got the following errors:
[hierarchyviewer]Unable to get view server protocol version from device
[hierarchyviewer]Unable to debug device

But there is a way to enable the use of HierarchyViewer inside your own application. Romain Guy suggests to use ViewServer class: https://github.com/romainguy/ViewServer

The recommended way to use this API is to register activities when they are created, and to unregister them when they get destroyed:
public class MyActivity extends Activity {
    public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         // Set content view, etc.
         ViewServer.get(this).addWindow(this);
     }

     public void onDestroy() {
         super.onDestroy();
         ViewServer.get(this).removeWindow(this);
    }

     public void onResume() {
         super.onResume();
         ViewServer.get(this).setFocusedWindow(this);
    }
}


To use this view server, your application must require the INTERNET permission:


As a result you will be able to make HierarchyViewer work on any devices:




Sometimes it could be useful to build ViewServer as Library project, as described at:  http://developer.android.com/tools/projects/projects-eclipse.html
And then just referencing it from your application like this:




And once again: do not forget to add INTERNET permission! Otherwise you will got some NullPointerExceptions with ServerSocket.



Thursday, August 2, 2012

Downloading the Android sources behind a proxy

Manual for Windows
Setup

The simplest way to get the Source Tree for Windows is to use Cygwin

1.       Download Cygwin web installer from http://cygwin.com/install.html and run it
2.       You can install packages from Internet using IE proxy settings
3.       Specify the root directory – things will be easier if the path to it won’t contain spaces
4.       In addition to default packages you will need the following:  bash, curl, git and python. The installer will automatically resolve the dependencies.
5.       After installation you will have “Cygwin bash shell” link in Main Menu. Run it
6.       Type cd in command prompt to be sure you are in your home dir – usualy it is your “My Documents”
7.       Create file .bash_profile in this dir with following contents:
export PATH=/usr/bin:~/bin:$PATH
export http_proxy=proxy:port
proxy:port is name and port of your proxy server
8.       Reopen the Cygwin shell and cd to your home dir

Installing a Repo client
Type the following commands in the Cygwin terminal:
mkdir ~/bin
curl --insecure --proxy proxy:port https://dl-ssl.google.com/dl/googlesource/git-repo/repo > ~/bin/repo
chmod a+x ~/bin/repo


Configuring Git
Type the following commands in the shell:
git config --global http.sslverify "false"
git config --global http.proxy "proxy:port"

Getting the files
Now you can follow instructions from http://source.android.com/source/downloading.html 

Manual for Linux
Setup  
  • Add the following line to the .bashrc in your home directory, replace proxy and port with your settings: 
export PATH=~/bin:$PATH
export http_proxy=proxy:port 
  • Run  
source .bashrc

Installing a Repo client

Type the following commands in the shell

mkdir ~/bin
curl --proxy proxy:port https://dl-ssl.google.com/dl/googlesource/git-repo/repo > ~/bin/repo
chmod a+x ~/bin/rep

Configuring Git

Add proxy to Git
git config --global http.proxy "proxy:port"

Getting the files
Now you can follow instructions from http://source.android.com/source/downloading.html 

Tuesday, July 17, 2012

Android NDK


The NDK is a toolset that allows you to implement parts of your app using native-code languages such as C and C++ (http://developer.android.com/tools/sdk/ndk/index.html)
You will also need Eclipse CDT Plugin installed to build C/C++ native part from Eclipse. To install it use menu Help -> Install New Software..., then setup CDT Main Features from http://download.eclipse.org/tools/cdt/releases/juno URL.
The Java Native Interface
 Programmer's Guide and Specification
(http://java.sun.com/docs/books/jni/html/start.html)
shows a simple example of using the Java Native Interface:


Let’s go through all these steps.

1.     Create a class that declares the native method

Let’s create simple HelloWorld Android application and modify HelloWorldActivity.java in the following way:
public class HelloWorldActivity extends Activity {

    public native String getMyData();

    static {
        System.loadLibrary("mylib");
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_hello_world);

        setTitle(getMyData());
    }
}
In Eclipse, create a new directory named jni at the project's root using menu File | New | Folder.
Inside the jni directory, create a new file named Android.mk using menu File | New | File. If CDT is properly installed, the file should have the following specific icon in the Package Explorer view

Write the following content into this file. Basically, this describes how to compile our native library named mylib which is composed of one source file the helloworld.c:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE    := mylib
LOCAL_SRC_FILES := helloworld.c
include $(BUILD_SHARED_LIBRARY
As project files for native compilation are ready, we can write the expected native source code. Although the C implementation file must be written by hand, the corresponding header file can be generated with a helper tool provided by the JDK: javah.

2.     Build project

Execute “Build Project” command to generate *.class files under HelloWorld/bin/classes directory

3.     Generate header file

-       Open project Properties window and go to the Builder section



-       Click on New… and create a new builder of type Program.


In Location select javah.exe file using Browse File System… button.
For Working Directory select your project bin/classes folder using Browse Workspace… button.
In Arguments add:
-o ${workspace_loc:/HelloWorld/jni}/helloworld.h julia.mlinnik.helloworld.HelloWorldActivity

such arguments will result in creating HelloWorld/jni/helloworld.h header.

You can use –d option instead of –o:
-d ${workspace_loc:/HelloWorld/jni} julia.mlinnik.helloworld.HelloWorldActivity

that will generate TestJni/jni/julia_mlinnik_helloworld_HelloWorldActivity.h header.

-       Validate and position it after Java Builder in the list (because JNI files are generated from Java .class files).
helloworld.h should have the following content:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class julia_mlinnik_helloworld_HelloWorldActivity */

#ifndef _Included_julia_mlinnik_helloworld_HelloWorldActivity
#define _Included_julia_mlinnik_helloworld_HelloWorldActivity
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     julia_mlinnik_helloworld_HelloWorldActivity
 * Method:    getMyData
 * Signature: ()Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_julia_mlinnik_helloworld_HelloWorldActivity_getMyData
  (JNIEnv *, jobject);

#ifdef __cplusplus
}
#endif
#endif

4.    Write the C implementation of the native method
Create helloworld.c file under jni directory:
#include "helloworld.h"
JNIEXPORT jstring Java_julia_mlinnik_helloworld_HelloWorldActivity_getMyData
    (JNIEnv* pEnv, jobject pThis)
{
    return (*pEnv)->NewStringUTF(pEnv,
                                 "My native project talks C++");
}

5.    Compile C code and generate native library
Let’s convert our project to C/C++ project. Open menu File | New | Other....


Check your project, choose MakeFile project and Other Toolchain and finally click on Finish.





Open your project Properties. In the C/C++ Build section, uncheck Use default build command and enter ${NDKROOT}/ndk-build.cmd as a Build command. NDKROOT is environment variable containing the path to Android NDK in your system.
Validate by clicking on OK:

But now, we notice that the include statement of jni.h file is underlined in yellow. This is because it was not found by the CDT Indexer for code completion. Note that the compiler itself resolves them since there is no compilation error. Indeed, the indexer is not aware of NDK include paths, contrary to the NDK compiler.

Go to section C/C++ General/Paths and Symbols and then in Includes tab.

Click on Add... and enter the path to the directory containing this include file which is located inside NDK's platforms directory:

${NDKROOT}/platforms/android-9/arch-arm/usr/include
${NDKROOT}/sources/cxx-stl/gnu-libstdc++/include

${NDKROOT}/toolchains/arm-linux-androideabi-4.4.3/prebuilt/windows/lib/gcc/arm-linux-androideabi/4.4.3/include
${ProjDirPath}/../../sdk/native/jni/include

 When Eclipse proposes to rebuild its index, say Yes





Finally check that Builders have the following order:
Main idea:
javah after Java Builder
CDT Builder after javah and before Android Package Build
6.    Run application
After all you see the following screen:





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.


External Tools for database review


Sometimes it may be helpful to inspect content of database with external tools.
I would recommend the following tools:

-       SQLite Database Browser (http://sqlitebrowser.sourceforge.net/) is a freeware, public domain, open source visual tool used to create, design and edit database files compatible with SQLite.


 
-       CellObject Android DevTools (http://www.questoid.com/is an Eclipse plug-in tool built on top of Dalvik Debug Monitor Server (DDMS), intended for Android application developers to browse SQLite and shared preferences XML files on Android device emulator.
You can select database in File Explorer View and press SQLite Browser icon (marked with red circle)

 
 
Questoid SQLite Browser View will be opened:


Some examples of query requests to Content Provider


Query() function in Content Provider has the following signature:

public abstract Cursor query (Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) 

Parameters
uri
The URI to query. This will be the full URI sent by the client; if the client is requesting a specific record, the URI will end in a record number that the implementation should parse and add to a WHERE or HAVING clause, specifying that _id value.
projection
The list of columns to put into the cursor. If null all columns are included.
selection
A selection criteria to apply when filtering rows. If null then all rows are included.
selectionArgs
You may include ?s in selection, which will be replaced by the values from selectionArgs, in order that they appear in the selection. The values will be bound as Strings.
sortOrder
How the rows in the cursor should be sorted. If null then the provider is free to define the sort order.

Using SUM function 
SUM function should be placed in projection parameter:

String[] projection = new String[] { "sum(" + columnName + ")" };
Cursor cur = getContentResolver().query(                  DataContentProvider.CONTENT_URI, projection, selection, null,
                null);
int columnSum = cur.getInt(0);

Using GROUP BY clause
GROUP BY should be placed in selection parameter:

String[] projection = new String[] { columnNameA,
                "sum(" + columnNameB + ") AS total_B" };
String  selection = columnNameA + " LIKE 'A'"
                + ") GROUP BY (" + columnNameA;

or

String selection = "0 == 0) GROUP BY (" + columnNameA;


Let’s assume we have the following table:


The SQL query
SELECT CATEGORY, SUM(ON_HAND) AS TOTAL_ON_HAND
 FROM COMPACT_DISC_STOCK
 GROUP BY CATEGORY;

Could be written using Content Provider as:

String[] projection = new String[] { DataContentProvider.CATEGORY,
                "SUM(" + DataContentProvider.ON_HAND + ") AS TOTAL_ON_HAND" };
        String selection = "0 == 0) GROUP BY (" + DataContentProvider.CATEGORY;
        Cursor cur = getContentResolver().query(
                DataContentProvider.CONTENT_URI, projection, selection, null,
                null);

and we will get the following result table:

CATEGORY         TOTAL_ON_HAND
Instrumental                  34
Vocal                               26

The SQL query:
SELECT CATEGORY, SUM(ON_HAND) AS TOTAL_ON_HAND
 FROM COMPACT_DISC_STOCK
 WHERE PRICE < 11.00
 GROUP BY CATEGORY;
 
Could be written using Content Provider as:

String[] projection = new String[] { DataContentProvider.CATEGORY,
                "SUM(" + DataContentProvider.ON_HAND + ") AS TOTAL_ON_HAND" };
        String selection = DataContentProvider.PRICE + " < 11.00) GROUP BY (" + DataContentProvider.CATEGORY;
        Cursor cur = getContentResolver().query(
                DataContentProvider.CONTENT_URI, projection, selection, null,
                null);
and we will get the following result table:

CATEGORY         TOTAL_ON_HAND
Instrumental                  23
Vocal                               8

Using Date and Time in queries

For example we have table where one of the columns has type DATE
db.execSQL("CREATE TABLE " + ACTIVITIES_TABLE_NAME + " (" + _ID
                    + " INTEGER PRIMARY KEY AUTOINCREMENT, "
                    + ACTIVITY_NAME + " TEXT NOT NULL, "
                    + START_DATE + " DATE DEFAULT CURRENT_TIMESTAMP, "
                    + DURATION + " INTEGER" + ");");


We have cursor:

Cursor cur = getContentResolver().query(
                DataContentProvider.CONTENT_URI, null, null, null, null);


We could receive date with the following methods:

String dateTime = cur.getString(2);
Date date;
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
try {
    date = sdf.parse(dateTime);
} catch (ParseException e) {
    Log.e(TAG, "Parsing datetime failed", e);
}






 


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.