Affichage des articles dont le libellé est Android. Afficher tous les articles
Affichage des articles dont le libellé est Android. Afficher tous les articles

dimanche 6 octobre 2013

Joyn or RCS

RCS (Rich Communication Services) is a GSMA standard that aims to bring a set of rich communication (that goes beyond SMS and phone calls) yet inter-operable services  across different domains managed by different telecom operators. This telcos standard is marketed under the name of Joyn.
Many operators has already deployed on their networks offering users VoIP and presence services that can be accessed by installing an application from the market store (Google Play, AppStore). In addition, some smarphone manufacturers who have joined the movement already embed the RCS stack into their devices.

The next step of commercializing Joyn is to build an ecosystem by providing APIs and empowering the developers community to create communication-based applications that relies on the platform. Orange through Orange Partner and Deutsch Telekom through the Developer Garden programs are leading these efforts in Europe. For instance, they jointly sponsored the Joyn Hackathon (press release) were the Joyn API  was introduced.

The remaining of this post explains how to use the Android Joyn SDK to build conversational applications. The overall interaction between an application and the Joyn SDK (and behind the RCS platform) is explained in the following figure.
Joyn API call flow

  1. Instantiate Joyn service and establish a connection
   private ChatService mService;
   private JoynServiceListener mListener = new JoynServiceListener() {
      @Override public void onServiceDisconnected(int error) {
         Log.i(TAG, "ChatService disconnected!");
      }
      @Override public void onServiceConnected() {
         Log.i(TAG, "ChatService connected!");
      }
   };
   ...
   // Instanciate API
   mService = new ChatService(getApplicationContext(), mListener); 
   // Connect API
   mService.connect();
  1. When the the connection is successfully established then start calling API methods
   private Chat mChat;
   private ChatListener mChatListener = new ChatListener() {
      @Override public void onReportMessageFailed(String arg0) {}
      @Override public void onReportMessageDisplayed(String arg0) {}
      @Override public void onReportMessageDelivered(String arg0) {}
      @Override public void onNewMessage(ChatMessage arg0) {}
      @Override public void onComposingEvent(boolean arg0) {}
   };
   ...
   @Override public void onServiceConnected() {
      Log.i(TAG, "ChatService connected!");
      if (mService != null && mService.isServiceRegistered()) {
         // Get remote contact
         String contact = getIntent().getStringExtra("contact");
         // Call API Methods
         mChat = mService.openSingleChat(contact, mChatListener); 
         mChat.sendMessage("hello world!");
      }
   }
The API doc of the ChatService can be found on this link.

dimanche 9 juin 2013

Adding search capabilities to an Android application

To set up a search assistant in an application, you need to go through the following steps :
  • Define a searchable configuration: An XML file that configures some settings for the search dialog or widget. It includes settings for features such as hint text, search suggestion, voice search, etc.
  • A searchable activity that will receive the search query, to perform the search on the application data data, then to display the results.
  • A search interface, provided by either: a search dialog that will appear at the top of the screen when the user presses the device SEARCH button (if available), it can also called programmatically from the code, or a SearchView widget.
First, the XML searchable configuration res/xml/searchable.xml:
<?xml version="1.0" encoding="utf-8"?>
<searchable xmlns:android="http://schemas.android.com/apk/res/android"
    android:label="@string/search_label"
    android:hint="@string/search_hint"
    android:searchSuggestAuthority="com.fontself.sms.provider.ContactProvider"    
    android:searchSuggestIntentAction="android.intent.action.VIEW">
</searchable> 

Second, the ContentProvider that will be called by the Android system to perform search:
public class MyContentProvider extends ContentProvider {
   public static String AUTHORITY = "com.app.package.MyContentProvider";
   private static final int SEARCH_SUGGEST = 0;
   private static final int SHORTCUT_REFRESH = 1;
   private static final UriMatcher sURIMatcher = buildUriMatcher();

   private static final String[] COLUMNS = {
      "_id",  // must include this column
      SearchManager.SUGGEST_COLUMN_TEXT_1,
      SearchManager.SUGGEST_COLUMN_TEXT_2,
      SearchManager.SUGGEST_COLUMN_INTENT_DATA,     
   };
   private static UriMatcher buildUriMatcher() {
      UriMatcher matcher =  new UriMatcher(UriMatcher.NO_MATCH);
      matcher.addURI(AUTHORITY, SearchManager.SUGGEST_URI_PATH_QUERY, SEARCH_SUGGEST);
      matcher.addURI(AUTHORITY, SearchManager.SUGGEST_URI_PATH_QUERY + "/*", SEARCH_SUGGEST);
      matcher.addURI(AUTHORITY, SearchManager.SUGGEST_URI_PATH_SHORTCUT, SHORTCUT_REFRESH);
      matcher.addURI(AUTHORITY, SearchManager.SUGGEST_URI_PATH_SHORTCUT + "/*", SHORTCUT_REFRESH);
      return matcher;
   }
   @Override public String getType(Uri uri) {
      switch (sURIMatcher.match(uri)) {  
      case SEARCH_SUGGEST:
         return SearchManager.SUGGEST_MIME_TYPE;
      case SHORTCUT_REFRESH:
         return SearchManager.SHORTCUT_MIME_TYPE;
      default:
         throw new IllegalArgumentException("Unknown URL " + uri);
      }
   }
   @Override public boolean onCreate() {  
      return false;
   }
   @Override
   public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {  
      if (!TextUtils.isEmpty(selection)) {
         throw new IllegalArgumentException("selection not allowed for " + uri);
      }
      if (selectionArgs != null && selectionArgs.length != 0) {
         throw new IllegalArgumentException("selectionArgs not allowed for " + uri);
      }
      if (!TextUtils.isEmpty(sortOrder)) {
         throw new IllegalArgumentException("sortOrder not allowed for " + uri);
      }
      switch (sURIMatcher.match(uri)) {
         case SEARCH_SUGGEST:
            String query = null;
            if (uri.getPathSegments().size() > 1) {
               query = uri.getLastPathSegment().toLowerCase();
            }
            return getSuggestions(query, projection);
         case SHORTCUT_REFRESH:
            String shortcutId = null;
            if (uri.getPathSegments().size() > 1) {
               shortcutId = uri.getLastPathSegment();
            }
            return refreshShortcut(shortcutId, projection);
         default:
            throw new IllegalArgumentException("Unknown URL " + uri);
      }  
   }
   private Cursor getSuggestions(String query, String[] projection) {
      String processedQuery = query == null ? "" : query.toLowerCase();
      List<MyObject> rows = doSearch(processedQuery);
        
      MatrixCursor cursor = new MatrixCursor(COLUMNS);
      for (MyObject row : rows) {
          cursor.addRow(columnValuesOfWord(row));
      }        
      return cursor;
   }
   private Object[] columnValuesOfWord(MyObject obj) {
      return new String[] {
         obj._id,    // _id
         obj.name,   // text1
         obj.phone,  // text2
         obj.phone,  // intent_data (included in the Intent when clicking on item)
      };
   }
   private Cursor refreshShortcut(String shortcutId, String[] projection) {
      return null;
   }
    
