Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 9505e0ac authored by Mitchell Wills's avatar Mitchell Wills Committed by Android Partner Code Review
Browse files

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

parents d76b7ee2 634c1f9d
Loading
Loading
Loading
Loading
+35 −13
Original line number Diff line number Diff line
@@ -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() {
        // 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();
        }
    }
}
+2 −1
Original line number Diff line number Diff line
@@ -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

+169 −0
Original line number Diff line number Diff line
/*
 * 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();
    }

}