Loading services/core/java/com/android/server/policy/KeyCombinationManager.java 0 → 100644 +229 −0 Original line number Diff line number Diff line /* * Copyright (C) 2020 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.server.policy; import static android.view.KeyEvent.KEYCODE_POWER; import android.os.SystemClock; import android.util.SparseLongArray; import android.view.KeyEvent; import com.android.internal.util.ToBooleanFunction; import java.util.ArrayList; import java.util.function.Consumer; /** * Handles a mapping of two keys combination. */ public class KeyCombinationManager { private static final String TAG = "KeyCombinationManager"; // Store the received down time of keycode. private final SparseLongArray mDownTimes = new SparseLongArray(2); private final ArrayList<TwoKeysCombinationRule> mRules = new ArrayList(); // Selected rules according to current key down. private final ArrayList<TwoKeysCombinationRule> mActiveRules = new ArrayList(); // The rule has been triggered by current keys. private TwoKeysCombinationRule mTriggeredRule; // Keys in a key combination must be pressed within this interval of each other. private static final long COMBINE_KEY_DELAY_MILLIS = 150; /** * Rule definition for two keys combination. * E.g : define volume_down + power key. * <pre class="prettyprint"> * TwoKeysCombinationRule rule = * new TwoKeysCombinationRule(KEYCODE_VOLUME_DOWN, KEYCODE_POWER) { * boolean preCondition() { // check if it needs to intercept key } * void execute() { // trigger action } * void cancel() { // cancel action } * }; * </pre> */ abstract static class TwoKeysCombinationRule { private int mKeyCode1; private int mKeyCode2; TwoKeysCombinationRule(int keyCode1, int keyCode2) { mKeyCode1 = keyCode1; mKeyCode2 = keyCode2; } boolean preCondition() { return true; } boolean shouldInterceptKey(int keyCode) { return preCondition() && (keyCode == mKeyCode1 || keyCode == mKeyCode2); } boolean shouldInterceptKeys(SparseLongArray downTimes) { final long now = SystemClock.uptimeMillis(); if (downTimes.get(mKeyCode1) > 0 && downTimes.get(mKeyCode2) > 0 && now <= downTimes.get(mKeyCode1) + COMBINE_KEY_DELAY_MILLIS && now <= downTimes.get(mKeyCode2) + COMBINE_KEY_DELAY_MILLIS) { return true; } return false; } abstract void execute(); abstract void cancel(); @Override public String toString() { return "KeyCode1 = " + KeyEvent.keyCodeToString(mKeyCode1) + ", KeyCode2 = " + KeyEvent.keyCodeToString(mKeyCode2); } } public KeyCombinationManager() { } void addRule(TwoKeysCombinationRule rule) { mRules.add(rule); } /** * Check if the key event could be triggered by combine key rule before dispatching to a window. */ void interceptKey(KeyEvent event, boolean interactive) { final boolean down = event.getAction() == KeyEvent.ACTION_DOWN; final int keyCode = event.getKeyCode(); final int count = mActiveRules.size(); final long eventTime = event.getEventTime(); if (interactive && down) { if (mDownTimes.size() > 0) { if (count > 0 && eventTime > mDownTimes.valueAt(0) + COMBINE_KEY_DELAY_MILLIS) { // exceed time from first key down. forAllRules(mActiveRules, (rule)-> rule.cancel()); mActiveRules.clear(); return; } else if (count == 0) { // has some key down but no active rule exist. return; } } if (mDownTimes.get(keyCode) == 0) { mDownTimes.put(keyCode, eventTime); } else { // ignore old key, maybe a repeat key. return; } if (mDownTimes.size() == 1) { mTriggeredRule = null; // check first key and pick active rules. forAllRules(mRules, (rule)-> { if (rule.shouldInterceptKey(keyCode)) { mActiveRules.add(rule); } }); } else { // Ignore if rule already triggered. if (mTriggeredRule != null) { return; } // check if second key can trigger rule, or remove the non-match rule. forAllActiveRules((rule) -> { if (!rule.shouldInterceptKeys(mDownTimes)) { return false; } rule.execute(); mTriggeredRule = rule; return true; }); mActiveRules.clear(); if (mTriggeredRule != null) { mActiveRules.add(mTriggeredRule); } } } else { mDownTimes.delete(keyCode); for (int index = count - 1; index >= 0; index--) { final TwoKeysCombinationRule rule = mActiveRules.get(index); if (rule.shouldInterceptKey(keyCode)) { rule.cancel(); mActiveRules.remove(index); } } } } /** * Return the interceptTimeout to tell InputDispatcher when is ready to deliver to window. */ long getKeyInterceptTimeout(int keyCode) { if (forAllActiveRules((rule) -> rule.shouldInterceptKey(keyCode))) { return mDownTimes.get(keyCode) + COMBINE_KEY_DELAY_MILLIS; } return 0; } /** * True if the key event had been handled. */ boolean isKeyConsumed(KeyEvent event) { if ((event.getFlags() & KeyEvent.FLAG_FALLBACK) != 0) { return false; } return mTriggeredRule != null && mTriggeredRule.shouldInterceptKey(event.getKeyCode()); } /** * True if power key is the candidate. */ boolean isPowerKeyIntercepted() { if (forAllActiveRules((rule) -> rule.shouldInterceptKey(KEYCODE_POWER))) { // return false if only if power key pressed. return mDownTimes.size() > 1 || mDownTimes.get(KEYCODE_POWER) == 0; } return false; } /** * Traverse each item of rules. */ private void forAllRules( ArrayList<TwoKeysCombinationRule> rules, Consumer<TwoKeysCombinationRule> callback) { final int count = rules.size(); for (int index = 0; index < count; index++) { final TwoKeysCombinationRule rule = rules.get(index); callback.accept(rule); } } /** * Traverse each item of active rules until some rule can be applied, otherwise return false. */ private boolean forAllActiveRules(ToBooleanFunction<TwoKeysCombinationRule> callback) { final int count = mActiveRules.size(); for (int index = 0; index < count; index++) { final TwoKeysCombinationRule rule = mActiveRules.get(index); if (callback.apply(rule)) { return true; } } return false; } } services/core/java/com/android/server/policy/PhoneWindowManager.java +146 −259 File changed.Preview size limit exceeded, changes collapsed. Show changes services/tests/wmtests/src/com/android/server/policy/KeyCombinationTests.java 0 → 100644 +208 −0 Original line number Diff line number Diff line /* * Copyright (C) 2020 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.server.policy; import static android.view.KeyEvent.ACTION_DOWN; import static android.view.KeyEvent.ACTION_UP; import static android.view.KeyEvent.KEYCODE_BACK; import static android.view.KeyEvent.KEYCODE_POWER; import static android.view.KeyEvent.KEYCODE_VOLUME_DOWN; import static android.view.KeyEvent.KEYCODE_VOLUME_UP; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertTrue; import android.os.Handler; import android.os.Looper; import android.os.SystemClock; import android.view.KeyEvent; import androidx.test.filters.SmallTest; import org.junit.Before; import org.junit.Test; /** * Test class for {@link KeyCombinationManager}. * * Build/Install/Run: * atest KeyCombinationTests */ @SmallTest public class KeyCombinationTests { private KeyCombinationManager mKeyCombinationManager; private boolean mAction1Triggered = false; private boolean mAction2Triggered = false; private boolean mAction3Triggered = false; private boolean mPreCondition = true; private static final long SCHEDULE_TIME = 300; @Before public void setUp() { mKeyCombinationManager = new KeyCombinationManager(); initKeyCombinationRules(); } private void initKeyCombinationRules() { // Rule 1 : power + volume_down trigger action immediately. mKeyCombinationManager.addRule( new KeyCombinationManager.TwoKeysCombinationRule(KEYCODE_VOLUME_DOWN, KEYCODE_POWER) { @Override void execute() { mAction1Triggered = true; } @Override void cancel() { } }); // Rule 2 : volume_up + volume_down with condition. mKeyCombinationManager.addRule( new KeyCombinationManager.TwoKeysCombinationRule(KEYCODE_VOLUME_DOWN, KEYCODE_VOLUME_UP) { @Override boolean preCondition() { return mPreCondition; } @Override void execute() { mAction2Triggered = true; } @Override void cancel() { } }); // Rule 3 : power + volume_up schedule and trigger action after timeout. mKeyCombinationManager.addRule( new KeyCombinationManager.TwoKeysCombinationRule(KEYCODE_VOLUME_UP, KEYCODE_POWER) { final Runnable mAction = new Runnable() { @Override public void run() { mAction3Triggered = true; } }; final Handler mHandler = new Handler(Looper.getMainLooper()); @Override void execute() { mHandler.postDelayed(mAction, SCHEDULE_TIME); } @Override void cancel() { mHandler.removeCallbacks(mAction); } }); } private void pressKeys(long firstKeyTime, int firstKeyCode, long secondKeyTime, int secondKeyCode) { pressKeys(firstKeyTime, firstKeyCode, secondKeyTime, secondKeyCode, 0); } private void pressKeys(long firstKeyTime, int firstKeyCode, long secondKeyTime, int secondKeyCode, long pressTime) { final KeyEvent firstKeyDown = new KeyEvent(firstKeyTime, firstKeyTime, ACTION_DOWN, firstKeyCode, 0 /* repeat */, 0 /* metaState */); final KeyEvent secondKeyDown = new KeyEvent(secondKeyTime, secondKeyTime, ACTION_DOWN, secondKeyCode, 0 /* repeat */, 0 /* metaState */); mKeyCombinationManager.interceptKey(firstKeyDown, true); mKeyCombinationManager.interceptKey(secondKeyDown, true); // keep press down. try { Thread.sleep(pressTime); } catch (InterruptedException e) { e.printStackTrace(); } final KeyEvent firstKeyUp = new KeyEvent(firstKeyTime, firstKeyTime, ACTION_UP, firstKeyCode, 0 /* repeat */, 0 /* metaState */); final KeyEvent secondKeyUp = new KeyEvent(secondKeyTime, secondKeyTime, ACTION_UP, secondKeyCode, 0 /* repeat */, 0 /* metaState */); mKeyCombinationManager.interceptKey(firstKeyUp, true); mKeyCombinationManager.interceptKey(secondKeyUp, true); } @Test public void testTriggerRule() { final long eventTime = SystemClock.uptimeMillis(); pressKeys(eventTime, KEYCODE_POWER, eventTime, KEYCODE_VOLUME_DOWN); assertTrue(mAction1Triggered); pressKeys(eventTime, KEYCODE_VOLUME_UP, eventTime, KEYCODE_VOLUME_DOWN); assertTrue(mAction2Triggered); pressKeys(eventTime, KEYCODE_POWER, eventTime, KEYCODE_VOLUME_UP, SCHEDULE_TIME + 50); assertTrue(mAction3Triggered); } /** * Nothing should happen if there is no definition. */ @Test public void testNotTrigger_NoRule() { final long eventTime = SystemClock.uptimeMillis(); pressKeys(eventTime, KEYCODE_BACK, eventTime, KEYCODE_VOLUME_DOWN); assertFalse(mAction1Triggered); assertFalse(mAction2Triggered); assertFalse(mAction3Triggered); } /** * Nothing should happen if the interval of press time is too long. */ @Test public void testNotTrigger_Interval() { final long eventTime = SystemClock.uptimeMillis(); final long earlyEventTime = eventTime - 200; // COMBINE_KEY_DELAY_MILLIS = 150; pressKeys(earlyEventTime, KEYCODE_POWER, eventTime, KEYCODE_VOLUME_DOWN); assertFalse(mAction1Triggered); } /** * Nothing should happen if the condition is false. */ @Test public void testNotTrigger_Condition() { final long eventTime = SystemClock.uptimeMillis(); // we won't trigger action 2 because the condition is false. mPreCondition = false; pressKeys(eventTime, KEYCODE_VOLUME_UP, eventTime, KEYCODE_VOLUME_DOWN); assertFalse(mAction2Triggered); } /** * Nothing should happen if the keys released too early. */ @Test public void testNotTrigger_EarlyRelease() { final long eventTime = SystemClock.uptimeMillis(); pressKeys(eventTime, KEYCODE_POWER, eventTime, KEYCODE_VOLUME_UP); assertFalse(mAction3Triggered); } } Loading
services/core/java/com/android/server/policy/KeyCombinationManager.java 0 → 100644 +229 −0 Original line number Diff line number Diff line /* * Copyright (C) 2020 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.server.policy; import static android.view.KeyEvent.KEYCODE_POWER; import android.os.SystemClock; import android.util.SparseLongArray; import android.view.KeyEvent; import com.android.internal.util.ToBooleanFunction; import java.util.ArrayList; import java.util.function.Consumer; /** * Handles a mapping of two keys combination. */ public class KeyCombinationManager { private static final String TAG = "KeyCombinationManager"; // Store the received down time of keycode. private final SparseLongArray mDownTimes = new SparseLongArray(2); private final ArrayList<TwoKeysCombinationRule> mRules = new ArrayList(); // Selected rules according to current key down. private final ArrayList<TwoKeysCombinationRule> mActiveRules = new ArrayList(); // The rule has been triggered by current keys. private TwoKeysCombinationRule mTriggeredRule; // Keys in a key combination must be pressed within this interval of each other. private static final long COMBINE_KEY_DELAY_MILLIS = 150; /** * Rule definition for two keys combination. * E.g : define volume_down + power key. * <pre class="prettyprint"> * TwoKeysCombinationRule rule = * new TwoKeysCombinationRule(KEYCODE_VOLUME_DOWN, KEYCODE_POWER) { * boolean preCondition() { // check if it needs to intercept key } * void execute() { // trigger action } * void cancel() { // cancel action } * }; * </pre> */ abstract static class TwoKeysCombinationRule { private int mKeyCode1; private int mKeyCode2; TwoKeysCombinationRule(int keyCode1, int keyCode2) { mKeyCode1 = keyCode1; mKeyCode2 = keyCode2; } boolean preCondition() { return true; } boolean shouldInterceptKey(int keyCode) { return preCondition() && (keyCode == mKeyCode1 || keyCode == mKeyCode2); } boolean shouldInterceptKeys(SparseLongArray downTimes) { final long now = SystemClock.uptimeMillis(); if (downTimes.get(mKeyCode1) > 0 && downTimes.get(mKeyCode2) > 0 && now <= downTimes.get(mKeyCode1) + COMBINE_KEY_DELAY_MILLIS && now <= downTimes.get(mKeyCode2) + COMBINE_KEY_DELAY_MILLIS) { return true; } return false; } abstract void execute(); abstract void cancel(); @Override public String toString() { return "KeyCode1 = " + KeyEvent.keyCodeToString(mKeyCode1) + ", KeyCode2 = " + KeyEvent.keyCodeToString(mKeyCode2); } } public KeyCombinationManager() { } void addRule(TwoKeysCombinationRule rule) { mRules.add(rule); } /** * Check if the key event could be triggered by combine key rule before dispatching to a window. */ void interceptKey(KeyEvent event, boolean interactive) { final boolean down = event.getAction() == KeyEvent.ACTION_DOWN; final int keyCode = event.getKeyCode(); final int count = mActiveRules.size(); final long eventTime = event.getEventTime(); if (interactive && down) { if (mDownTimes.size() > 0) { if (count > 0 && eventTime > mDownTimes.valueAt(0) + COMBINE_KEY_DELAY_MILLIS) { // exceed time from first key down. forAllRules(mActiveRules, (rule)-> rule.cancel()); mActiveRules.clear(); return; } else if (count == 0) { // has some key down but no active rule exist. return; } } if (mDownTimes.get(keyCode) == 0) { mDownTimes.put(keyCode, eventTime); } else { // ignore old key, maybe a repeat key. return; } if (mDownTimes.size() == 1) { mTriggeredRule = null; // check first key and pick active rules. forAllRules(mRules, (rule)-> { if (rule.shouldInterceptKey(keyCode)) { mActiveRules.add(rule); } }); } else { // Ignore if rule already triggered. if (mTriggeredRule != null) { return; } // check if second key can trigger rule, or remove the non-match rule. forAllActiveRules((rule) -> { if (!rule.shouldInterceptKeys(mDownTimes)) { return false; } rule.execute(); mTriggeredRule = rule; return true; }); mActiveRules.clear(); if (mTriggeredRule != null) { mActiveRules.add(mTriggeredRule); } } } else { mDownTimes.delete(keyCode); for (int index = count - 1; index >= 0; index--) { final TwoKeysCombinationRule rule = mActiveRules.get(index); if (rule.shouldInterceptKey(keyCode)) { rule.cancel(); mActiveRules.remove(index); } } } } /** * Return the interceptTimeout to tell InputDispatcher when is ready to deliver to window. */ long getKeyInterceptTimeout(int keyCode) { if (forAllActiveRules((rule) -> rule.shouldInterceptKey(keyCode))) { return mDownTimes.get(keyCode) + COMBINE_KEY_DELAY_MILLIS; } return 0; } /** * True if the key event had been handled. */ boolean isKeyConsumed(KeyEvent event) { if ((event.getFlags() & KeyEvent.FLAG_FALLBACK) != 0) { return false; } return mTriggeredRule != null && mTriggeredRule.shouldInterceptKey(event.getKeyCode()); } /** * True if power key is the candidate. */ boolean isPowerKeyIntercepted() { if (forAllActiveRules((rule) -> rule.shouldInterceptKey(KEYCODE_POWER))) { // return false if only if power key pressed. return mDownTimes.size() > 1 || mDownTimes.get(KEYCODE_POWER) == 0; } return false; } /** * Traverse each item of rules. */ private void forAllRules( ArrayList<TwoKeysCombinationRule> rules, Consumer<TwoKeysCombinationRule> callback) { final int count = rules.size(); for (int index = 0; index < count; index++) { final TwoKeysCombinationRule rule = rules.get(index); callback.accept(rule); } } /** * Traverse each item of active rules until some rule can be applied, otherwise return false. */ private boolean forAllActiveRules(ToBooleanFunction<TwoKeysCombinationRule> callback) { final int count = mActiveRules.size(); for (int index = 0; index < count; index++) { final TwoKeysCombinationRule rule = mActiveRules.get(index); if (callback.apply(rule)) { return true; } } return false; } }
services/core/java/com/android/server/policy/PhoneWindowManager.java +146 −259 File changed.Preview size limit exceeded, changes collapsed. Show changes
services/tests/wmtests/src/com/android/server/policy/KeyCombinationTests.java 0 → 100644 +208 −0 Original line number Diff line number Diff line /* * Copyright (C) 2020 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.server.policy; import static android.view.KeyEvent.ACTION_DOWN; import static android.view.KeyEvent.ACTION_UP; import static android.view.KeyEvent.KEYCODE_BACK; import static android.view.KeyEvent.KEYCODE_POWER; import static android.view.KeyEvent.KEYCODE_VOLUME_DOWN; import static android.view.KeyEvent.KEYCODE_VOLUME_UP; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertTrue; import android.os.Handler; import android.os.Looper; import android.os.SystemClock; import android.view.KeyEvent; import androidx.test.filters.SmallTest; import org.junit.Before; import org.junit.Test; /** * Test class for {@link KeyCombinationManager}. * * Build/Install/Run: * atest KeyCombinationTests */ @SmallTest public class KeyCombinationTests { private KeyCombinationManager mKeyCombinationManager; private boolean mAction1Triggered = false; private boolean mAction2Triggered = false; private boolean mAction3Triggered = false; private boolean mPreCondition = true; private static final long SCHEDULE_TIME = 300; @Before public void setUp() { mKeyCombinationManager = new KeyCombinationManager(); initKeyCombinationRules(); } private void initKeyCombinationRules() { // Rule 1 : power + volume_down trigger action immediately. mKeyCombinationManager.addRule( new KeyCombinationManager.TwoKeysCombinationRule(KEYCODE_VOLUME_DOWN, KEYCODE_POWER) { @Override void execute() { mAction1Triggered = true; } @Override void cancel() { } }); // Rule 2 : volume_up + volume_down with condition. mKeyCombinationManager.addRule( new KeyCombinationManager.TwoKeysCombinationRule(KEYCODE_VOLUME_DOWN, KEYCODE_VOLUME_UP) { @Override boolean preCondition() { return mPreCondition; } @Override void execute() { mAction2Triggered = true; } @Override void cancel() { } }); // Rule 3 : power + volume_up schedule and trigger action after timeout. mKeyCombinationManager.addRule( new KeyCombinationManager.TwoKeysCombinationRule(KEYCODE_VOLUME_UP, KEYCODE_POWER) { final Runnable mAction = new Runnable() { @Override public void run() { mAction3Triggered = true; } }; final Handler mHandler = new Handler(Looper.getMainLooper()); @Override void execute() { mHandler.postDelayed(mAction, SCHEDULE_TIME); } @Override void cancel() { mHandler.removeCallbacks(mAction); } }); } private void pressKeys(long firstKeyTime, int firstKeyCode, long secondKeyTime, int secondKeyCode) { pressKeys(firstKeyTime, firstKeyCode, secondKeyTime, secondKeyCode, 0); } private void pressKeys(long firstKeyTime, int firstKeyCode, long secondKeyTime, int secondKeyCode, long pressTime) { final KeyEvent firstKeyDown = new KeyEvent(firstKeyTime, firstKeyTime, ACTION_DOWN, firstKeyCode, 0 /* repeat */, 0 /* metaState */); final KeyEvent secondKeyDown = new KeyEvent(secondKeyTime, secondKeyTime, ACTION_DOWN, secondKeyCode, 0 /* repeat */, 0 /* metaState */); mKeyCombinationManager.interceptKey(firstKeyDown, true); mKeyCombinationManager.interceptKey(secondKeyDown, true); // keep press down. try { Thread.sleep(pressTime); } catch (InterruptedException e) { e.printStackTrace(); } final KeyEvent firstKeyUp = new KeyEvent(firstKeyTime, firstKeyTime, ACTION_UP, firstKeyCode, 0 /* repeat */, 0 /* metaState */); final KeyEvent secondKeyUp = new KeyEvent(secondKeyTime, secondKeyTime, ACTION_UP, secondKeyCode, 0 /* repeat */, 0 /* metaState */); mKeyCombinationManager.interceptKey(firstKeyUp, true); mKeyCombinationManager.interceptKey(secondKeyUp, true); } @Test public void testTriggerRule() { final long eventTime = SystemClock.uptimeMillis(); pressKeys(eventTime, KEYCODE_POWER, eventTime, KEYCODE_VOLUME_DOWN); assertTrue(mAction1Triggered); pressKeys(eventTime, KEYCODE_VOLUME_UP, eventTime, KEYCODE_VOLUME_DOWN); assertTrue(mAction2Triggered); pressKeys(eventTime, KEYCODE_POWER, eventTime, KEYCODE_VOLUME_UP, SCHEDULE_TIME + 50); assertTrue(mAction3Triggered); } /** * Nothing should happen if there is no definition. */ @Test public void testNotTrigger_NoRule() { final long eventTime = SystemClock.uptimeMillis(); pressKeys(eventTime, KEYCODE_BACK, eventTime, KEYCODE_VOLUME_DOWN); assertFalse(mAction1Triggered); assertFalse(mAction2Triggered); assertFalse(mAction3Triggered); } /** * Nothing should happen if the interval of press time is too long. */ @Test public void testNotTrigger_Interval() { final long eventTime = SystemClock.uptimeMillis(); final long earlyEventTime = eventTime - 200; // COMBINE_KEY_DELAY_MILLIS = 150; pressKeys(earlyEventTime, KEYCODE_POWER, eventTime, KEYCODE_VOLUME_DOWN); assertFalse(mAction1Triggered); } /** * Nothing should happen if the condition is false. */ @Test public void testNotTrigger_Condition() { final long eventTime = SystemClock.uptimeMillis(); // we won't trigger action 2 because the condition is false. mPreCondition = false; pressKeys(eventTime, KEYCODE_VOLUME_UP, eventTime, KEYCODE_VOLUME_DOWN); assertFalse(mAction2Triggered); } /** * Nothing should happen if the keys released too early. */ @Test public void testNotTrigger_EarlyRelease() { final long eventTime = SystemClock.uptimeMillis(); pressKeys(eventTime, KEYCODE_POWER, eventTime, KEYCODE_VOLUME_UP); assertFalse(mAction3Triggered); } }