   @Override public Uri insert(Uri uri, ContentValues values) {
      throw new UnsupportedOperationException();
   }

   @Override public int delete(Uri uri, String selection, String[] selectionArgs) {
      throw new UnsupportedOperationException();
   }

   @Override public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
      throw new UnsupportedOperationException();
   }
}

Third, define the SearchActivity:
public class MySearchActivity extends ListActivity {
   public void onCreate(Bundle savedInstanceState) { 
      super.onCreate(savedInstanceState); 
   //setContentView(R.layout.search);  
        setListAdapter(new MyAdapter.getInstance(MySearchActivity.this));
        handleIntent(getIntent());  
   } 

   @Override protected void onNewIntent(Intent intent) { 
      setIntent(intent); 
      handleIntent(intent);  
   } 

   public void onListItemClick(ListView l, View v, int position, long id) {       
      // call detail activity for clicked entry     
   } 

   private void handleIntent(Intent intent) {    
      if (Intent.ACTION_SEARCH.equals(intent.getAction())) {            
         // Handle the normal search query case
         String query = intent.getStringExtra(SearchManager.QUERY);            
         doSearch(query);       
      } else if (Intent.ACTION_VIEW.equals(intent.getAction())) {
         // Handle a suggestions click (because the suggestions all use ACTION_VIEW)
         Uri data = intent.getData();
         showResult(data);
      }
   }    
   
   private void showResult(Uri data) {}

   private void doSearch(String queryStr) {    
      // get a Cursor, prepare the ListAdapter and set it
   } 

}
Finally, update the AndroidManifest.xml file:
<application>
<meta-data android:name="android.app.default_searchable" android:value=".MySearchActivity" />
     <activity android:name=".MySearchActivity" android:label="@string/app_name" android:launchMode="singleTop" > 

<!-- enable the search dialog to send searches to MessageActivity -->       
         <intent-filter >
             <action android:name="android.intent.action.SEARCH" /> 
        </intent-filter> 
        <intent-filter > 
           <action android:name="android.intent.action.VIEW" /> 
        </intent-filter> 
        <meta-data android:name="android.app.searchable" android:resource="@xml/searchable" />

     </activity>
<provider android:name=".MyContentProvider" android:authorities="com.app.package.MyContentProvider" />
</application>

Resources

For more details check official documentation on ContentProvider, Search dialogs, how to implement custom suggestions, and how to use recent search suggestions. In case, you are using ActionBarSherlock check this blog post.
You may also have to check the SearchableDitionary sample project that comes with the Android SDK.

dimanche 19 mai 2013

Sample Android OAuth client

OAuth is the Internet protocol that gives you an authorized access to resources on the Internet without grating user credentials. It has been deployed by many services like Twitter, Google, Yahoo or LinkedIn. And there are some libraries / code snippet in most every programming language that implement OAuth, but it is still hard to get it work (at least on Android).
Here is a sample code snippets based on the Signpost library that uses a custom WebView to intercept HTTP calls and handle them.
First the OAuthHelper.java the helper class:
private Context mContext;
private OAuthConsumer mConsumer;
private OAuthProvider mProvider;
private String mCallbackUrl;
private SharedPreferences mSettings;
private OAuthListener mListener;

public OAuthHelper(Context context, String consumerKey, String consumerSecret) {
   if ((consumerKey == null || "".equals(consumerKey)) && (consumerSecret == null || "".equals(consumerSecret))) {
      throw new IllegalArgumentException("You must specify your \"consumer Key\" and your \"consumer Secret\" when instantiating a OAuthHelper object");
   }
   mContext = context;
   mConsumer = new CommonsHttpOAuthConsumer(consumerKey, consumerSecret);
   mProvider = new CommonsHttpOAuthProvider(QypeConstants.REQUEST_TOKEN_URL, QypeConstants.ACCESS_TOKEN_URL, QypeConstants.AUTHORIZE_URL);
   mProvider.setOAuth10a(true);
   mCallbackUrl = QypeConstants.REDIRECT_URI;
     
   mSettings = context.getSharedPreferences(QypeConstants.PREFS_NAME, 0);
}

