Friday, November 28, 2014

Played the Lister sample on the simulator of Apple Watch

During the Thanksgiving break this week, I found time to play the Lister Apple recently published at their Developer site https://developer.apple.com/library/prerelease/ios/samplecode/Lister/Introduction/Intro.html

At first I used Xcode 6.1 to load and build the project, and encountered some problems, then I changed to use Xcode 6.2 Beta (https://developer.apple.com/devcenter/download.action?path=/Developer_Tools/xcode_6.2_beta_auk9ez/xcode_6.2_beta.dmg), the issues disappeared.

Following the directions in iOS and Watch Quick Start.pdf, I was able to launch the Lister app on the iPhone simulator and the Lister Watch App on the Apple Watch Simulator. While running the apps simultaneously, data on these 2 simulators could be synced.

Before I was able to use Xcode 6.2 Beta to build and run the sample, I removed the project and the sample code I once tried with Xcode 6.1, the derived data under user account was also removed, to make sure it is a fresh start of the project with Xcode 6.2 Beta.

Sunday, August 24, 2014

Using CMU's PocketSphinx voice recognition engine to create a service for Android app

Recently CMU's PocketSphinx project published the library for Android platform, including a demo code. On the project webpage it also provides a short tutorial at http://cmusphinx.sourceforge.net/wiki/tutorialandroid.

I downloaded the lib and the demo project, spending 2 hours to understand the flow the demo and figure out how to use it in a service for Android app. I am going to list some details I can still remember, and hope it would be helpful  for someone who wants to use the lib.

I am assuming readers of this post are familiar with the basic concepts of Android platform and have already setup the development environment in the computer. Eclipse IDE is used in this project.

1. Go to the link http://cmusphinx.sourceforge.net/wiki/download/ to download the library http://sourceforge.net/projects/cmusphinx/files/pocketsphinx/0.8/

2. Go to http://cmusphinx.sourceforge.net/wiki/tutorialandroid to download the demo project, follow the steps to set up the development environment if it has not been set up on the computer

3. Create a service that implements RecognitionListener. Please remember to import the packages in the java code. Most of the code is copied over from the demo code from PocketSphinx project. Please pay attention to the lines I have commented out. Since this is a service, it shall not have UI related lines of code. I am still keeping some of the lines in the original demo code.

package com.me.android.test;
import static edu.cmu.pocketsphinx.SpeechRecognizerSetup.defaultSetup;

import java.io.File;
import java.io.IOException;
import java.util.HashMap;

import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.IBinder;
import android.util.Log;
import edu.cmu.pocketsphinx.Assets;
import edu.cmu.pocketsphinx.Hypothesis;
import edu.cmu.pocketsphinx.RecognitionListener;
import edu.cmu.pocketsphinx.SpeechRecognizer;

/*
 * Based on the sample from PocketSphinxDemo
 */

public class PocketSphinxVoiceRecognitionService extends Service implements RecognitionListener{

public static String TAG ="PocketSphinxVoiceRecognitionService";
    private static final String KWS_SEARCH = "wakeup";
    private static final String FORECAST_SEARCH = "forecast";
    private static final String DIGITS_SEARCH = "digits";
    private static final String MENU_SEARCH = "menu";
    private static final String KEYPHRASE = "oh mighty computer";

    private SpeechRecognizer recognizer;
    private HashMap<String, Integer> captions;
    
    public Context context;
    
@Override
public void onCreate() {
// TODO Auto-generated method stub
super.onCreate();
context = getApplicationContext();
        // Prepare the data for UI
Log.i(TAG, "onCreate: setup search options");
        captions = new HashMap<String, Integer>();
        captions.put(KWS_SEARCH, R.string.kws_caption);
        captions.put(MENU_SEARCH, R.string.menu_caption);
        captions.put(DIGITS_SEARCH, R.string.digits_caption);
        captions.put(FORECAST_SEARCH, R.string.forecast_caption);
        //setContentView(R.layout.main);
        //((TextView) findViewById(R.id.caption_text)).setText("Preparing the recognizer");
        
        // Recognizer initialization is a time-consuming and it involves IO,
        // so we execute it in async task

        new AsyncTask<Void, Void, Exception>() {
            @Override
            protected Exception doInBackground(Void... params) {
                try {
                Log.i(TAG, "AsyncTask:doInBackground: setup recognizr");
                    Assets assets = new Assets(context);
                    File assetDir = assets.syncAssets();
                    setupRecognizer(assetDir);
                } catch (IOException e) {
                    return e;
                }
                return null;
            }

            @Override
            protected void onPostExecute(Exception result) {
                if (result != null) {
                    //((TextView) findViewById(R.id.caption_text)).setText("Failed to init recognizer " + result);
                Log.e(TAG, "onPostExecute: failed to init recognizer: " + result);
                } else {
                Log.i(TAG, "AsyncTask: onPostExecute: swtich to the digit search");
                    switchSearch(/*KWS_SEARCH*/DIGITS_SEARCH);
                }
            }
        }.execute();        
}

  private void switchSearch(String searchName) {
        recognizer.stop();
        recognizer.startListening(searchName);
        String caption = getResources().getString(captions.get(searchName));
        //((TextView) findViewById(R.id.caption_text)).setText(caption);
    }

    private void setupRecognizer(File assetsDir) {
        File modelsDir = new File(assetsDir, "models");
        recognizer = defaultSetup()
                .setAcousticModel(new File(modelsDir, "hmm/en-us-semi"))
                .setDictionary(new File(modelsDir, "dict/cmu07a.dic"))
                .setRawLogDir(assetsDir).setKeywordThreshold(1e-20f)
                .getRecognizer();
        recognizer.addListener(this);

        // Create keyword-activation search.
        recognizer.addKeyphraseSearch(KWS_SEARCH, KEYPHRASE);
        // Create grammar-based searches.
        File menuGrammar = new File(modelsDir, "grammar/menu.gram");
        recognizer.addGrammarSearch(MENU_SEARCH, menuGrammar);
        File digitsGrammar = new File(modelsDir, "grammar/digits.gram");
        recognizer.addGrammarSearch(DIGITS_SEARCH, digitsGrammar);
        // Create language model search.
        File languageModel = new File(modelsDir, "lm/weather.dmp");
        recognizer.addNgramSearch(FORECAST_SEARCH, languageModel);
    }
   
public PocketSphinxVoiceRecognitionService() {
// TODO Auto-generated constructor stub
}

@Override
public void onBeginningOfSpeech() {
// TODO Auto-generated method stub
}

@Override
public void onEndOfSpeech() {
// TODO Auto-generated method stub
        if (DIGITS_SEARCH.equals(recognizer.getSearchName())
                || FORECAST_SEARCH.equals(recognizer.getSearchName()))
            switchSearch(/*KWS_SEARCH*/DIGITS_SEARCH);
}

@Override
public void onPartialResult(Hypothesis hypothesis) {
// TODO Auto-generated method stub
        String text = hypothesis.getHypstr();
        if (text.equals(KEYPHRASE))
            switchSearch(MENU_SEARCH);
        else if (text.equals(DIGITS_SEARCH))
            switchSearch(DIGITS_SEARCH);
        else if (text.equals(FORECAST_SEARCH))
            switchSearch(FORECAST_SEARCH);
        else {
            //((TextView) findViewById(R.id.result_text)).setText(text);
        }
}

@Override
public void onResult(Hypothesis hypothesis) {
// TODO Auto-generated method stub
        //((TextView) findViewById(R.id.result_text)).setText("");
        if (hypothesis != null) {
            String text = hypothesis.getHypstr();
            //makeText(getApplicationContext(), text, Toast.LENGTH_SHORT).show();
            Log.i(TAG, "onResult: " + text);
        }
}

@Override
public IBinder onBind(Intent intent) {
// TODO Auto-generated method stub
return null;
}

}

4.  Add the service to the manifest file
         <service android:name="com.me.android.test.PocketSphinxVoiceRecognitionService"
            android:enabled="true"
            android:exported="false" />

5. Copy and paste the following folders from the demo project to the "libs" folder of the current project 
      armeabi
      armeabi-v7a
      mips
      x86

6. Copy the lib pocketsphinx-android-0.8-nolib.jar to the  the "libs" folder of current project

7. Copy the "assets" folder from the demo project to the current project

8. Copy the values in the strings.xml under "Values" folder of the demo project, and paste them to the strings.xml of current project.

9.  Add your own words to be used in the current project
Open the file digits.gram under "assets/sync/models/grammar" and following the format to add your own words to be used in the current project

10. Copy the following files from the demo project to the current project
   assets.xml
    custom_rules.xml
    build.xml
and make some changes such as project names, etc.

11. In other code such as the main activity or widget service provider, check if this service has been started. If it is not, then start up this service

Up to this step, the project shall be able to get compiled and build. The apk can also be launched to the device.

12. The last but the most important step to make the service work and take the voice command

Project -> Properties -> Builders -> New

Configure the builds and add the asset list builder to the project. Please refer to the parameters used by "Asset List Builder" of the demo project. The asset list builder shall be in the top of the builder list.

This builder will build the assets of voice recognition.

13. Build the project and launch the apk to the Android device

14. The app will be able to take the voice commands that use the words listed in the digits.gram file.


Friday, July 04, 2014

Learning Android Wear programming

Due to hurricane Arthur, most of my July 4th outdoor activities have been canceled, and I decided to start learning Android Wear programming.

Following the tutorial at http://developer.android.com/training/wearables/apps/creating.html, I have updated my emulators to support SDK Level 20, assign ARM image, and downloaded the Android Wear app from Google Play store.

I am using an older Android phone. After downloading Android Wear app from Google Play store and installed it, the device suggested I need to update Google Search service and Google Play app. After these 2 apps got updated, the Android Wear app started on the phone.

Developer needs to
start up the emulator first, and
wait for the emulator to fully start up with home screen displayed
connect the phone to the PC/laptop
forward the port adb -d forward tcp:5601 tcp:5601 in the command prompt of Android Studio
start up the Android Wear app on the device

In the Android Wear app on the phone, a view similar to the home screen of the emulator is also displayed. There is an watch icon in the action bar on the left side, next to it is the title "Emulator", below it is the status. On the right side of the action barm there is an Android Smartwatch icon, settings icon, and menu. tapping on watch icon, the status of the emulator in the Android Wear app would say "Connected". Now we can see the notifications on the phone is available on the emulator.

If the phone can not connect to the emulator, please unplug the phone from the PC/laptop, and plug it again, it is also helpful to wipe the data when the emulator is started.




Friday, June 27, 2014

Android Studio 0.8 beta: where is the menu item for Android SDK Manager

Google released the Android Studio beta 0.8.

It is said developer can not apply this update to version 0.6, and need to download the latest version 0.8 and install it directly.

That was what I did today. But I also removed the older version, and did not import the settings of version 0.6.

In the new version, when I first looked for the Android SDK Manager under "Tools" I could not see it in the dropdown list.

I was wondering where the menu item is hidden, and tried to find it out, because I need to install other SDKs.

So I checked several menu and did not find it.

After a while I realized it may be hidden at the end of the drop down list and it would not be seen under "Tool".

In Preferences -> Menu and Toolbars ->  Main Menu ->Tools, there is an item "Android". Under this item, there are  AVD Manager, SDK Manager, Android Device Monitor, etc..

In the right pane of the Preference Window, there are buttons allowing user to move up, move down, buttons. I moved the item "Android" up close to its parent node "Tools".

But I still could not see the item "Android" under "Tools".

OK, now I have moved the item "Android" to the first child node position under "Tools", and I have run the SDK manager from Android Studio startup window, and I can see the item "Android" from the dropdown menu of "Tools".

Sunday, June 15, 2014

Use app to dial into conference call automatically on Android device

Mobile device is so widely used in the population. User can setup exchange mail account on the device, and sync the corporate emails, calendar events including the conferences calls.

Traditionally when people dial into the conference, they need to type in the phone numbers, pin, and password. It is not very easy for people to remember these numbers, and sometimes when people type the numbers on the virtual dial pad, they could make mistake, as a result they have to dial again.

One solution for this issue is to use the so called "one-click-to-dial-in" approach. The mobile app can dial in to the conference when user clicks on the event. This feature has been implemented in early mobile platforms such as Blackberry and iOS. The Android platform does not provide this feature in its build-in feature. User either to write the app or install the app developed by others, and there are bunches of apps claim to have this feature implemented.

However due to patent issues among the vendors of mobile platforms and among OEMs and mobile platform owners, it is not easy to do it on Android platform.

Basically, like mentioned above, there are 3 parts in the dialing numbers -
1. the conference call phone number, the bridge
2. the conference id or the meeting id
3. the password, or the attendee id
Some conference call service vendors also ask user to press the # keys.

In Android platform, developer can use the Intent to make a phone call.
Intent callIntent = new Intent(Intent.ACTION_CALL, Uri.parse("tel:" + Uri.encode(callString));callIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);context.startActivity(callIntent); 
The callString  contains the dialing numbers.

For simple calls, the callString contains the phone numbers, for example, 9732581301, and there is not problem for the call to go through.

It becomes a little bit complicated when dealing with the conference call. The callString needs to contains all the numbers xxxxxxxxxx yyyyyyyyy zzzz, where xxxxxxxxxx is the conference call phone number (bridge), yyyyyyyyy is the conference/meeting id, and zzzz is the password or attendee id.

The problem is how to put these parts together. Generally it is suggested that pause(s) is needed between these 3 parts. The pause is represented by ",", it is about 2 seconds. The the dialing string would looks like 8889991111,,,732596101,,,8901. When we consider that the conference call service would ask user to press 3 key after the pin (meeting id number) is passed to the system, the calling string needs to be changed to 8889991111,,,732596101#,,,8901#.

In my test cases, when this format is used to call in conference by the app, the call could go through sometimes, and sometimes the bridge number or the pin or the meeting number could not be recognized by the conference system, and the dial in failed.

There is a thread in bug report/feature request board at google.com, it talks about issue related to the problem above. The thread was started in 2010, the last post was March 2014, the total posts is around 120. There seems to be no good solution.

One app in Google Play store says it is aiming to solve this issue. From the toast string values shown when the app makes conference call, it looks like the app is using the format of
xxxxxxxxxx,yyyyyyyyy#**,,zzzz#1

By using this format, most of the time, the call could go through. The problem is all the numbers mentioned above are passed to the conference call system in one shot. This cause the phone to generate tones like the call has been hung up, the dial tones of the bridge number (pin, meeting id) can not be heard clearly.

In a blog http://blogs.technet.com/b/lync/archive/2010/09/22/setting-up-your-speed-dial-to-quickly-amp-automatically-call-into-your-meetings.aspx, it is said the format would look like
18001231234,,,{confID+#},,,*,*,{yourExtensionOrFullPhoneNumber+#},,{yourPIN+#}
I have not tested it, and am not sure if it would work.

However, I believe that one generic dial string format can not be applied to all devices on the market. Developer needs to test and use the proper dialing format for the devices, which means the program needs to get the device information, such as manufacturer, model, brand, carrier and country or region, then apply the format that works on the device. 

From my tests on different devices, I feel the issue may also related to the conference service providers, and the state of the service, such as workload of the service provider's servers, network traffic and network delays.

The possible ways to make the dialing strings work stable maybe rely on using the hidden telephony and network APIs in Android framework. I would look into this and update here later.


I got time to look into the dialing issue again. I manually played the dialing strings a some popular devices on the market, trying to simulate human dialing behavior, tricking the system think a real human is dialing, or let the code try its best to mimic the human dialing behavior.

For example, when dialing the numbers on the keypad, human usually dial 3 digits or 4 digits at one time, then dial 3 or 4 digits again to complete the whole dialing numbers.

Base on this pattern, the meeting id can be divided into several parts separated by a pause:
7325961010 => 732,596,101, or 7325,9610,1,

This solution works on most of the android devices I tested.

Hope the update above help.

Monday, April 28, 2014

Find the location of each event instances in the event instances table and the event table

Android provides the APIs allowing developers to query the calendar events and instances of these events if they are recurring.

In the page http://developer.android.com/guide/topics/providers/calendar-provider.html, the fields of the tables are listed.

If you quickly go through the Events table, Reminders table, Attendees table and Instances table, you would see that the 3 later tables all have the EVENT_ID field, but there is not such field in the Event table.

And in the Instances table, there is not the field of event location, this field is listed in the Events table.

So the question is how to assign the location values in the Events in a Calendar to the instances.

If you read through the whole page carefully, you will see the hint.

In the paragraph below Figure 1, it is mentioned
"
CalendarContract.EventsThis table holds the event-specific information. Each row in this table has the information for a single event—for example, event title, location, start time, end time, and so on. The event can occur one-time or can recur multiple times. Attendees, reminders, and extended properties are stored in separate tables. They each have an EVENT_IDthat references the _ID in the Events table.
"

Indeed there is an ID field in the Events table which is the same as those EVENT_ID fields in the other tables.

This _ID is inherited from the base class "BaseColumns", and can be used in the  query string of the Events table.

Now we can use the value of this field, _ID, to match the EVENT_ID in the other tables and assign the field values in the Events table to the Events instances.




Tuesday, March 25, 2014

Updating to ADT 22.6.1

while trying to update ADT to 22.6.1 I ran into the problem twice, as I am keeping copies of Eclipse and Android Studio on several of my machines.

The first incident happened when I updated ADT of the Eclipse IDE on my Windows 8 laptop.
After updating, the build always complains the mismatch versions of Android SDK, the 2nd time it occurred while I updated it for the Android Studio on my MBP.

For the issue on Eclipse, I finally chose to update the ADT manually as it is suggested at the bottom of this page http://developer.android.com/sdk/installing/installing-adt.html, then everything became smooth.

For the problem with the Android Studio, I wasted a lot of time to understand why the SDK manager stopped working after Android SDK Tool was updated to 22.6.1.

After installing SDK Tool 22.6.1, I could not have the UI of Android SDK Manager when I started it from Tools -> Android -> SDK Manager, although it was OK to launch the UI when I ran it in the command prompt (SDK->Tools->Android), and I have to uninstalled the Android Studio to get it straight.

After failing 2 or 3 times, I decided to install the Android SDK Tool 22.6.1 first and only install this updates in the step. Then after installing it, everything became OK, and I started to update other packages, such as the Android Wear Preview and Google Support package in Extras.