The HTC Lock Screen API allows apps to offer people interactive customizations for the lock screen on some of HTC's latest devices like the One X. For example, the LockScreenDemo sample code downloaded with the SDK add-on from the Android SDK manager generates a touchable alarm clock in the center of the lock screen in the screenshot below:
The user chooses to use a lock screen provided by an app in their phone settings, under the personalization option. The app must not have a registered remote control client via the AudioManager.registerRemoteControlClient method for the chosen lock screen to appear. This is because registering a remote control client causes media information and playback controls to appear in the same area instead..
For this sample code, when touched, the alarm clock starts to animate bouncing side to side:
An Activity in the application should detect the lockscreen API, enable the service, and prompt the user to use the lockscreen if desired. The lock screen offered by the app will be offered in the phone's personalization settings once the service is enabled. Here is the Activity that does that from the LockScreenDemo sample code:
public class SetupActivity extends Activity {
private static final String LOG_TAG = "LockScreenDemo SetupActivity";
private static final String NEW_LOCKSCREEN_API_CLASS_NAME = "com.htc.lockscreen.fusion.open.SimpleEngine";
@Override
protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.setup);
final TextView text = (TextView) findViewById(R.id.text);
// Check for lockscreen API.
final boolean isNewLockscreenApiSupported = isNewLockscreenApiSupported();
Log.i(LOG_TAG, "isNewLockscreenApi: " + isNewLockscreenApiSupported);
// If not supported, let the user know and finish executing.
if ( !isNewLockscreenApiSupported ) {
text.setText(R.string.setup_prompt_not_supported);
return;
}
// Otherwise enable lockscreen service and let user know how to enable.
text.setText(R.string.setup_prompt_supported);
final ComponentName name = new ComponentName(this, SampleService.class);
Log.i(LOG_TAG, "enabling service: " + name);
final PackageManager pm = (PackageManager) getPackageManager();
pm.setComponentEnabledSetting(name, PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP);
}
/**
* @return true if the lockscreen API is supported
*/
public static final boolean isNewLockscreenApiSupported() {
try {
Class.forName(NEW_LOCKSCREEN_API_CLASS_NAME);
return true;
} catch (final ClassNotFoundException e) {
}
return false;
}
}
Emulator test mode:
The user chooses to use a lock screen provided by an app in their phone settings, under the personalization option. The app must not have a registered remote control client via the AudioManager.registerRemoteControlClient method for the chosen lock screen to appear. This is because registering a remote control client causes media information and playback controls to appear in the same area instead..
The app uses the AndroidManifest.xml to define the services it provides to render the lock screen. One service provides the lock screen for normal use. The other service provides the lock screen when the user is previewing it in the personalization settings. Permissions and libraries required by the lock screen also have to be declared in this file. The lock screen service should be defined in the manifest as disabled and only enabled when the lockscreen API is detected to prevent issues with older phones that had an incomplete API. Here is the relevant section of the manifest of the LockScreenDemo sample code with examples of the needed services:
=================================================================================
<uses-permission a:name="android.permission.WAKE_LOCK"/>
<uses-permission a:name="com.htc.idlescreen.permission.IDLESCREEN_SERVICE" />
<application a:icon="@drawable/icon"
a:label="@string/app_name">
<!-- Libraries needed for the lockscreen API. -->
<uses-library a:name="com.htc.lockscreen.fusion"
a:required="false" />
<uses-library a:name="com.htc.fusion.fx"
a:required="false" />
<!-- Activity to enable lockscreen service and prompt user on how to select it. -->
<activity a:name="com.htc.sample.idlescreen.SetupActivity">
<intent-filter>
<action a:name="android.intent.action.MAIN" />
<category a:name="android.intent.category.DEFAULT" />
<category a:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<!-- Service to show user a preview of the lockscreen in the lockscreen chooser. -->
<service a:name="SamplePreviewService"
a:process=":preview" >
<intent-filter>
<action a:name="com.htc.lockscreen.idlescreen.preview.IdleScreenService"/>
</intent-filter>
</service>
<!-- Service to show user custom interactive lockscreen content before they unlock their phone. -->
<!-- Disabled by default so does not break on earlier versions of OpenSense where the API was different. -->
<service a:name="SampleService"
a:permission="com.htc.idlescreen.permission.IDLESCREEN_SERVICE"
a:enabled="false">
<intent-filter>
<action a:name="com.htc.lockscreen.idlescreen.IdleScreenService"/>
</intent-filter>
<!-- Data that describes your lockscreen to the user in the lockscreen chooser. -->
<meta-data a:name="com.htc.lockscreen.idlescreen"
a:resource="@xml/idlescreen"/>
</service>
</application>
<uses-permission a:name="com.htc.idlescreen.permission.IDLESCREEN_SERVICE" />
<application a:icon="@drawable/icon"
a:label="@string/app_name">
<!-- Libraries needed for the lockscreen API. -->
<uses-library a:name="com.htc.lockscreen.fusion"
a:required="false" />
<uses-library a:name="com.htc.fusion.fx"
a:required="false" />
<!-- Activity to enable lockscreen service and prompt user on how to select it. -->
<activity a:name="com.htc.sample.idlescreen.SetupActivity">
<intent-filter>
<action a:name="android.intent.action.MAIN" />
<category a:name="android.intent.category.DEFAULT" />
<category a:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<!-- Service to show user a preview of the lockscreen in the lockscreen chooser. -->
<service a:name="SamplePreviewService"
a:process=":preview" >
<intent-filter>
<action a:name="com.htc.lockscreen.idlescreen.preview.IdleScreenService"/>
</intent-filter>
</service>
<!-- Service to show user custom interactive lockscreen content before they unlock their phone. -->
<!-- Disabled by default so does not break on earlier versions of OpenSense where the API was different. -->
<service a:name="SampleService"
a:permission="com.htc.idlescreen.permission.IDLESCREEN_SERVICE"
a:enabled="false">
<intent-filter>
<action a:name="com.htc.lockscreen.idlescreen.IdleScreenService"/>
</intent-filter>
<!-- Data that describes your lockscreen to the user in the lockscreen chooser. -->
<meta-data a:name="com.htc.lockscreen.idlescreen"
a:resource="@xml/idlescreen"/>
</service>
</application>
=================================================================================
The defined service must extend SimpleIdleScreenService and return a class that extends SimpleEngine from the onCreateEngine method. This engine sets the content view to use and defines how the lockscreen reacts to events. Compared to an Activity set to show in front of the system keyguard, using the HTC Lock Screen API allows working with the rest of the HTC lock screen functionality such as dragging user chosen apps into the circle to start them quickly. Compared to Live Wallpapers, the HTC Lock Screen API allows using full View hierarchies with click listeners. Live Wallpapers only support detecting touch to the entire surface at once and requires the developer to handle fine grained details. MotionEvents for ACTION_DOWN are limited, however, due to the need not to respond inside user's pockets. An example touch interaction is visible at the bottom of the LockScreenDemo service source code:
=================================================================================
public class SampleService extends SimpleIdleScreenService {
@Override
public SimpleEngine onCreateEngine() {
return new IdleScreenRemoteEngine();
}
public class IdleScreenRemoteEngine extends SimpleEngine implements OnClickListener, OnTouchListener{
private Button mButton;
private AlarmAnime mClock;
private boolean mStart = false;
public IdleScreenRemoteEngine() {
}
public void onCreate(SurfaceHolder holder) {
this.setContent(R.layout.lockscreen);
this.setShowLiveWallpaper(false);
this.setBackground(R.drawable.background);
mClock = (AlarmAnime)this.findViewById(R.id.clock);
mButton = (Button)this.findViewById(R.id.button1);
mButton.setOnClickListener(this);
mButton.setOnTouchListener(this);
}
public void onStart() {
}
public void onResume() {
}
public void onPause() {
}
public void onStop() {
}
public void onDestroy() {
}
@Override
public void onClick(View view) {
if (!mStart) {
mClock.startAnime();
mButton.setText(R.string.button2);
mStart = true;
}
else {
mClock.stopAnime();
mButton.setText(R.string.button1);
mStart = false;
}
}
@Override
public boolean onTouch(View arg0, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
skipShowHint();
}
return false;
}
}
}
@Override
public SimpleEngine onCreateEngine() {
return new IdleScreenRemoteEngine();
}
public class IdleScreenRemoteEngine extends SimpleEngine implements OnClickListener, OnTouchListener{
private Button mButton;
private AlarmAnime mClock;
private boolean mStart = false;
public IdleScreenRemoteEngine() {
}
public void onCreate(SurfaceHolder holder) {
this.setContent(R.layout.lockscreen);
this.setShowLiveWallpaper(false);
this.setBackground(R.drawable.background);
mClock = (AlarmAnime)this.findViewById(R.id.clock);
mButton = (Button)this.findViewById(R.id.button1);
mButton.setOnClickListener(this);
mButton.setOnTouchListener(this);
}
public void onStart() {
}
public void onResume() {
}
public void onPause() {
}
public void onStop() {
}
public void onDestroy() {
}
@Override
public void onClick(View view) {
if (!mStart) {
mClock.startAnime();
mButton.setText(R.string.button2);
mStart = true;
}
else {
mClock.stopAnime();
mButton.setText(R.string.button1);
mStart = false;
}
}
@Override
public boolean onTouch(View arg0, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
skipShowHint();
}
return false;
}
}
}
=================================================================================
public class SetupActivity extends Activity {
private static final String LOG_TAG = "LockScreenDemo SetupActivity";
private static final String NEW_LOCKSCREEN_API_CLASS_NAME = "com.htc.lockscreen.fusion.open.SimpleEngine";
@Override
protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.setup);
final TextView text = (TextView) findViewById(R.id.text);
// Check for lockscreen API.
final boolean isNewLockscreenApiSupported = isNewLockscreenApiSupported();
Log.i(LOG_TAG, "isNewLockscreenApi: " + isNewLockscreenApiSupported);
// If not supported, let the user know and finish executing.
if ( !isNewLockscreenApiSupported ) {
text.setText(R.string.setup_prompt_not_supported);
return;
}
// Otherwise enable lockscreen service and let user know how to enable.
text.setText(R.string.setup_prompt_supported);
final ComponentName name = new ComponentName(this, SampleService.class);
Log.i(LOG_TAG, "enabling service: " + name);
final PackageManager pm = (PackageManager) getPackageManager();
pm.setComponentEnabledSetting(name, PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP);
}
/**
* @return true if the lockscreen API is supported
*/
public static final boolean isNewLockscreenApiSupported() {
try {
Class.forName(NEW_LOCKSCREEN_API_CLASS_NAME);
return true;
} catch (final ClassNotFoundException e) {
}
return false;
}
}
The second sample project, LockScreenSupportDemo wraps the current API to provide the ability to test your code in the emulator as well as testing on current Sense 4 roms that have not yet been updated to the current lock screen API. The older API (referred to as legacy in the sample project) is deprecated but provided for early access testing on current devices.
The following code illustrates the common code implementation of the lock screen example found in the previous example with one minor change, instead of using a button, a touch event is handled to toggle the clock's movement.
public class LockScreenImpl extends LockScreenImplBase implements LockScreenListener, OnTouchListener {
private AlarmAnime mClock;
private boolean mStart;
public LockScreenImpl(SampleService.IdleScreenRemoteEngine context) {
super(context);
}
// support for older deprecated API
public LockScreenImpl(SampleServiceLegacy.IdleScreenRemoteEngine context) {
super(context);
}
// support for running in an activity (emulator)
public LockScreenImpl(Activity context) {
super(context);
}
@Override
public void onCreate(SurfaceHolder holder) {
setContent(R.layout.main);
setShowLiveWallpaper(true);
mClock = (AlarmAnime) findViewById(R.id.clock);
mClock.setOnTouchListener(this);
}
@Override
public boolean onTouch(View view, MotionEvent event) {
if (view.getId() == R.id.clock) {
if (event.getAction() == MotionEvent.ACTION_UP) {
if (!mStart) {
Log.w("SampleService", "START animation");
mClock.startAnime();
mStart = true;
} else {
Log.w("SampleService", "STOP animation");
mClock.stopAnime();
mStart = false;
}
}
else if (event.getAction() == MotionEvent.ACTION_DOWN) {
skipShowHint();
}
return true;
}
return false;
}
@Override
public void onStart() {
}
@Override
public void onResume() {
}
@Override
public void onPause() {
}
@Override
public void onStop() {
}
@Override
public void onDestroy() {
}
}
private AlarmAnime mClock;
private boolean mStart;
public LockScreenImpl(SampleService.IdleScreenRemoteEngine context) {
super(context);
}
// support for older deprecated API
public LockScreenImpl(SampleServiceLegacy.IdleScreenRemoteEngine context) {
super(context);
}
// support for running in an activity (emulator)
public LockScreenImpl(Activity context) {
super(context);
}
@Override
public void onCreate(SurfaceHolder holder) {
setContent(R.layout.main);
setShowLiveWallpaper(true);
mClock = (AlarmAnime) findViewById(R.id.clock);
mClock.setOnTouchListener(this);
}
@Override
public boolean onTouch(View view, MotionEvent event) {
if (view.getId() == R.id.clock) {
if (event.getAction() == MotionEvent.ACTION_UP) {
if (!mStart) {
Log.w("SampleService", "START animation");
mClock.startAnime();
mStart = true;
} else {
Log.w("SampleService", "STOP animation");
mClock.stopAnime();
mStart = false;
}
}
else if (event.getAction() == MotionEvent.ACTION_DOWN) {
skipShowHint();
}
return true;
}
return false;
}
@Override
public void onStart() {
}
@Override
public void onResume() {
}
@Override
public void onPause() {
}
@Override
public void onStop() {
}
@Override
public void onDestroy() {
}
}
As with the previous sample, life cycle methods are provided here but as overridden methods. The full source code for the wrapper is available as part of the sample project.
Emulator test mode:
Source: HTCDEV