public void authorize(OAuthListener listener) 
   throws OAuthMessageSignerException, OAuthNotAuthorizedException, OAuthExpectationFailedException, OAuthCommunicationException {

   mListener = listener;
   OAuthLoginDialog oauthDialog = new OAuthLoginDialog(mContext, this);
   oauthDialog.loadUrl(getRequestToken());
   oauthDialog.show();
}

public String getRequestToken() 
   throws OAuthMessageSignerException, OAuthNotAuthorizedException,
   OAuthExpectationFailedException, OAuthCommunicationException {
   String authUrl = mProvider.retrieveRequestToken(mConsumer, mCallbackUrl);
   return authUrl;  
}

public String[] getVerifier(String uriString) {       
   return getVerifier(Uri.parse(uriString));
}

public String[] getVerifier(Uri uri) {
   // extract the token if it exists     
   if (uri == null) {
      return null;
   }
   String token = uri.getQueryParameter("oauth_token");
   String verifier = uri.getQueryParameter("oauth_verifier");
   return new String[] { token, verifier };
}

public String[] getAccessToken() {
   String access_token = mSettings.getString(ACCESS_TOKEN, null);
   String secret_token = mSettings.getString(SECRET_TOKEN, null);
   if(access_token==null || secret_token==null)
      return null;
   return new String[] {access_token, secret_token};
}

public void getAccessToken(final String verifier) {
   new Thread(new Runnable() {   
      public void run() {
         try {
            mProvider.retrieveAccessToken(mConsumer, verifier);
            mSettings.edit().putString(ACCESS_TOKEN, mConsumer.getToken()).commit();
            mSettings.edit().putString(SECRET_TOKEN, mConsumer.getTokenSecret()).commit();
            mListener.onOAuthComplete();     
         }catch(Exception e) {            
            e.printStackTrace();
         }
      }
   }).run();
}

public String request(String url) {
   String content = null;
   try {
       String accessToken[] = getAccessToken();
       mConsumer.setTokenWithSecret(accessToken[0], accessToken[1]);   
       HttpGet request = new HttpGet(url);
       // sign the request
       mConsumer.sign(request);
       // send the request
       HttpClient httpClient = new DefaultHttpClient();
       HttpResponse response = httpClient.execute(request);   
       content = EntityUtils.toString(response.getEntity());
   } catch (Exception e) {
       e.printStackTrace(); 
   }
   return content;
}
Second, the OAuthLoginDialog.java that will be used to intercept HTTP call back url
public class OAuthLoginDialog extends Dialog {
   private WebView mWebView;
   private OAuthHelper mHelper;

   public OAuthLoginDialog(Context context, OAuthHelper helper) {
      super(context);
      requestWindowFeature(Window.FEATURE_NO_TITLE);
      setContentView(R.layout.login_dialog);

      LayoutParams params = getWindow().getAttributes();
      params.height = LayoutParams.MATCH_PARENT;
      params.width = LayoutParams.MATCH_PARENT;
      getWindow().setAttributes((android.view.WindowManager.LayoutParams) params);

      mHelper = helper;

      init();
   }
   private void init() {
      mWebView = (WebView) findViewById(R.id.webView);
      mWebView.getSettings().setJavaScriptEnabled(true);

      mWebView.setWebViewClient(new WebViewClient() {
         @Override public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {}
         @Override public void onPageStarted(WebView view, String url, Bitmap favicon) {
             super.onPageStarted(view, url, favicon);
             progressBar.setVisibility(View.VISIBLE);
             if(url.startsWith(REDIRECT_URI)) {
                if(mHelper.getAccessToken() == null) {
                   String[] token = mHelper.getVerifier(url);
                   mHelper.getAccessToken(token[1]);
                }     
                mWebView.clearCache(true);
                dismiss();
             } 
         }

         @Override public void onPageFinished(WebView view, String url) {
             super.onPageFinished(view, url);
             progressBar.setVisibility(View.GONE);
         }

      });

   }
 
   public void loadUrl(String url) {
      mWebView.loadUrl(url);
   }
}
Third, the OAuth listener interface
public interface OAuthListener {
   // Called when the user is logged to the server
   public void onOAuthComplete();
   // Called when the user has canceled the login process
   public void onOAuthCancel();
   // Called when the login process has failed
   public void onOAuthError(int errorCode, String description, String failingUrl);
}
Finally, this is the code used to initiate an OAuth connection
OAuthHelper mHelper = new OAuthHelper(mContext, API_KEY, API_SECRET);
mHelper.authorize(new OAuthListener() {    
   public void onOAuthError(int errorCode, String description, String failingUrl) {}
   public void onOAuthComplete() {
      String testUrl = "http://sample_url_for_oauth_authorization/";
      mHelper.request(testUrl)
   }    
   public void onOAuthCancel() {}
});  
More resources can be found here implementing client side OAuth for android (it uses intent filter instead of a custom dialog) and here a sample OAuth for twitter.

Get the code for a sample project on Github.

jeudi 9 mai 2013

Stuff for Android

Build systems

Buck by facebook: a build system for Android that encourages the creation of small, reusable modules consisting of code and resources. Not yet supported on windows !
It was presented by Simon Stewart at GTAC 2013 (Google Test Automation Conference) How Facebook Tests Facebook on Android.

Gradle Android build, a presentation will be held on Google IO.

Open source libraries

