顯示具有 Android 標籤的文章。 顯示所有文章
顯示具有 Android 標籤的文章。 顯示所有文章

2012-02-04

Implement an AppWidget for BozaAlarm - Part I: Limitation

Today, I released BozaAlarm v4.10 to the Android Market. What's new in this version is the simple AppWidget to display the next enabled alarm.


Here, I'd like to talk about how I implemented this simple widget. Before I go too far, you probably need to take a look at least these two topics,

  1. AppWidgetProvider
  2. App Widget Design Guidelines
Just as I said before, there are some limitations in App Widget design and understand these should save you some time.

Basically, the Launcher process hosts AppWidgetHostView for each App Widget and talk with your process through RPC calls. You can image there are many RPC (binder) calls between Launcher and your process. That's why you will use RemoteViews to package your update actions in your process and apply them on the App Widget on the Launcher side. In fact, the RemoteViews is implemented as Command pattern in software terminology.

Now, you can imagine why AppWidget design is so restricted.

Moreover, because it's always unsafe to load classes from other process in a process, you're not allowed to use custom classes in your widget layout. Only built-in widget classes can be used in your layout xml file and only methods tagged by RemotableMethod in these classes are allowed.

Experiment the Tips for reducing APK file size

I came across this article: Tips for reducing APK file size at SonyEricsson Developer blog. Among them, the one I am aware of is the PNG file optimization. So, I took some time to experiment it.

First, I downloaded the GUI wrapper of command-line optimizer ImageOptim and optimize the PNG files at res/ directory. According to the result, it reduce total file size from 748K bytes to 700K bytes.

Second, I recompile the release binary of my application. But, the size of APK file remains the same as the one w/o PNG optimization.

Hmm. I repeated this process some times to make sure I didn't miss some important steps. While I was wondering, I noticed some messages spewed out during compilation like,
  [crunch] Processing image to cache: /Users/yenliangl/Work/Android/bozaalarm-android/res/drawable-hdpi/handler_app.png => /Users/yenliangl/Work/Android/bozaalarm-android/bin/res/drawable-hdpi/handler_app.png
Looks like PNG optimization has been included in the standard Android tool v14??

This should confirm my guess. From official site of Android tool, it says that in revision 14, aapt optimizes PNG during compilation.

png processing in aapt.
When aapt packages the resources, its main goal is to compile the XML to binary format and to create a resource table with all the resource values (string, color, ids, etc...). Additionally, it processes the png files to optimize them (for instance, pre-processing of 9-patches).
Because the aapt process is not incremental, this means every build goes through all png files and processes them always. For large projects with numerous (and/or large) png this process could take a long time.
Revision 14 now processes the png files outside of the aapt packaging step and caches them. Only modified png files are re-processed. 



2012-01-06

TimePicker/DatePicker with keyboard input problem

I got some complaints from users saying they can't use keyboard to input in TimePicker or DatePicker widgets and found that this bug has been posted on Android developer forum for some time.

The quick workaround for this bug is,

2011-05-16

My new Android application TallyCounter

I've published a new application, TallyCounter, on the Android Market.


It's very easy to use. You can download it on this link.

2011-05-04

Run Android CTS

Recently, I was asked to look into CTS report of my work. So, I downloaded CTS on Android website and started running it. After hours of experiments, these are my experiences that should save you some time.
  • Use SDK 1.6r1 against your CTS. Don't use SDK r10.
  • Modify maxTestCount to -1 in your $CTS_ROOT/repository/host_config.xml
  • Don't run large test plan. Predefined test plans in the CTS are,
    • CTS plan
      • VM plan
      • Java plan
      • Android plan
        • RefApp plan
        • AppSecurity plan
      • Signature plan
    • Performance plan
  • If you run into errors complaining this "Installing met .... due to unknown reasons", try to run a customized smaller test plan composed of nonExecuted packages.
    • add --plan reset_of_packages. It prompts you available packages to add into this plan and you can add packages that haven't run yet since the error happens.
  • You can run CTS in Linux, Windows or Ubuntu. The whole CTS suite is just a script wrapped on top of "java -cp". It should be platform-independent. If you are running CTS on Windows/cygwin system, 
    • Move the definition of ${JARS} in your startcts to the following,
      JARS=`cygpath -w -p ${CTS_LIB}:${DDM_LIB}:${JUNIT_LIB}:${HOSTTEST_LIB}`
    • and, this line
      java ${JAVA_OPTS} -cp ${JARS} com.android.cts.TestHost `cygpath -w ${CONFIG}` "$@" ${DDCONFIG}

