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

Commit eede1a69 authored by Svetoslav's avatar Svetoslav Committed by Android Git Automerger
Browse files

am aa7cc1e2: Merge "Adding idle maintenance service."

* commit 'aa7cc1e2':
  Adding idle maintenance service.
parents c4ca87f0 aa7cc1e2
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -5848,6 +5848,8 @@ package android.content {
    field public static final java.lang.String ACTION_GTALK_SERVICE_CONNECTED = "android.intent.action.GTALK_CONNECTED";
    field public static final java.lang.String ACTION_GTALK_SERVICE_DISCONNECTED = "android.intent.action.GTALK_DISCONNECTED";
    field public static final java.lang.String ACTION_HEADSET_PLUG = "android.intent.action.HEADSET_PLUG";
    field public static final java.lang.String ACTION_IDLE_MAINTENANCE_END = "android.intent.action.ACTION_IDLE_MAINTENANCE_END";
    field public static final java.lang.String ACTION_IDLE_MAINTENANCE_START = "android.intent.action.ACTION_IDLE_MAINTENANCE_START";
    field public static final java.lang.String ACTION_INPUT_METHOD_CHANGED = "android.intent.action.INPUT_METHOD_CHANGED";
    field public static final java.lang.String ACTION_INSERT = "android.intent.action.INSERT";
    field public static final java.lang.String ACTION_INSERT_OR_EDIT = "android.intent.action.INSERT_OR_EDIT";
+55 −0
Original line number Diff line number Diff line
@@ -2318,6 +2318,61 @@ public class Intent implements Parcelable, Cloneable {
    public static final String ACTION_DOCK_EVENT =
            "android.intent.action.DOCK_EVENT";

    /**
     * Broadcast Action: A broadcast when idle maintenance can be started.
     * This means that the user is not interacting with the device and is
     * not expected to do so soon. Typical use of the idle maintenance is
     * to perform somehow expensive tasks that can be postponed at a moment
     * when they will not degrade user experience.
     * <p>
     * <p class="note">In order to keep the device responsive in case of an
     * unexpected user interaction, implementations of a maintenance task
     * should be interruptible. In such a scenario a broadcast with action
     * {@link #ACTION_IDLE_MAINTENANCE_END} will be sent. In other words, you
     * should not do the maintenance work in
     * {@link BroadcastReceiver#onReceive(Context, Intent)}, rather start a
     * maintenance service by {@link Context#startService(Intent)}. Also
     * you should hold a wake lock while your maintenance service is running
     * to prevent the device going to sleep.
     * </p>
     * <p>
     * <p class="note">This is a protected intent that can only be sent by
     * the system.
     * </p>
     *
     * @see #ACTION_IDLE_MAINTENANCE_END
     */
    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
    public static final String ACTION_IDLE_MAINTENANCE_START =
            "android.intent.action.ACTION_IDLE_MAINTENANCE_START";

    /**
     * Broadcast Action:  A broadcast when idle maintenance should be stopped.
     * This means that the user was not interacting with the device as a result
     * of which a broadcast with action {@link #ACTION_IDLE_MAINTENANCE_START}
     * was sent and now the user started interacting with the device. Typical
     * use of the idle maintenance is to perform somehow expensive tasks that
     * can be postponed at a moment when they will not degrade user experience.
     * <p>
     * <p class="note">In order to keep the device responsive in case of an
     * unexpected user interaction, implementations of a maintenance task
     * should be interruptible. Hence, on receiving a broadcast with this
     * action, the maintenance task should be interrupted as soon as possible.
     * In other words, you should not do the maintenance work in
     * {@link BroadcastReceiver#onReceive(Context, Intent)}, rather stop the
     * maintenance service that was started on receiving of
     * {@link #ACTION_IDLE_MAINTENANCE_START}.Also you should release the wake
     * lock you acquired when your maintenance service started.
     * </p>
     * <p class="note">This is a protected intent that can only be sent
     * by the system.
     *
     * @see #ACTION_IDLE_MAINTENANCE_START
     */
    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
    public static final String ACTION_IDLE_MAINTENANCE_END =
            "android.intent.action.ACTION_IDLE_MAINTENANCE_END";

    /**
     * Broadcast Action: a remote intent is to be broadcasted.
     *
+3 −0
Original line number Diff line number Diff line
@@ -170,6 +170,9 @@
    <protected-broadcast android:name="android.intent.action.AIRPLANE_MODE" />
    <protected-broadcast android:name="android.intent.action.ADVANCED_SETTINGS" />

    <protected-broadcast android:name="android.intent.action.ACTION_IDLE_MAINTENANCE_START" />
    <protected-broadcast android:name="android.intent.action.ACTION_IDLE_MAINTENANCE_END" />

    <!-- ====================================== -->
    <!-- Permissions for things that cost money -->
    <!-- ====================================== -->
+202 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2013 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;

import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.BatteryManager;
import android.os.Handler;
import android.os.Looper;
import android.os.PowerManager;
import android.os.PowerManager.WakeLock;
import android.os.SystemClock;
import android.os.UserHandle;
import android.util.Log;

import java.util.Calendar;
import java.util.TimeZone;

/**
 * This service observes the device state and when applicable sends
 * broadcasts at the beginning and at the end of a period during which
 * observers can perform idle maintenance tasks. Typical use of the
 * idle maintenance is to perform somehow expensive tasks that can be
 * postponed to a moment when they will not degrade user experience.
 *
 * The current implementation is very simple. The start of a maintenance
 * window is announced if: the screen is off or showing a dream AND the
 * battery level is more than twenty percent AND at least one hour passed
 * since the screen went off or a dream started (i.e. since the last user
 * activity).
 *
 * The end of a maintenance window is announced only if: a start was
 * announced AND the screen turned on or a dream was stopped.
 */
public class IdleMaintenanceService extends BroadcastReceiver {

    private final boolean DEBUG = false;

    private static final String LOG_TAG = IdleMaintenanceService.class.getSimpleName();

    private static final int LAST_USER_ACTIVITY_TIME_INVALID = -1;

    private static final int MIN_IDLE_MAINTENANCE_START_BATTERY_LEVEL = 20; // percent

    private static final long MIN_IDLE_MAINTENANCE_START_USER_INACTIVITY = 60 * 60 * 1000; // 1 hour

    private final Intent mIdleMaintenanceStartIntent =
            new Intent(Intent.ACTION_IDLE_MAINTENANCE_START);

    private final Intent mIdleMaintenanceEndIntent =
            new Intent(Intent.ACTION_IDLE_MAINTENANCE_END);

    private final Context mContext;

    private final WakeLock mWakeLock;

    private final Handler mHandler;

    private final Calendar mTempCalendar = Calendar.getInstance();

    private final Calendar mLastIdleMaintenanceStartTime = Calendar.getInstance();

    private long mLastUserActivityElapsedTimeMillis = LAST_USER_ACTIVITY_TIME_INVALID;

    private int mBatteryLevel;

    private boolean mIdleMaintenanceStarted;

    public IdleMaintenanceService(Context context) {
        mContext = context;

        PowerManager powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
        mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, LOG_TAG);

        mHandler = new Handler(mContext.getMainLooper());

        // Move one day back so we can run maintenance the first day after starting.
        final int prevDayOfYear = mLastIdleMaintenanceStartTime.get(Calendar.DAY_OF_YEAR) - 1;
        mLastIdleMaintenanceStartTime.set(Calendar.DAY_OF_YEAR, prevDayOfYear);

        register(mContext.getMainLooper());
    }

    public void register(Looper looper) {
        IntentFilter intentFilter = new IntentFilter();

        // Battery actions.
        intentFilter.addAction(Intent.ACTION_BATTERY_CHANGED);

        // Screen actions.
        intentFilter.addAction(Intent.ACTION_SCREEN_ON);
        intentFilter.addAction(Intent.ACTION_SCREEN_OFF);

        // Dream actions.
        intentFilter.addAction(Intent.ACTION_DREAMING_STARTED);
        intentFilter.addAction(Intent.ACTION_DREAMING_STOPPED);

        mContext.registerReceiverAsUser(this, UserHandle.ALL,
                intentFilter, null, new Handler(looper));
    }

    private void updateIdleMaintenanceState() {
        if (mIdleMaintenanceStarted) {
            // Idle maintenance can be interrupted only by
            // a change of the device state.
            if (!deviceStatePermitsIdleMaintenance()) {
                mIdleMaintenanceStarted = false;
                sendIdleMaintenanceEndIntent();
            }
        } else if (deviceStatePermitsIdleMaintenance()
                && lastUserActivityPermitsIdleMaintenanceStart()
                && lastRunPermitsIdleMaintenanceStart()) {
            mIdleMaintenanceStarted = true;
            mLastIdleMaintenanceStartTime.setTimeInMillis(System.currentTimeMillis());
            sendIdleMaintenanceStartIntent();
        }
    }

    private void sendIdleMaintenanceStartIntent() {
        if (DEBUG) {
            Log.i(LOG_TAG, "Broadcasting " + Intent.ACTION_IDLE_MAINTENANCE_START);
        }
        mWakeLock.acquire();
        mContext.sendOrderedBroadcastAsUser(mIdleMaintenanceStartIntent, UserHandle.ALL,
                null, this, mHandler, Activity.RESULT_OK, null, null);
    }

    private void sendIdleMaintenanceEndIntent() {
        if (DEBUG) {
            Log.i(LOG_TAG, "Broadcasting " + Intent.ACTION_IDLE_MAINTENANCE_END);
        }
        mWakeLock.acquire();
        mContext.sendOrderedBroadcastAsUser(mIdleMaintenanceEndIntent, UserHandle.ALL,
                null, this, mHandler, Activity.RESULT_OK, null, null);
    }

    private boolean deviceStatePermitsIdleMaintenance() {
        return (mLastUserActivityElapsedTimeMillis != LAST_USER_ACTIVITY_TIME_INVALID
                && mBatteryLevel > MIN_IDLE_MAINTENANCE_START_BATTERY_LEVEL);
    }

    private boolean lastUserActivityPermitsIdleMaintenanceStart() {
        return (SystemClock.elapsedRealtime() - mLastUserActivityElapsedTimeMillis
                > MIN_IDLE_MAINTENANCE_START_USER_INACTIVITY);
    }

    private boolean lastRunPermitsIdleMaintenanceStart() {
        Calendar now = mTempCalendar;
        // Not setting the Locale since we do not care of locale
        // specific properties such as the first day of the week.
        now.setTimeZone(TimeZone.getDefault());
        now.setTimeInMillis(System.currentTimeMillis());

        Calendar lastRun = mLastIdleMaintenanceStartTime;
        // Not setting the Locale since we do not care of locale
        // specific properties such as the first day of the week.
        lastRun.setTimeZone(TimeZone.getDefault());

        return now.get(Calendar.DAY_OF_YEAR) != lastRun.get(Calendar.DAY_OF_YEAR);
    }

    @Override
    public void onReceive(Context context, Intent intent) {
        if (DEBUG) {
            Log.i(LOG_TAG, intent.getAction());
        }
        String action = intent.getAction();
        if (Intent.ACTION_BATTERY_CHANGED.equals(action)) {
            final int maxBatteryLevel = intent.getExtras().getInt(BatteryManager.EXTRA_SCALE);
            final int currBatteryLevel = intent.getExtras().getInt(BatteryManager.EXTRA_LEVEL);
            mBatteryLevel = (int) (((float) maxBatteryLevel / 100) * currBatteryLevel);
        } else if (Intent.ACTION_SCREEN_ON.equals(action)
                || Intent.ACTION_DREAMING_STOPPED.equals(action)) {
            mLastUserActivityElapsedTimeMillis = LAST_USER_ACTIVITY_TIME_INVALID;
        } else if (Intent.ACTION_SCREEN_OFF.equals(action)
                || Intent.ACTION_DREAMING_STARTED.equals(action)) {
            mLastUserActivityElapsedTimeMillis = SystemClock.elapsedRealtime();
        } else if (Intent.ACTION_IDLE_MAINTENANCE_START.equals(action)
                || Intent.ACTION_IDLE_MAINTENANCE_END.equals(action)) {
            mWakeLock.release();
            return;
        }
        updateIdleMaintenanceState();
    }
}
+8 −1
Original line number Diff line number Diff line
@@ -736,6 +736,13 @@ class ServerThread extends Thread {
                    reportWtf("starting DreamManagerService", e);
                }
            }

            try {
                Slog.i(TAG, "IdleMaintenanceService");
                new IdleMaintenanceService(context);
            } catch (Throwable e) {
                reportWtf("starting IdleMaintenanceService", e);
            }
        }

        // Before things start rolling, be sure we have decided whether