Android Bootstrap A presentation about common libraries.

Testing rocks debuging sucks

AndroidMock a framework by Google for mocking Android interfaces/classes based on EasyMock syntax.
BoundBox provides access to all fields, constructor and methods (public or not) of a Class for Unit-testing without having to modify the latter.

Design patterns

Libraries for the Action bar UI design patter:
Object Pool pattern for better performance: discussion, Pools.java, a sample.

User Interface Design

  • Blur effect for Android design (just like in the Path app).
  • How to make the ChatHead feautre as available on the Facebook Messenger.
  • A blog post describing how to make counters on the navigation drawer.

Random stuff

Sample downloader by commonsguy showing the using of handlers/messanger to communicate between Service and the main thread.
User authentication.
Which IDE: eclipse vs InteliJ.
The dumpsys commands with Android adb

dimanche 7 avril 2013

Some podcast radios

The following are some cool Podcasts about development on different technologes: Java, Android, etc.

Software Engineering Radio usually performs interviews with authors or professionals from different backgrounds in software engineering (development, databases, Java technology). This radio is sponsored by the IEEE Software magazine.

Android Weekly performs a weekly review of Android news from new Apps to insights from Google

Android Devcast a serie of french podcast by PAUG (Paris Android User Group) on everything concerning Android development.

Java Posse performs good news cast about Java technologies (and even Scala), frameworks and the JVM. Some Googlers are part of the host.

Lostcast is a podcast by two indie game developers that talk about their experience with HTML5 based games development, advanced technics, interviews with industry pioneers.

Talking Big Data is hosted by IBMer David Pittman, it addresses everything related to Big Data, analytics and related technologies.

Hacker Public Radio is hosted by a long list of members and has the particularity of having the content produced by the community of listeners, the radio abords different areas especially on the open source community (conferences or projects news, etc.).

You can find more by visiting this StackOverflow thread. or on Vogel's blog.

All these podcasts can be found on podbay.fm, there you can also find more podcast radios.



dimanche 17 mars 2013

Embedded Android

Some resources from the Embedded Android Workshop:
1. Android Internals

2. Working with the AOSP

3. Native Android user-space

4. Using and Customizing the Android Framework

This is a link to the presentation.

More presentations by

Check also the Embedded Linux Wiki and Porting Android to Devices.


lundi 4 février 2013

Adding headers button to a ListView

Here is an example showing how to add headers button to a ListView

Button btn1 = new Button(MyActivity.this);
btn1.setText("button1");
btn1.setLayoutParams(new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
btn1.setGravity(Gravity.CENTER);
btn1.setOnClickListener(new OnClickListener() {   
 @Override
 public void onClick(View v) {
  // do something
 }
});
Button btn2 = new Button(MyActivity.this);
btn2.setText("button2");
btn2.setLayoutParams(new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
btn2.setGravity(Gravity.CENTER);
btn2.setOnClickListener(new OnClickListener() {   
 @Override
 public void onClick(View v) {
  // do something
 }
});
        
LinearLayout layout = new LinearLayout(this);  
layout.setOrientation(LinearLayout.HORIZONTAL);  
layout.setLayoutParams(new ListView.LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT));  
layout.setGravity(Gravity.CENTER);  
layout.addView(btn1);  
layout.addView(btn2);
myList.addHeaderView(layout);//or getListView().addHeaderView(layout);

lundi 10 décembre 2012

Push notifications with Parse.com

Parse.com provides a set of handy tools to rapidly build mobile applications that needs back-end. One of the main provided features is the push notifications that enables asynchronous and real-time communiation. This post illustrates how to set up the push mechnism in an Android application.
  • First, we need to create an Account at Parse.com and register an application to have an application key and client key
  • Second, in the application settings wen have to enable push notifications
  • Then, download the latestes Parse SDK, i.e. the parse.jar
  • Finally, code time
In the manifest, we need to add the following lines to ask permessions
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> 
<uses-permission android:name="android.permission.VIBRATE" />
Then, before the </application> register a broadcast receiver
<receiver android:name=".PushReceiver">
   <intent-filter>  
 <action android:name="com.telethonix.NOTIFICATION"> </action>
   </intent-filter>
</receiver>
Then create the PushReceiver class as follow:
public class PushReceiver extends BroadcastReceiver {
   public final static String TAG = PushReceiver.class.getSimpleName(); 
   @Override
   public void onReceive(Context context, Intent intent) {     
      try {
 Bundle extras = intent.getExtras();        
 String message = extras != null ? extras.getString("com.parse.Data") : "";    
 Log.i(TAG, "Received message: "+message);
       // do something with the received data
      } catch (JSONException e) {
 e.printStackTrace();
      }  
   }
}
Finnaly in the main activity, we should initiate parse with the application keys, and send a notification
String app_id     = "your application key";
String client_key = "your client key";
Parse.initialize(this, app_id, client_key);
// subscribe to be notified
PushService.subscribe(MainActivity.this, "channel", MainActivity.class);
// push a message
ParsePush push = new ParsePush(); push.setChannel("channel");
push.setData(data);
push.sendInBackground(new SendCallback(){   
   @Override
   public void done(ParseException arg0){
      Log.i(TAG, "push sent done!");
   }
});
More details at Push Developer Guide.

mercredi 14 novembre 2012

Android Solution Logs

Get a bitmap from an ImageView (StackOverflow)
imageView.setDrawingCacheEnabled(true);
imageView.measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), 
                   MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