2011-04-27

New alpha version for features I've implemented since v3.07b

I've compiled a new alpha version that includes features I've implemented since the release of v3.07b on the Android Market. These features include,
  1. New alarm action for launching application.
  2. New equation mode on the AlarmAlert activity to unlock snooze/dismiss buttons.
  3. The redesigned password input widget.
Screenshots are uploaded to my Picasa space.

Select an application to launch

Set password for an alert

Solve a simple equation to unlock snooze/dismiss buttons

Enter specified or random-generated password to unlock snooze/dismiss buttons
You can install this new alpha version from my Dropbox space.

2011-04-26

New alarm action to launch an application main activity

I was struggling to add this feature my application because I believe it won't do much help. Still, I tried to implement this as a practice. It's pretty easy to get all installed applications on the phone by these lines.



and you can launch the main activity of an application by
Intent launchIntent = pm.getLaunchIntentForPackage(packageName);
launchIntent.setFlags(Intent.FLAG_ACTIVIY_NEW_TASK|Intent.FLAG_ACTIVITY_NO_USER_ACTION);
context.startActivity(launchIntent);

What I said it doesn't do much help is that you can only bring up an activity unless you know how to talk to it through its public interface.

2011-04-22

How to toggle power controls (Wi-Fi, GPS, bluetooth, brightness, sync)

This topic has been asked on Android forums regularly and sometimes you are told not to turn on/off GPS/Bluetooth without consents from users. That's true.

Despite this consent issue, what is the simplest way to toggle power controls programmatically? For Android 2.1+, my opinion is to take advantage of the code Settings already provides.

Intent intent = new Intent();
intent.setClassName("com.android.settings","com.android.settings.widget.SettingsAppWidgetProvider");
intent.addCategory(Intent.CATEGORY_ALTERNATIVE);
intent.setData(Uri.parse("custom:" + getButtonId()));
context.sendBroadcast(intent);

The getButtonId() returns the button id on the PowerControl widget. You can try to put this widget on your device. It is not impossible that these button ids are changed in the future Android release or by vendors. But, I have to say it's not very likely to happen.

  • Wi-Fi:  0
  • Brightness: 1
  • Sync: 2
  • GPS: 3
  • Bluetooth: 4

Another advantage of using this is the reduction of hardware permissions you have to declare.

2011-04-21

New alpha version of BozaAlarm v3.01a

I've implemented these experimental changes into this new alpha version - v3.01a.

  1. GPS and Bluetooth toggling action.
  2. Wi-Fi action has no toggling option any more. It toggles current Wi-Fi state.
  3. Honor user's group-by setting when viewing alarms through View/All.
This new test release can be downloaded here in my Dropbox space.

2011-04-09

How CursorAdapter works

For people who are new to Android development, sometimes, they are wondering why CursorAdapter know when to refresh your UI and how the connection between your model and view is set?

I looked into the Android source code and learned that two key method calls will establish this connection. This following diagram was drawn in order to understand how your model (Database) and View (AdapterView) are bound together. From this diagram, we can see
  1. Cursor.setNotificationUri(ContentResolver, Uri)
  2. ContentResolver.notifyChange(...) 
The first call will insert an hash entry (Uri, Cursor) into the hash map hold by ContentResolver (ContentService) so that whenever the data pointed by this Uri object is changed, it will requery the Cursor object.

But the question is how ContentResolver knows your data is changed? It's your responsibility to tell it. In your implementation of ContentProvider or internal database, when you insert, delete, update the data successfully, you need to call ContentResolver.notifyChange(Uri, ...) to let ContentResolver know it should requery the hosted Cursor object keyed by the passed Uri.

