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

Commit ebc106cb authored by Mitchell Wills's avatar Mitchell Wills Committed by android-build-merger
Browse files

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

am: 9505e0ac

* commit '9505e0ac':
  Modify WakeupMessage to only send the message if not canceled
parents 642a4f5b 9505e0ac
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();
    }

}