imageView.layout(0, 0, imageView.getMeasuredWidth(), imageView.getMeasuredHeight()); 
imageView.buildDrawingCache(true);
Bitmap bmap = Bitmap.createBitmap(imageView.getDrawingCache());
imageView.setDrawingCacheEnabled(false);

Display the soft keyboard (StackOverflow)
// show keyboard
InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
imm.toggleSoftInput(InputMethodManager.SHOW_FORCED,0);
// hide keyboard
InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(view.getWindowToken(),0); 

Check whether the software keyboard is displayed (StackOverflow)
final View activityRootView = findViewById(R.id.activityRoot);
activityRootView.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
    Rect r = new Rect();
    //r will be populated with the coordinates of your view that area still visible.
    activityRootView.getWindowVisibleDisplayFrame(r);

    int heightDiff = activityRootView.getRootView().getHeight() - (r.bottom - r.top);
    if (heightDiff > 100) { // if more than 100 pixels, its probably a keyboard...
        ... do something here
    }
 }
});

Get the root view of current activity (StackOverflow)
View rootView = getWindow().getDecorView().findViewById(android.R.id.content)

Canvas drawing example on Android, link.
Draw text on canvas with a custom font (StackOverflow)
Paint paint = new Paint(); 
paint.setStyle(Paint.Style.FILL);     
paint.setColor(Color.WHITE); 
paint.setTextSize(30);
Typeface tf = Typeface.create("Helvetica", Typeface.BOLD);
paint.setTypeface(tf);     
Canvas canvas = new Canvas(bitmap);     
canvas.drawText(text, x, y, paint);

Gesture event detection (snippets)
  • Implements android.view.GestureDetector.OnGestureListener interface
  • Create a new GestureDetector instance: detector = new  GestureDetector(this, this)
  • Call detector.onTouchEvent(event) on the activity onTouchEvent(event)
Detecting touch event (Android Coding).

Display action bar (StackOverflow)

Capture a frame from a video (change since API 14) 
protected Bitmap videoFrame(String uriString, long msec) {  
  MediaMetadataRetriever retriever = new MediaMetadataRetriever();
  try {                         
     if (Build.VERSION.SDK_INT >= 14) {       
        Map<String, String> headers = new HashMap<String, String>();
        retriever.setDataSource(uriString, headers);
     }else {
        Uri uri = Uri.parse(uriString);
        retriever.setDataSource(context, uri);
     }                     
     return retriever.getFrameAtTime(msec);
  } catch (Exception ex) {
         ex.printStackTrace();
  } finally {
     try {
        retriever.release();
     } catch (RuntimeException ex) {
     }
  }
  return null;
}

Share content via intents (tutorial), sharing image (StackOverflow), set title (StackOverflow)
Intent sharingIntent = new Intent(android.content.Intent.ACTION_SEND);
sharingIntent.setType("text/plain");
String shareBody = "Here is the share content body";
sharingIntent.putExtra(android.content.Intent.EXTRA_SUBJECT, "Subject Here");
sharingIntent.putExtra(android.content.Intent.EXTRA_TEXT, shareBody);
startActivity(Intent.createChooser(sharingIntent, "Share via"));

Customized looking TabActivity (link).

Use Intent share to invoke a Twitter application to post text+image (StackOverflow)
private void share(String nameApp, String imagePath) {
    List<Intent> targetedShareIntents = new ArrayList<Intent>();
    Intent share = new Intent(android.content.Intent.ACTION_SEND);
    share.setType("image/jpeg");
    List<ResolveInfo> resInfo = getPackageManager().queryIntentActivities(share, 0);
    if (!resInfo.isEmpty()){
        for (ResolveInfo info : resInfo) {
            Intent targetedShare = new Intent(android.content.Intent.ACTION_SEND);
            targetedShare.setType("image/jpeg"); // put here your mime type

            if (info.activityInfo.packageName.toLowerCase().contains(nameApp) || 
                    info.activityInfo.name.toLowerCase().contains(nameApp)) {
                targetedShare.putExtra(Intent.EXTRA_TEXT,     "My body of post/email");
                targetedShare.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(new File(imagePath)) );
                targetedShare.setPackage(info.activityInfo.packageName);
                targetedShareIntents.add(targetedShare);
            }
        }

        Intent chooserIntent = Intent.createChooser(targetedShareIntents.remove(0), "Select app to share");
        chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, targetedShareIntents.toArray(new Parcelable[]{}));
        startActivity(chooserIntent);
    }
}

Display pictures of a specific folder in the Gallery app (StackOverflow)
To be continued.

mardi 13 novembre 2012

SnapMeme

The application allows users to create mémé from a snapshot of a video. Dailymotion API are used to search for videos (according to user keywords) and disply the chosen video to user. Then at any moment the user can take a snapshot of the current displayed frame from the video, add text on it to share the resulting drawing (also called Mémé) on social networks (e.g. Twitter).

Démo
 
A snapshot of the posted message on twitter

More details about the event can be found here.

TerroirCaisse

More information about the event can be found here at iphon.fr (in french).
The used data was provided by data.visitprovence.com. The application source code can be found at github.

jeudi 4 octobre 2012

Read/Write NFC tag in Android

Android & NFC howto

When a NFC tag is discovered, the system checks if the foreground application has registered to such an Intent with a call to enableForegroundDispatch(). If not, it checks whether any other applications has registered for the NDEF_DISCOVERED intent. If an application want to register for events related to a tag of a specific technology (e.g. classic MiFare tags) then it should register to the TECH_DISCOVERED intent.
The following diagram explains in details how an event related to reading a tag is handled:

Read NFC tag in Android