As for the internal database, if it is SQLiteDatabase, I prefer using non-exported ContentProvider because its well-established interface.



Customize the layout of PreferenceActivity

In my application, I'd like to have non-Preference items shown on a PreferenceActivity like the TimePicker widget below,


Actually, it is easy to get this. The PreferenceActivity sets its content view to a ListView with list as its view id.
@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(com.android.internal.R.layout.preference_list_content);
        
        mPreferenceManager = onCreatePreferenceManager();
        getListView().setScrollBarStyle(View.SCROLLBARS_INSIDE_OVERLAY);
    }
and this preference_list_content is just a ListView,

which can be replaced by your own layout xml file as long as it contains one ListView with android:id/list as its id. You can put as many widgets as you want in this layout xml file and use setContentView() to use it in your onCreate() method.

2011-04-08

Why TaskKiller prevents my Alarm apps from working

It is well-known that task killers on the Android Market prevents Alarm applications from working. But why? To understand this, we should understand how alarms in Android are handled. A hardware alarm event should go through 2 layers to bring up your software component.
  1. Hardware alarm event
  2. AlarmManagerService
  3. Your application components to handle alarms
The AlarmManagerService stores the information required to link hardware alarm event to your application components. The task killer actutually removes these information from AlarmManagerService and this is why your Alarm application doesn't work after a task killer kills your application.

For devices running Android 2.1 and below, the method the task killer uses to kill an application is ActivityManager.restartPackage(String) that in turn triggers the UninstallReceiver defined in AlarmManagerService to remove all links registered by your application.
class UninstallReceiver extends BroadcastReceiver {
        public UninstallReceiver() {
            IntentFilter filter = new IntentFilter();
            filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
            filter.addAction(Intent.ACTION_PACKAGE_RESTARTED); // <------ this line
            filter.addDataScheme("package");
            mContext.registerReceiver(this, filter);
        }
        
        @Override
        public void onReceive(Context context, Intent intent) {
            synchronized (mLock) {
                Uri data = intent.getData();
                if (data != null) {
                    String pkg = data.getSchemeSpecificPart();
                    removeLocked(pkg);
                    mBroadcastStats.remove(pkg);
                }
            }
        }
    }

But, in Android 2.2, things changed. The restartPackage method is changed to be just a wrapper of a new method killBackgroundProcesses. So, task killers on the Market are doing the same thing as default Out-Of-Memory (OOM) killer. Although we can still install them to proactively kill processes, but they are not necessary any more.

A Preference that get result back from Activity it started

We know that Preferences in Android don't provide public interface to get result back from the activities they launched. The only clue we can find is that RingtonePreference has done the exact thing in protected level. Fortunately, It is very easy to extend RingtonePreference.

So, this is the behavior of RingtonePreference that we want to achieve,
  1. Click to start our activity (not RingtonePicker)
  2. Get result back from activity and store it as SharedPreference for later use. (any kind of result other than Ringtone Uri)
you definitely need to do some modifications on RingtonePreference as I did.

I am developing a ContactPreference in my application that starts an activity for user to pick contacts and returns them back to the ContactPreference. Here is what I did by overriding RingtonePreference,

public class ContactPreference extends android.preference.RingtonePreference {
    // 
    @Override
    public boolean onActivityResult(int requestCode, int resultCode, Intent data) {
        if (super.onActivityResult(requestCode, resultCode, data)) {
            ArrayList addrList =null;
            if (data != null) {    
                addrList = data.getStringArrayListExtra(ContactPicker.EXTRA_ADDRESS_LIST);
            }
            onSaveAddressList(addrList);                       
            return true;                     
        }                          
        return false;             
    }

