2

I have an Android Wear watch face that I'm trying to have vibrate the watch on the hour. It is working except in cases where the watch screen is off. According to the log statements, the handler method is called every minute and the chime method is called on the hour. If I'm debugging over bluetooth with the Moto 360, it works even with the screen off. If I install a release apk, it only vibrates if the screen is on. If the screen is off at the top of the hour, it wont vibrate until the screen comes back on. I have tried acquiring a wake lock before the vibrate with no luck. I'm thinking it may work if I acquire a wake lock in the onCreate and release it in the onDestroy but I would rather not do that to preserve battery. Another interesting tidbit is that I have another function that vibrates when certain data changes in the wearable data api and that is working with the screen off. Maybe the WearableListenerService wakes the watch up long enough for the vibrate to occur. Is there something wrong with my logic or is this a limitation of certain Android Wear devices?

Time change handler:

final Handler mUpdateTimeHandler = new Handler() {
    @Override
    public void handleMessage(Message message) {
        switch (message.what) {
            case MSG_UPDATE_TIME:
                MyLog.d("Time Tick Message Handler");
                doTimeTickStuff();

                long timeMs = System.currentTimeMillis();
                long delayMs = mInteractiveUpdateRateMs - (timeMs % mInteractiveUpdateRateMs);

                mUpdateTimeHandler.sendEmptyMessageDelayed(MSG_UPDATE_TIME, delayMs);

                break;
        }
    }
};

doTimeTickStuff()

private void doTimeTickStuff()
{
    MyLog.d("timetickstuff");

    try {
        mCalendar = Calendar.getInstance();

        int currMin = mCalendar.get(Calendar.MINUTE);

        if (currMin == 0) {
            hourlyChime();
        }
    }
    catch(Exception ex)
    {
        MyLog.e(ex, "Error occurred in time tick handler");
    }

    if (mIsVisible) {
        invalidate();
    }
}

hourlyChime()

private void hourlyChime(){
    Vibrator v = (Vibrator) getBaseContext().getSystemService(VIBRATOR_SERVICE);
    if (v.hasVibrator()) {
        MyLog.d("vibrating");
        v.vibrate(1000);
    } 
    else {
        MyLog.d("No vibrator");
    }
}

Update The solution that worked was to create an AlarmManager and register it with a broadcast receiver in the watch face onCreate then unregister the receiver in onDestroy

onCreate()

@Override
public void onCreate(SurfaceHolder holder) {
    super.onCreate(holder);

    mChimeAlarmManager =
            (AlarmManager) getSystemService(Context.ALARM_SERVICE);

    Intent ambientStateIntent = new Intent("packagename.HOURLY_CHIME");
    mChimePendingIntent = PendingIntent.getBroadcast(getApplicationContext(),
        1234, ambientStateIntent, PendingIntent.FLAG_UPDATE_CURRENT);

    WeatherTime.this.registerReceiver(chimeReceiver,
        new IntentFilter("packagename.HOURLY_CHIME"));

    long alarmMs = getMsTillNextHour() + System.currentTimeMillis();

    mChimeAlarmManager.setExact(
        AlarmManager.RTC_WAKEUP,
        alarmMs,
        mChimePendingIntent);
}

Broadcast Receiver

private BroadcastReceiver chimeReceiver = new BroadcastReceiver()
{
    @Override
    public void onReceive(Context context, Intent intent) {
        hourlyChime();

        mChimeAlarmManager.setExact(
            AlarmManager.RTC_WAKEUP,
            getMsTillNextHour() + System.currentTimeMillis(),
            mChimePendingIntent);
    }
};

onDestroy()

@Override
public void onDestroy() {
    mChimeAlarmManager.cancel(mChimePendingIntent);

    super.onDestroy();
}
Chris A.
  • 23
  • 4
  • What Logs do you get when it's supposed to vibrate on hour tick in ambient mode? – Louis CAD Dec 21 '15 at 15:45
  • Ambient mode or not, I get all the log statements I would expect. Most importantly, I get the "vibrating" log so it is able to get an instance of the vibrator and recognize that the watch has a vibrator but it does not vibrate unless the screen is on or I am debugging. – Chris A. Dec 21 '15 at 15:57
  • So it vibrates in ambient mode if you're debugging, right? How can you be certain that it reaches the vibrate code if you have the problem only while debugging (which is required to see logs, right?) – Louis CAD Dec 21 '15 at 16:04
  • You're correct, I can't be certain it is reaching the vibrate code in the release apk but I don't know why it would behave any different. I did a factory reset on the watch, installed the app and took a bug report after the top of the hour. The "DUMP OF SERVICE vibrator" portion showed no vibrations registered for my app but around the time the screen turned off I found "notification_cancel:" and "power_sleep_requested:" entries in the bug report. – Chris A. Dec 21 '15 at 18:42
  • You should display a circle (or a char, or anything) when your handler is called in ambient mode, and display something else, like a square when vibration succeeds, and a triangle when it fails – Louis CAD Dec 21 '15 at 19:29

2 Answers2

2

When the watch goes into ambient mode, it goes into a deep sleep. As a result, code written with Handler will not run. As a result, you should use AlarmManager. For details on how to implement this, you should refer to the "Update more frequently" section on this page about the always-on functionality of Android Wear.

With regards to Bluetooth debug mode, I suspect that it works because the watch never goes into deep sleep. The same happens when I develop apps while the watch is docked.

Lastly, as for the wake up frequency, I think your functionality is fine as it only fires once an hour. For others reading this, please refrain from waking the watch up more than once a minute as this will severely impact battery life. Always test your watch face for battery life before uploading to the Play Store.

Hoi
  • 599
  • 2
  • 7
2

in my project i use Alarm manager with MyIntentService extends IntentService. To wake up (on screen) device in onHandleIntent use following:

if (intent.getAction() != null) {
    tmp = intent.getAction();
    PowerManager powerManager = (PowerManager) getSystemService(POWER_SERVICE);
    wakeLock = powerManager.newWakeLock((PowerManager.SCREEN_BRIGHT_WAKE_LOCK | PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP), TAG);
    wakeLock.setReferenceCounted(true);
    if(!wakeLock.isHeld()) {
        wakeLock.acquire();
    }
}
Peter Hall
  • 53,120
  • 14
  • 139
  • 204