Declare in the manifest a specific Activity to receive an Intent when a NFC tag is read by the device:
<activity android:name=".NfcReaderActivity" android:label="NfcReaderActivity">
    <intent-filter>
     <action android:name="android.nfc.action.TAG_DISCOVERED"/>
     <category android:name="android.intent.category.DEFAULT"/>
    </intent-filter>
</activity>

To register the foreground Activity for tag reading events, add following lines to onResume() method:
@Override
protected onResume() {
  PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, new Intent(this, this.getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0);
  mAdapter.enableForegroundDispatch(this, pendingIntent, null, null);
} 

In the onNewIntent(Intent)to read the tag after receiving a ACTION_TAG_DISCOVERED intent :
protected void onNewIntent(Intent intent) {
  String action = intent.getAction();
  if (NfcAdapter.ACTION_TAG_DISCOVERED.equals(action)){
    Parcelable[] rawMsgs = intent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES);
    NdefMessage[] messages;
    if (rawMsgs != null) {
      messages = new NdefMessage[rawMsgs.length];
      for (int i = 0; i < rawMsgs.length; i++) {
        messages[i] = (NdefMessage) rawMsgs[i];     
        // To get a NdefRecord and its different properties from a NdefMesssage   
     NdefRecord record = messages[i].getRecords()[i];
     byte[] id = record.getId();
     short tnf = record.getTnf();
     byte[] type = record.getType();
     String message = getTextData(record.getPayload());
      }
    }
  }
}

This is how to read the content tag of type text RTD_TEXT: 
// Decoding a payload containing text
private String getTextData(byte[] payload) {
  if(payload == null) 
    return null;
  try {
    String encoding = ((payload[0] & 0200) == 0) ? "UTF-8" : "UTF-16";
    int langageCodeLength = payload[0] & 0077;
    return new String(payload, langageCodeLength + 1, payload.length - langageCodeLength - 1, encoding);     
  } catch(Exception e) {
    e.printStackTrace();
  }
  return null;
}

Write NFC tag in Android

In the manifest:
<uses-permission android:name="android.permission.NFC" />
<uses-feature android:name="android.hardware.nfc" android:required="true" />
in onCreate() get the NfcAdapter:
NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(this);
The data part of a TAG is a NdefMessage of multiple NdefRecord this is how to encode a text into a payload:
// Encoding a text into a tag payload 
NdefRecord createRecord(String message) {
  byte[] langBytes = Locale.ENGLISH.getLanguage().getBytes(Charset.forName("US-ASCII"));
  byte[] textBytes = message.getBytes(Charset.forName("UTF-8"));
  char status = (char) (langBytes.length);
  byte[] data = new byte[1 + langBytes.length + textBytes.length];
  data[0] = (byte) status;
  System.arraycopy(langBytes, 0, data, 1, langBytes.length);
  System.arraycopy(textBytes, 0, data, 1 + langBytes.length, textBytes.length);
  return new NdefRecord(NdefRecord.TNF_WELL_KNOWN, NdefRecord.RTD_TEXT, new byte[0], data);
}
// Create a message from a record
NdefMessage createMessage(NdefRecord record) {
  NdefRecord[] records = new NdefRecord[1];
  records[0] = record;
  NdefMessage message = new NdefMessage(records);
  return message;
}
To enable/disable sending a message each time a tag is read
@Override
protected void onPause() {
  super.onPause();
  nfcAdapter.disableForegroundNdefPush(this);
}
@Override
protected void onResume() {
  super.onResume();
  NdefRecord record = creerRecord("this is a message");
  NdefMessage message = creerMessage(record);
  nfcAdapter.enableForegroundNdefPush(this, message);
}
If there is no tag, we can simulate a tag been read by sending the corresponding intent, this is how:
final Intent intent = new Intent(NfcAdapter.ACTION_TAG_DISCOVERED);
NdefMessage[] messages = new NdefMessage[1];
messages[0] = createMessage(createRecord("Simulating a tag reception"));
intent.putExtra(NfcAdapter.EXTRA_NDEF_MESSAGES, messages);
startActivity(intent); 

Reference Programming Android, chapter 16: Sensors, NFC, Speech, Gestures, and Accessibility.

mercredi 26 septembre 2012

Getting started with NFC on Android

This posts introduces Near Field Communication (NFC), its Modes, the Android APIs that can be used to deal with NFC tags and the mysterious NDEF Format (NDEFMessages, NDEFRecord, RTC, TNF, etc.).

Not all Android-based smartphones support NFC, actually the list is limited to Nexus S, Galaxy S II, Xperia Acro, Lg LU6200, Acer Liquid express, Sony Nozomi, Nexus Prime, LG Gelato nfc, etc.

The NFC API was introduced with Android 2.3 (API 9, Dec. 2010) on Nexus S. A more complete API was released win Android 2.3.3 (API 10, Feb. 2011). This API provides tools for detecting TAGs, reading infromation stored on the tag and writing information, and also communicating with other NFC tags. These operations are also called modes:
  • Peer-to-Peer Mode: limited support on Android
  • Reader/Writer Mode: excellent support
  • Card Emulation Mode: no support
The Core classes of the NFC API are NFCAdapter, NFCManager, Tag Technology Classes.
Here is a typical manafest file for an application that want to use the Android NFC API:
Android Manifest
//NFC Permission 
<uses-permission android:name="android.permission.NFC" />
//API Level 
<uses-sdk android:minSdkVersion="10" />
//NFC Feature 
<uses-feature android:name="android.hardware.nfc" android:required="true" />