    @Override
    protected void onPrepareRingtonePickerIntent(Intent intent) {
        // Remove all extras already placed by RingtonePreference
        intent.setAction(null);
        intent.removeExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI);
        intent.removeExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_DEFAULT);
        intent.removeExtra(RingtoneManager.EXTRA_RINGTONE_DEFAULT_URI);
        intent.removeExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_SILENT);
        intent.removeExtra(RingtoneManager.EXTRA_RINGTONE_TYPE);

        // Set the activity we want to start.
        intent.setClass(getContext(), ContactPicker.class);

        // Addresses that was picked. So that your activity can handle it, s.t., checked them on/off.
        ArrayList addrList = onRestoreAddressList();
        if (addrList != null) {
            intent.putStringArrayListExtra(ContactPicker.EXTRA_ADDRESS_LIST, addrList);
        }
    }

2011-03-25

aidl file of Winamp

I've been experimenting adding support for Winamp in my application that is supposed to be released soon.   But the first thing I need to consider is a correct Android interface file that describes the interface Winamp's music service has. After several trial&error, I have some findings,

  1. Winamp uses built-in MediaStore provider.
    • Playlists created in built-in Music application can be seen in Winamp and playlists created from Winamp can be seen in built-in Music application.
  2. Winamp uses built-in IMediaPlaybackService.aidl which package names replaced with com.nullsoft.winamp. Still, it changes some code that makes my integration difficult. Here are my steps to guess its aidl interface,
    • Installed Winamp in a rooted device and debug it.
    • Because Winamp doesn't obfuscate its code, we can use Java's reflection to see its class and method interfaces easily. In jdb,
      • Run 'classes' to see all classes started with com.nullsoft.winamp. You can see one line for com.nullsoft.winamp.IMediaPlaybackService
      • Run 'methods com.nullsoft.winamp.IMediaPlaybackService' to list all method declaration of this class. You can see it not having differences than standard IMediaPlaybackService.
      • com.nullsoft.winamp.IMediaPlaybackService duration()
        com.nullsoft.winamp.IMediaPlaybackService enqueue(long[], int)
        com.nullsoft.winamp.IMediaPlaybackService getAlbumId()
        com.nullsoft.winamp.IMediaPlaybackService getAlbumName()
        com.nullsoft.winamp.IMediaPlaybackService getArtistId()
        com.nullsoft.winamp.IMediaPlaybackService getArtistName()
        com.nullsoft.winamp.IMediaPlaybackService getAudioId()
        com.nullsoft.winamp.IMediaPlaybackService getMediaMountedCount()
        com.nullsoft.winamp.IMediaPlaybackService getPath()
        com.nullsoft.winamp.IMediaPlaybackService getQueue()
        com.nullsoft.winamp.IMediaPlaybackService getQueueLen()
        com.nullsoft.winamp.IMediaPlaybackService getQueuePosition()
        com.nullsoft.winamp.IMediaPlaybackService getRepeatMode()
        com.nullsoft.winamp.IMediaPlaybackService getShuffleMode()
        com.nullsoft.winamp.IMediaPlaybackService getTrackName()
        com.nullsoft.winamp.IMediaPlaybackService isPlaying()
        com.nullsoft.winamp.IMediaPlaybackService moveQueueItem(int, int)
        com.nullsoft.winamp.IMediaPlaybackService next()
        com.nullsoft.winamp.IMediaPlaybackService open(long[], int)
        com.nullsoft.winamp.IMediaPlaybackService openFile(java.lang.String, boolean)
        com.nullsoft.winamp.IMediaPlaybackService openFileAsync(java.lang.String)
        com.nullsoft.winamp.IMediaPlaybackService pause()
        com.nullsoft.winamp.IMediaPlaybackService play()
        com.nullsoft.winamp.IMediaPlaybackService position()
        com.nullsoft.winamp.IMediaPlaybackService prev()
        com.nullsoft.winamp.IMediaPlaybackService quit()
        com.nullsoft.winamp.IMediaPlaybackService removeTrack(long)
        com.nullsoft.winamp.IMediaPlaybackService removeTracks(int, int)
        com.nullsoft.winamp.IMediaPlaybackService seek(long)
        com.nullsoft.winamp.IMediaPlaybackService setQueuePosition(int)
        com.nullsoft.winamp.IMediaPlaybackService setRepeatMode(int)
        com.nullsoft.winamp.IMediaPlaybackService setShuffleMode(int)
        com.nullsoft.winamp.IMediaPlaybackService stop()
        
    • Duplicate IMediaPlaybackService.aidl from built-in Music code and replace its package name, include this file in your project.
    • Although the code builds successfully, it doesn't work as expected.
      public void onServiceConnected(ComponentName name, IBinder binder) {
          com.nullsoft.winamp.IMediaPlaybackService s = com.nullsoft.winamp.IMediaPlaybackService.Stub.asInterface(binder);
          try {
              // s.isPlaying() and s.top() are working.
              if (s.isPlaying()) {
                  s.stop();
              }
      
              // s.open() broadcasts com.nullsoft.winamp.queuechanged if audio ids
              // are set successfully. 
              s.open(mData, 0);
      
              /* s.next() works but only plays one song and causes 
                 com.nullsoft.winamp.playstatechanged and com.nullsoft.winamp.metachanged
                 broadcasted. */ 
              s.next();
      
              /* s.play() doesn't work. Guess that MediaPlayer is not initialized if Winamp doesn't 
                 change too much from standard Music */
              // s.play();
          } catch (RemoteException e) {
          }
      }
      
    • I should do more experiments to guess how it responds to different parameters and modes.
