// Copyright 2016 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. package org.chromium.chrome.browser; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.os.Handler; import android.os.Looper; import org.chromium.base.ApiCompatibilityUtils; import org.chromium.base.ApplicationStatus; import org.chromium.base.ContextUtils; import org.chromium.base.ThreadUtils; import org.chromium.base.VisibleForTesting; import org.chromium.chrome.browser.invalidation.DelayedInvalidationsController; import org.chromium.chrome.browser.omaha.OmahaClient; import java.util.concurrent.atomic.AtomicBoolean; /** * Monitors the event that indicates the screen is turning on. Used to run actions that shouldn't * occur while the phone's screen is off (i.e. when the user expects the phone to be "asleep"). * * When conditions are right, code in {@link PowerBroadcastReceiver.ServiceRunnable#runActions()} * is executed. */ public class PowerBroadcastReceiver extends BroadcastReceiver { private final AtomicBoolean mIsRegistered = new AtomicBoolean(false); private PowerManagerHelper mPowerManagerHelper; private ServiceRunnable mServiceRunnable; /** * Stubs out interaction with the PowerManager. */ @VisibleForTesting static class PowerManagerHelper { /** @return whether the screen is on or not. */ public boolean isScreenOn(Context context) { return ApiCompatibilityUtils.isInteractive(context); } } /** * Defines a set of actions to perform when the conditions are met. */ @VisibleForTesting public static class ServiceRunnable implements Runnable { static final int STATE_UNINITIALIZED = 0; static final int STATE_POSTED = 1; static final int STATE_CANCELED = 2; static final int STATE_COMPLETED = 3; /** * ANRs are triggered if the app fails to respond to a touch event within 5 seconds. Posting * this runnable after 5 seconds lets ChromeTabbedActivity.onResume() perform whatever more * important tasks are necessary: http://b/5864891 */ private static final long MS_DELAY_TO_RUN = 5000; private final Handler mHandler = new Handler(Looper.getMainLooper()); private int mState = STATE_UNINITIALIZED; public int getState() { return mState; } public void post() { if (mState == STATE_POSTED) return; setState(STATE_POSTED); mHandler.postDelayed(this, getDelayToRun()); } public void cancel() { if (mState != STATE_POSTED) return; setState(STATE_CANCELED); mHandler.removeCallbacks(this); } /** Unless testing, do not override this function. */ @Override public void run() { if (mState != STATE_POSTED) return; setState(STATE_COMPLETED); runActions(); } public void setState(int state) { mState = state; } /** * Executed when all of the system conditions are met. */ public void runActions() { Context context = ContextUtils.getApplicationContext(); OmahaClient.onForegroundSessionStart(context); DelayedInvalidationsController.getInstance().notifyPendingInvalidations(context); } public long getDelayToRun() { return MS_DELAY_TO_RUN; } } public PowerBroadcastReceiver() { mServiceRunnable = new ServiceRunnable(); mPowerManagerHelper = new PowerManagerHelper(); } /** See {@link ChromeApplication#onForegroundSessionStart()}. */ public void onForegroundSessionStart() { ThreadUtils.assertOnUiThread(); assert Looper.getMainLooper() == Looper.myLooper(); if (mPowerManagerHelper.isScreenOn(ContextUtils.getApplicationContext())) { mServiceRunnable.post(); } else { registerReceiver(); } } /** See {@link ChromeApplication#onForegroundSessionEnd()}. */ public void onForegroundSessionEnd() { assert Looper.getMainLooper() == Looper.myLooper(); mServiceRunnable.cancel(); unregisterReceiver(); } @Override public void onReceive(Context context, Intent intent) { if (Intent.ACTION_SCREEN_ON.equals(intent.getAction()) && ApplicationStatus.hasVisibleActivities()) { mServiceRunnable.post(); unregisterReceiver(); } } /** * @return Whether or not this is registered with a context. */ @VisibleForTesting public boolean isRegistered() { return mIsRegistered.get(); } /** * Unregisters this broadcast receiver so it no longer receives Intents. * Also cancels any Runnables waiting to be executed. */ private void unregisterReceiver() { if (mIsRegistered.getAndSet(false)) { ContextUtils.getApplicationContext().unregisterReceiver(this); } } /** * Registers this broadcast receiver so it receives Intents. */ private void registerReceiver() { assert Looper.getMainLooper() == Looper.myLooper(); if (mIsRegistered.getAndSet(true)) return; ContextUtils.getApplicationContext().registerReceiver( this, new IntentFilter(Intent.ACTION_SCREEN_ON)); } /** * Sets the runnable that contains the actions to do when the screen is on. */ @VisibleForTesting void setServiceRunnableForTests(ServiceRunnable runnable) { assert mServiceRunnable != null; mServiceRunnable.cancel(); mServiceRunnable = runnable; } /** * Sets the PowerManagerHelper that will be used to check if the screen is on. */ @VisibleForTesting void setPowerManagerHelperForTests(PowerManagerHelper helper) { mPowerManagerHelper = helper; } }