<application android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:theme="@style/AppTheme" >
<activity android:name=".NfcRWActivity" android:label="NfcRWActivity">
  <intent-filter>
    <action android:name="android.nfc.action.NDEF_DISCOVERED" />
    <data android:mimeType="application/json" />
    <category android:name="android.intent.category.DEFAULT" />
  </intent-filter>
</activity>
</application>
NFCManager used to get the NFCAdapter the real thing that control Foreground Dispatch and P2P NDEF Push, check if NFC is turned on
NfcManager nfc_manager = (NfcManager) getSystemService(NFC_SERVICE);
NFCAdapter nfc_adapter = nfc_manager.getDefaultAdapter();

To read a tag it is easy: unlock phone, touch tag, most likely the intent chooser will open.

Foreground Dispatch System

Foreground Dispatch allows the declaration of the currently active application as prioritar for handling the tags read by user. Thus the Foreground Activity may redirect all intents related to tag, technology or NDEF discovery to itself.
nfcAdapter.enableForegroundDispatch(activity, pendingIntent, intentFiltersArray, techListsArray);

pi = PendingIntent.getActivity(activity, 0, new Intent(activity, activity.getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0);

IntentFilter ndef = new IntentFilter(NfcAdapter.ACTION_NDEF_DISCOVERED);
ndef.addDataType("*/*");
intentFiltersArray = new IntentFilter[] { ndef };
techListsArray = new String[][] {new String[] { NfcA.class.getName() } };
Don't forget to enable in onResume(), disable in onPause(). The tag can be read in onNewIntent().
@Override
protected void onResume() {
  super.onResume();
  nfcAdapter.enableForegroundDispatch(this, pi, intentFiltersArray, techListsArray);
}

@Override
protected void onPause() {
  super.onPause();
  nfcAdapter.disableForegroundDispatch(this);
}

@Override 
protected void onNewIntent(Intent intent) {
  super.onNewIntent(intent);
  Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
  tag.getId() //returns id as byte[]
  String techs[] = tag.getTechList();
  for (String tech: techs) {
   if (tech.equals("android.nfc.tech.Ndef")) {
    Ndef ndef = Ndef.get(tag);
    ndef.getType();
    ndef.getMaxSize();
    ndef.canMakeReadOnly();
    ndef.isWritable();
    NFCUtil.printNdefMessageDetails(ndef.getCachedNdefMessage());
   }
  }
}

Intent Dispatch System

Ndef discovered
For text or any other mime type:
<intent-filter>
  <action android:name="android.nfc.action.NDEF_DISCOVERED"/>
  <data android:mimeType="text/plain"/>
  <category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
For url:
<intent-filter>
  <action android:name="android.nfc.action.NDEF_DISCOVERED"/>
  <data android:scheme="http" android:host="www.r0ly.fr"/>
  <category android:name="android.intent.category.DEFAULT"/>
</intent-filter>

Tech discovered
In the manifest
<intent-filter>
  <action android:name="android.nfc.action.TECH_DISCOVERED"/>
</intent-filter>

<meta-data android:name="android.nfc.action.TECH_DISCOVERED" android:resource="@xml/techlist"/>
In the xml/techlist.xml file:
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
  <tech-list>
    <tech>android.nfc.tech.IsoDep</tech>
    <tech>android.nfc.tech.MifareClassic</tech>
  </tech-list>
</resources>

Tag Technologies maps to tech specifications or to pseudo-technologie or capabilities like NDEF or NDEFFormatable.
  • TagTechnology The interface that all tag technology classes must implement.
  • NfcA  Provides access to NFC-A (ISO 14443-3A) properties and I/O operations.
  • NfcB Provides access to NFC-B (ISO 14443-3B) properties and I/O operations.
  • NfcF Provides access to NFC-F (JIS 6319-4) properties and I/O operations.
  • NfcV Provides access to NFC-V (ISO 15693) properties and I/O operations.
  • IsoDep Provides access to ISO-DEP (ISO 14443-4) properties and I/O operations.
  • Ndef Provides access to NDEF data and operations on NFC tags that have been formatted as NDEF.
  • NdefFormatable Provides a format operations for tags that may be NDEF formattable.
  • MifareClassic Provides access to MIFARE Classic properties and I/O operations, if this Android device supports MIFARE.
  • MifareUltralight Provides access to MIFARE Ultralight properties and I/O operations, if this Android device supports MIFARE.
NDEF stands for NFC Data Exchange FormatRecords can be MIME-type media, URIs or RTDs (Record Type Definitions)
NdefRecord
Reading Tag Content
//r = NdefRecord
if (r.getTnf() == NdefRecord.TNF_ABSOLUTE_URI) {
  b.append(String.format("TNF_ABSOLUTE_URI: type(%1$s), id(%2$s), payload(%3$s)\n", new String(r.getType()), idBytes.toString(), new String(r.getPaylod())));

} else if (r.getTnf() == NdefRecord.TNF_MIME_MEDIA) {
  b.append(String.format("TNF_MIME_MEDIA: type(%1$s), id(%2$s), payload(%3$s)\n", new String(r.getType()), idBytes.toString(),new String(r.getPayload())));

}

What to listen for?

The following figure depicts how the precedent intent related to tag reading are dispatch, so if you want your application to:
  • receive a notification about a tag been read while your app is in foreground, then you should use for enableForegroundDispatch. 
  • receive a notification even if it is not the foreground app, then register for NDEF_DISCOVERED.
  • receive a notification when the tag is of a specific technology TECH_DISCOVERED.
  • otherwise you should register for TAG_DISCOVERED.
Tag dispatch
Direct Start
1. Write custom NDEF MIME-type media messages
2. Use custom IntentFilter to blind to your messages

Writing NDEF MIME
NdefMessage msg = NFCUtil.getNdefMimeMessage("application/json", "{\"key\":\"value\"}");
Intent i = new Intent(this, WriteActivity.class);
i.putExtra(WriteActivity.NDEF_MESSAGE, msg);
startActivity(i);
private static NdefRecord getMimeRecord(String mimeType, String content) {
  NdefRecord record = new NdefRecord(NdefRecord.TNF_MIME_MEDIA, mimeType.getBytes(), getRandomIdBytes(), content.getBytes());
  return record;
}
Ndef ndef = Ndef.get(tag);
if (ndef.isWritable() && ndef.getMaxSize() > this.msg.toByteArray().length) {
  ndef.connect();
  ndef.writeNdefMessage(this.msg);
  ndef.close();
} else {
  // do something
}
P2P: NDEF Push
Create NDEF Message which is pushed to another active device once the other device is close
NfcAdapter.enableForegroundNdefPush(activity, ndefmessage);
NfcAdapter.disableForegroundNdefPush(activity);

dimanche 2 septembre 2012

La Poste: a cloud-based mobile application for mail customers

Lastly, I had participate to Le Mobile 2012 Hackathon 1st edition organized by WIP Connector (Wireless Industry Partnership) as part of Le Mobile'2012 annual conference. The event was about building innovative mobile applications for the French Post Office and Auchan. As part of a three member team, we made a project for helping customers of French Post to track their mails and improving their experience.

After the event, we have been invited to the French Post headquarter to give a talk about our idea to a community of high managers from the IT and R&D departments. Here is the presentation (french):

 The idea behind this project is to allow users to track their sent and received postal mail with an application running on their smartphone.
We also imagined a new kind of postal stamp that contains a code bar and can be printed. First, sender scan the stamp code bar then fill a form that contains information about receiver.
Second, when he sends the letter he can notify the receiver through the system so that he/she get ready to receive the letter.
When post office receive the letter they update its state after each operation so that both sender and receiver get notified and updated with the new letter status.
Receiver can notify Post office, at anytime, about changes that must be considered for the delevery of the letter. For example, he may ask to change the address from its home to work address.
Finally, when letter is received and delivered to receiver, the sender is notified automatically.

We deveopped this idea during the two days of the event and the result consists of the following elements:

  • An Android client application for users, an old version was developed with jQuery Mobile.
  • An Android client application for Post employee.
  • An Arduino + IOIO + CodeBar Scanner prototype is developed to make a smartphone barcode reader with high accuracy.
  • A web application for the server side built on top of Google App Engine (J2EE).

The project code source can be found at GitHub.


mardi 3 juillet 2012

Using Speech Input API

The Android SDK provides support for an easy integration of speech input into native applications. We need just to send out an intent RecognizerIntent (no permission is required) to call any available voice recognition service in the phone (e.g. Google Voice, Nuance Dragon API for mobile). Then, a list of recognized word is send back to the application which can capture them in the onActivityResult method.

jeudi 28 juin 2012

Text to Speech in Android


To use the Android Speech to Text API you need to implement the TextToSpeech.OnInitListener interface. In the onInit() method, check the returned status to value, it ca be TextToSpeech.SUCCESS for initialization success or TextToSpeech.ERROR in case of failure. If the initialization succeeded then set your preferred language to US english (or FRANCE for french). 
Note that a language may not be available, check the documentation for more information.
Here is a complete example of how to use the Speech to Text API.
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.speech.tts.TextToSpeech;
import android.util.Log;
import java.util.Locale;

public class MainActivity extends Activity implements TextToSpeech.OnInitListener {
 
 private String TAG = MainActivity.class.getSimpleName();
 private TextToSpeech mTts;
 
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);        
        setContentView(R.layout.main);
        
        mTts = new TextToSpeech(this, this);
    } 

 @Override
 public void onInit(int status) {
        if (status == TextToSpeech.SUCCESS) {            
            int result = mTts.setLanguage(Locale.US);            
            if (result == TextToSpeech.LANG_MISSING_DATA || result == TextToSpeech.LANG_NOT_SUPPORTED) {
                Log.e(TAG, "Language data is missing or the language is not supported.");
            } else {                
               String text = "hello, do you hear me?";  
               mTts.speak(text,TextToSpeech.QUEUE_FLUSH, null);
}
        } else {
            Log.e(TAG, "Could not initialize TextToSpeech.");
        }
 }
}

mardi 26 juin 2012

LemonWash for Parrot's Asteroid Car Receiver

I had the chance to participate (and even win) to one of BeMyApp most great Hackathon week-ends that was about inventing the futre car and building Android applications to run on the first connected car receiver the Asteriod by Parrot.
During this hackathon (from May, 11st to 13rd 2012) I was part of a team composed of three developers (including me), a designer, a community manager, and team leader. We developed LemonWach, an application for requesting car wash service.

dimanche 17 juin 2012

Handling events on Android



In this post, we will see how to listen for some events (e.g. call related events) on Adroid devices in order to perform a certain action:




samedi 16 juin 2012

HTML5 versus Android



Native apps or mobile web? It's often a hard choice when deciding where to invest your mobile development resources. While the mobile web continues to grow, native apps and App Stores are incredibly popular. 
Reto Meier and Michael Mahemoff present at Google I/O 2011 both perspectives in an app development smackdown.