This process (reverse engineering) is normally what authors of an application don't want people do on their applications.

OBFUSCATE YOUR APPLICATION IF YOU DO MIND.

2011-01-25

Use Emacs/JDEE in Android Development

Eclipse/ADT is a great tool for Android development. But for people using Emacs, it is difficult to get used to it. Fortunately, JDEE is easy to be customized to work on Android development. Here are my settings on my MacBookPro,

;; Load JDEE
(require 'jde)

(custom-set-variables
 ;; custom-set-variables was added by Custom.
 ;; If you edit it by hand, you could mess it up, so be careful.
 ;; Your init file should contain only one such instance.
 ;; If there is more than one, they won't work right.
 '(jde-global-classpath (quote ("~/android/out/target/common/obj/JAVA_LIBRARIES/framework_intermediates/classes.jar" "~/android/out/target/common/obj/JAVA_LIBRARIES/core_intermediates/classes.jar" "~/android/out/target/common/obj/JAVA_LIBRARIES/services_intermediates/classes.jar" "~/android/out/out/target/common/obj/JAVA_LIBRARIES/android.policy_intermediates/classes.jar")))
 '(jde-jdk-registry (quote (("1.5.0" . "/System/Library/Frameworks/JavaVM.framework/Versions/1.5.0"))))
 '(jde-sourcepath (quote ("~/android/source/frameworks/base/services" "~/android/source/frameworks/base/core/java" "~/android/source/dalvik/libcore/luni/src/main/java")))

Here, I have android source code in ~/android/source and build in ~/android/out. If you are using SDK, you should add android.jar in SDK to jde-global-classpath. Also, you can put these settings into a per-project file prj.el in your project top directory for JDEE to load.

For example, my prj.el in one of my project looks like,
(jde-project-file-version "1.0")

;; Reset class path and source path
(jde-set-variables
 '(jde-project-name "openalarm-android")
 '(jde-gen-buffer-boilerplate
   '("/**"
     "*  OpenAlarm - an extensible alarm for Android"
     "*  Copyright (C) 2010 Liu Yen-Liang (Josh)"
     "*"
     "*"
     "*  This program is free software: you can redistribute it and/or modify"
     "*  it under the terms of the GNU General Public License as published by"
     "*  the Free Software Foundation, either version 3 of the License, or"
     "*  (at your option) any later version."
     "*"
     "*  This program is distributed in the hope that it will be useful,"
     "*  but WITHOUT ANY WARRANTY; without even the implied warranty of"
     "*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the"
     "*  GNU General Public License for more details."
     "*"
     "*  You should have received a copy of the GNU General Public License"
     "*  along with this program. If not, see ."
     "*/"))
 '(jde-log-max 5000)
 '(jde-enable-abbrev-mode t)
 ;; Class path for browsing files and generate code templates
 '(jde-global-classpath
   (quote ("$ANDROID_SDK_ROOT/platforms/android-7/android.jar"
           "$HOME/projects/openalarm-android/bin")))
 '(jde-sourcepath
   (quote ("$HOME/projects/openalarm-android/src"
           "$HOME/android/eclair_21/frameworks/base/core/java")
          ))
 '(jde-compile-option-directory "$HOME/projects/openalarm-android/bin") 
 '(jde-complete-function (quote jde-complete-menu)) 
 '(jde-run-working-directory "$HOME/projects/openalarm-android/bin")
 '(jde-help-docsets
   (quote (("Android SDK Doc" "$ANDROID_SDK_ROOT/docs/index.html" nil))))
 '(jde-build-function (quote (jde-ant-build)))
 '(jde-ant-args "-emacs")
 '(jde-ant-complete-target t)
 '(jde-ant-enable-find t) 
 '(jde-ant-read-args nil)
 '(jde-ant-read-buildfile nil)
 '(jde-ant-read-target nil)
 '(jde-ant-use-global-classpath nil)
 '(jde-ant-working-directory "$HOME/projects/openalarm-openalarm/bin/") 
 '(jde-built-class-path (quote ("$HOME/projects/openalarm-android/bin")))
 )
