Merge "Modify WakeupMessage to only send the message if not canceled" into mm-wireless-dev

This commit is contained in:
Mitchell Wills
2016-02-25 23:36:29 +00:00
committed by Android Partner Code Review
3 changed files with 206 additions and 14 deletions

View File

@@ -21,7 +21,7 @@ import android.content.Context;
import android.os.Handler;
import android.os.Message;
/**
/**
* An AlarmListener that sends the specified message to a Handler and keeps the system awake until
* the message is processed.
*
@@ -33,19 +33,17 @@ import android.os.Message;
* the message, but does not guarantee that the system will be awake until the target object has
* processed it. This is because as soon as the onAlarmListener sends the message and returns, the
* AlarmManager releases its wakelock and the system is free to go to sleep again.
*
*/
public class WakeupMessage implements AlarmManager.OnAlarmListener {
private static AlarmManager sAlarmManager;
private final AlarmManager mAlarmManager;
private final Handler mHandler;
private final String mCmdName;
private final int mCmd, mArg1, mArg2;
private boolean mScheduled;
public WakeupMessage(Context context, Handler handler,
String cmdName, int cmd, int arg1, int arg2) {
if (sAlarmManager == null) {
sAlarmManager = context.getSystemService(AlarmManager.class);
}
mAlarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
mHandler = handler;
mCmdName = cmdName;
mCmd = cmd;
@@ -61,19 +59,43 @@ public class WakeupMessage implements AlarmManager.OnAlarmListener {
this(context, handler, cmdName, cmd, 0, 0);
}
public void schedule(long when) {
sAlarmManager.setExact(
/**
* Schedule the message to be delivered at the time in milliseconds of the
* {@link android.os.SystemClock#elapsedRealtime SystemClock.elapsedRealtime()} clock and wakeup
* the device when it goes off. If schedule is called multiple times without the message being
* dispatched then the alarm is rescheduled to the new time.
*/
public synchronized void schedule(long when) {
mAlarmManager.setExact(
AlarmManager.ELAPSED_REALTIME_WAKEUP, when, mCmdName, this, mHandler);
mScheduled = true;
}
public void cancel() {
sAlarmManager.cancel(this);
/**
* Cancel all pending messages. This includes alarms that may have been fired, but have not been
* run on the handler yet.
*/
public synchronized void cancel() {
if (mScheduled) {
mAlarmManager.cancel(this);
mScheduled = false;
}
}
@Override
public void onAlarm() {
Message msg = mHandler.obtainMessage(mCmd, mArg1, mArg2);
mHandler.handleMessage(msg);
msg.recycle();
// Once this method is called the alarm has already been fired and removed from
// AlarmManager (it is still partially tracked, but only for statistics). The alarm can now
// be marked as unscheduled so that it can be rescheduled in the message handler.
final boolean stillScheduled;
synchronized (this) {
stillScheduled = mScheduled;
mScheduled = false;
}
if (stillScheduled) {
Message msg = mHandler.obtainMessage(mCmd, mArg1, mArg2);
mHandler.handleMessage(msg);
msg.recycle();
}
}
}

View File

@@ -12,7 +12,8 @@ LOCAL_MODULE_TAGS := tests
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_STATIC_JAVA_LIBRARIES := \
android-support-test
android-support-test \
mockito-target
LOCAL_JAVA_LIBRARIES := android.test.runner

View File

@@ -0,0 +1,169 @@
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.internal.util;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
import android.app.AlarmManager;
import android.content.Context;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.test.suitebuilder.annotation.SmallTest;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.Spy;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
/**
* Unit tests for {@link com.android.internal.util.WakeupMessage}.
*/
@SmallTest
public class WakeupMessageTest {
private static final String TEST_CMD_NAME = "TEST cmd Name";
private static final int TEST_CMD = 18;
private static final int TEST_ARG1 = 33;
private static final int TEST_ARG2 = 182;
@Mock AlarmManager mAlarmManager;
WakeupMessage mMessage;
// Make a spy so that we can verify calls to it
@Spy MessageCapturingHandler mHandler = new MessageCapturingHandler();
ArgumentCaptor<AlarmManager.OnAlarmListener> mListenerCaptor =
ArgumentCaptor.forClass(AlarmManager.OnAlarmListener.class);
/**
* A Handler that will capture the most recent message sent to it.
*
* This handler is setup on the main Looper
*/
public static class MessageCapturingHandler extends Handler {
private Message mLastMessage;
public MessageCapturingHandler() {
super(Looper.getMainLooper(), /* Nothing is actually dispatched on this Looper */
null, false);
}
@Override
public void handleMessage(Message m) {
// need to copy since it will be recycled after this method returns
mLastMessage = Message.obtain(m);
}
public Message getLastMessage() {
return mLastMessage;
}
}
/**
* Sets up the test.
*/
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
Context context = mock(Context.class);
when(context.getSystemService(Context.ALARM_SERVICE)).thenReturn(mAlarmManager);
// capture the listener for each AlarmManager.setExact call
doNothing().when(mAlarmManager).setExact(anyInt(), anyLong(), any(String.class),
mListenerCaptor.capture(), any(Handler.class));
mMessage = new WakeupMessage(context, mHandler, TEST_CMD_NAME, TEST_CMD, TEST_ARG1,
TEST_ARG2);
}
/**
* Ensure the test is cleaned up and ready for the next test.
*/
@After
public void cleanup() {
validateMockitoUsage();
}
private void scheduleAndVerifyAlarm(long when) {
mMessage.schedule(when);
verify(mAlarmManager).setExact(eq(AlarmManager.ELAPSED_REALTIME_WAKEUP), eq(when),
eq(TEST_CMD_NAME), any(AlarmManager.OnAlarmListener.class), eq(mHandler));
}
private void verifyMessageDispatchedOnce() {
verify(mHandler, times(1)).handleMessage(any(Message.class));
assertEquals("what", TEST_CMD, mHandler.getLastMessage().what);
assertEquals("arg1", TEST_ARG1, mHandler.getLastMessage().arg1);
assertEquals("arg2", TEST_ARG2, mHandler.getLastMessage().arg2);
}
/**
* Schedule and deliver a single message
*/
@Test
public void scheduleAndDeliverMessage() {
final long when = 1001;
scheduleAndVerifyAlarm(when);
verify(mHandler, never()).handleMessage(any(Message.class));
mListenerCaptor.getValue().onAlarm();
verifyMessageDispatchedOnce();
}
/**
* Check that the message is not delivered if cancel is called it after its alarm fires but
* before onAlarm is called.
*
* This ensures that if cancel is called on the handler thread, any previously-scheduled message
* is guaranteed not to be delivered.
*/
@Test
public void scheduleAndCancelMessage() {
final long when = 1010;
scheduleAndVerifyAlarm(when);
mMessage.cancel();
mListenerCaptor.getValue().onAlarm();
verify(mHandler, never()).handleMessage(any(Message.class));
}
/**
* Verify nothing happens when cancel is called without a schedule
*/
@Test
public void cancelWithoutSchedule() {
mMessage.cancel();
}
/**
* Verify that the message is silently rescheduled if schedule is called twice without the
* message being dispatched first.
*/
@Test
public void scheduleTwiceWithoutMessageDispatched() {
final long when1 = 1011;
final long when2 = 1012;
scheduleAndVerifyAlarm(when1);
scheduleAndVerifyAlarm(when2);
mListenerCaptor.getValue().onAlarm();
verifyMessageDispatchedOnce();
}
}