These settings enable us using method completion, source browsing, etc. But, I am still experimenting if we can build android target in JDEE.

For MacOS users: the environment variables must be defined in ~/.MacOSX/environemtn.plist

2010-06-09

AppWidget design - Can I use my own custom widget?

The answer to the question is probably no. But, not impossible. If you take a look at the design of AppWidget class family. You should know that a class that doesn't meet all requirements below can't be inflated in the layout of an AppWidget.
  1. Annotated by RemoteViews
  2. Can't be found by PathClassLoader with default class path - "."
The first one is simple because RemoteViews is not private interface. You can annotate your custom class by RemoteViews as Android widgets do.

The second one is not easy to attack. You need to know where this dot refers to. It is actually defined as an environment variable BOOTCLASSPATH in a file init.rc wrapped in your boot.img or ramdisk.img.

export BOOTCLASSPATH /system/framework/core.jar:/system/framework/ext.jar:/system/framework/framework.jar:/system/framework/android.policy.jar:/system/framework/services.jar:/system/framework/org.startsmall.widget.jar
So, you need to,
  1. Wrap your widget class in a jar library.
    • Archive classes.dex only.
  2. Put your jar library into system.img.
    • The system.img can be decompressed by unyaffs. Decompress it and put jar library into system/framework.
    • Compress whole system/ directory back to system.img by mkyaffs2image.
  3. Modify init.rc to add custom widget jar into BOOTCLASSPATH.
    • The boot.img/ramdisk.img is just a cpio archive. Decompress  ramdisk.img, do modification to init.rc and archive it back as cpio file.
  4. Put the system.img and ramdisk.img to your avd directory (~/.android/avd/avd-XXX). Start emulator and use logcat to see whether your custom jar library is loaded by Android.
Although you can use your custom widget class now, but you may soon find you can't actually use non-RemotableViews annotated method through RemoteViews set methods. Defeated? Not really, if you are willing to hack Android, I guess you still can modify Android source code to make RemotableMethod visible to your custom widget class.

If you don't want to do it, you need to make your custom widget self-handled. You can't update custom widgets by set methods of RemoteViews, but you can send broadcasts to update it because AppWidgetHostView instantiates custom widgets in your context.

You can see from above that it is possible to write custom widget class to be used in AppWidget design only when you have permissions to access image files. This means either you work for a phone company or you make custom ROMs.

2010-05-24

Views that can be used along with RemoteViews

Views must be annotated RemoteView in order to be used in the layout file inflated by LayoutInflater. Look into source code of RemoteViews. You can find an interface that is used to do annotation.
@Target({ ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface RemoteView {
}

And, for views that supports RemoteViews have RemoteView annotation in the beginning of their class definition. For example,
@RemoteView
public class LinearLayout extends ViewGroup {
 // ...
}

In the latest Android source, views that supports RemoteViews are,
AbsoluteLayout.java:40:@RemoteView
AnalogClock.java:39:@RemoteView
Button.java:58:@RemoteView
Chronometer.java:45:@RemoteView
FrameLayout.java:47:@RemoteView
ImageButton.java:71:@RemoteView
ImageView.java:55:@RemoteView
LinearLayout.java:44:@RemoteView
ProgressBar.java:123:@RemoteView
RelativeLayout.java:66:@RemoteView
TextView.java:186:@RemoteView
ViewFlipper.java:38:@RemoteView

2010-04-21

Android UI Prototyping

If you are from Qt world which has excellent Qt-designer served as a good prototyping tool since its early version, you probably are disappointed about the lack of prototyping tools in Android world.

We already has DroidDraw, but it still has a long way to go, though. I came across a website and it mentions Android GUI Prototyping that adds Android prototyping into your Visio template sets. I am not familiar with Visio and I normally use the primitive toolkit (pen, paper and glue) to do prototyping. I don't know if this Visio stuff works for you.

2010-04-13

New release of OpenAlarm

A new version of OpenAlarm is released at 04/15/2010. This version has several minor bug fixes and some UI changes.
  • Fixed FC when no default ringtone exists.
  • Fixed silent alarm if no ringtone specified (Play fallback ringtone instead).
  • Fallback to normal mode if user selects password mode but no password set.
  • Users can pull phone number directly from contact database in Phone alarms. The rule of picking a number to call is
    1. Default phone number if exists. 
    2. First phone number.
    3. No phone number for this person and show "no phone number" error message along with the name of this person.
  • Justify the position of the banner so that it is at the center of the screen.
  • Use hardware search button to apply another search to an filtered result is NOT allowed anymore.
  • Insert device information to the e-mail caused by the clicking on my email address in the About dialog.
  • UI changes. Move New menu item to the main screen.
  • Add explicit on/off settings to ToggleSwitch.
這個版本修正了一些小臭蟲以及使用者介面上的一些小改變。
  • 修正當沒有預設響鈴時可能發生的強制關閉。
  • 修正當沒有設定響鈴時的無聲鬧鈴。使用緊急鬧鈴。
  • 如果使用者選擇密碼模式但忘記設定密碼,則使用正常模式。
  • 在設定電話鬧鈴時,使用者可以直接從聯絡人清單挑選聯絡人。電話號碼的選擇順序為,
    1. 預設號碼。
    2. 第一組號碼。
    3. 無號碼時則顯示錯誤訊息。
  • 對正OpenAlarm的Banner。
  • 對搜尋出的鬧鐘再執行搜尋並無太大意義。關閉這個功能。
  • 在關於此程式中,按我的email位址寄信給我,會在郵件中插入幾行除錯資訊。
  • 使用者介面的稍許更動。將加入新鬧鈴的選單項目移到主畫面。
  • 加入明確的開關設定。


2010-03-30

Android fragmentation makes my life harder :(

A recent article shows fragmentation of Android platform finally gets Google's attention. I've been suffered from it for quite some time. Distinct versions of Android were released in less than a year, not mentioned the different devices released by different vendors.

The article says Google wants to shift their focus from Android core to applications and decouple key applications from core platform which means we don't need to wait for firmware update. This is a good thing. But, does this really solve fragmentation? Of course not.

I really don't think fragmentation can be solved due to the double-edged characteristics of open-sourced Android (some analysts had been warned at Android's first debut).

For me, I owned a HTC magic and I don't really want to buy another top-selling Motorola Droid just for testing my application especially when mobile application development doesn't really pay my bills. I know some companies provide services of virtual devices for developers, like DeviceAnywhere,  but I also can conceive that game developers are unlikely to use this service.

I'd like to see following things on future Android platform,
  1. Users and developers are using same devices and same OS versions just like iPhone. This is unlikely to happen, though.
  2. Improved Android Market. In current environment, developer and users are separated. Users can leave comments but developers have no way to reply comments. I've been provided one feedback channel in OpenAlarm, but not a perfect way.