Merge "Introduce "IdleService" API to expose idle-time maintenance to apps"
This commit is contained in:
committed by
Android (Google) Code Review
commit
31b4834b6d
@ -89,6 +89,8 @@ LOCAL_SRC_FILES += \
|
||||
core/java/android/app/backup/IFullBackupRestoreObserver.aidl \
|
||||
core/java/android/app/backup/IRestoreObserver.aidl \
|
||||
core/java/android/app/backup/IRestoreSession.aidl \
|
||||
core/java/android/app/maintenance/IIdleCallback.aidl \
|
||||
core/java/android/app/maintenance/IIdleService.aidl \
|
||||
core/java/android/bluetooth/IBluetooth.aidl \
|
||||
core/java/android/bluetooth/IBluetoothA2dp.aidl \
|
||||
core/java/android/bluetooth/IBluetoothCallback.aidl \
|
||||
|
@ -4886,6 +4886,20 @@ package android.app.backup {
|
||||
|
||||
}
|
||||
|
||||
package android.app.maintenance {
|
||||
|
||||
public abstract class IdleService extends android.app.Service {
|
||||
ctor public IdleService();
|
||||
method public final void finishIdle();
|
||||
method public final android.os.IBinder onBind(android.content.Intent);
|
||||
method public abstract boolean onIdleStart();
|
||||
method public abstract void onIdleStop();
|
||||
field public static final java.lang.String PERMISSION_BIND = "android.permission.BIND_IDLE_SERVICE";
|
||||
field public static final java.lang.String SERVICE_INTERFACE = "android.service.idle.IdleService";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
package android.appwidget {
|
||||
|
||||
public class AppWidgetHost {
|
||||
|
53
core/java/android/app/maintenance/IIdleCallback.aidl
Normal file
53
core/java/android/app/maintenance/IIdleCallback.aidl
Normal file
@ -0,0 +1,53 @@
|
||||
/**
|
||||
* Copyright 2014, 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 android.app.maintenance;
|
||||
|
||||
import android.app.maintenance.IIdleService;
|
||||
|
||||
/**
|
||||
* The server side of the idle maintenance IPC protocols. The app-side implementation
|
||||
* invokes on this interface to indicate completion of the (asynchronous) instructions
|
||||
* issued by the server.
|
||||
*
|
||||
* In all cases, the 'who' parameter is the caller's service binder, used to track
|
||||
* which idle service instance is reporting.
|
||||
*
|
||||
* {@hide}
|
||||
*/
|
||||
interface IIdleCallback {
|
||||
/**
|
||||
* Acknowledge receipt and processing of the asynchronous "start idle work" incall.
|
||||
* 'result' is true if the app wants some time to perform ongoing background
|
||||
* idle-time work; or false if the app declares that it does not need any time
|
||||
* for such work.
|
||||
*/
|
||||
void acknowledgeStart(int token, boolean result);
|
||||
|
||||
/**
|
||||
* Acknowledge receipt and processing of the asynchronous "stop idle work" incall.
|
||||
*/
|
||||
void acknowledgeStop(int token);
|
||||
|
||||
/*
|
||||
* Tell the idle service manager that we're done with our idle maintenance, so that
|
||||
* it can go on to the next one and stop attributing wakelock time to us etc.
|
||||
*
|
||||
* @param opToken The identifier passed in the startIdleMaintenance() call that
|
||||
* indicated the beginning of this service's idle timeslice.
|
||||
*/
|
||||
void idleFinished(int token);
|
||||
}
|
34
core/java/android/app/maintenance/IIdleService.aidl
Normal file
34
core/java/android/app/maintenance/IIdleService.aidl
Normal file
@ -0,0 +1,34 @@
|
||||
/**
|
||||
* Copyright 2014, 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 android.app.maintenance;
|
||||
|
||||
import android.app.maintenance.IIdleCallback;
|
||||
|
||||
/**
|
||||
* Interface that the framework uses to communicate with application code
|
||||
* that implements an idle-time "maintenance" service. End user code does
|
||||
* not implement this interface directly; instead, the app's idle service
|
||||
* implementation will extend android.app.maintenance.IdleService.
|
||||
* {@hide}
|
||||
*/
|
||||
oneway interface IIdleService {
|
||||
/**
|
||||
* Begin your idle-time work.
|
||||
*/
|
||||
void startIdleMaintenance(IIdleCallback callbackBinder, int token);
|
||||
void stopIdleMaintenance(IIdleCallback callbackBinder, int token);
|
||||
}
|
228
core/java/android/app/maintenance/IdleService.java
Normal file
228
core/java/android/app/maintenance/IdleService.java
Normal file
@ -0,0 +1,228 @@
|
||||
/*
|
||||
* Copyright (C) 2014 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 android.app.maintenance;
|
||||
|
||||
import android.annotation.SdkConstant;
|
||||
import android.annotation.SdkConstant.SdkConstantType;
|
||||
import android.app.Service;
|
||||
import android.content.Intent;
|
||||
import android.os.Handler;
|
||||
import android.os.IBinder;
|
||||
import android.os.Looper;
|
||||
import android.os.Message;
|
||||
import android.os.RemoteException;
|
||||
import android.util.Log;
|
||||
import android.util.Slog;
|
||||
|
||||
/**
|
||||
* Idle maintenance API. Full docs TBW (to be written).
|
||||
*/
|
||||
public abstract class IdleService extends Service {
|
||||
private static final String TAG = "IdleService";
|
||||
|
||||
static final int MSG_START = 1;
|
||||
static final int MSG_STOP = 2;
|
||||
static final int MSG_FINISH = 3;
|
||||
|
||||
IdleHandler mHandler;
|
||||
IIdleCallback mCallbackBinder;
|
||||
int mToken;
|
||||
final Object mHandlerLock = new Object();
|
||||
|
||||
void ensureHandler() {
|
||||
synchronized (mHandlerLock) {
|
||||
if (mHandler == null) {
|
||||
mHandler = new IdleHandler(getMainLooper());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TBW: the idle service should supply an intent-filter handling this intent
|
||||
* <p>
|
||||
* <p class="note">The application must also protect the idle service with the
|
||||
* {@code "android.permission.BIND_IDLE_SERVICE"} permission to ensure that other
|
||||
* applications cannot maliciously bind to it. If an idle service's manifest
|
||||
* declaration does not require that permission, it will never be invoked.
|
||||
* </p>
|
||||
*/
|
||||
@SdkConstant(SdkConstantType.SERVICE_ACTION)
|
||||
public static final String SERVICE_INTERFACE =
|
||||
"android.service.idle.IdleService";
|
||||
|
||||
/**
|
||||
* Idle services must be protected with this permission:
|
||||
*
|
||||
* <pre class="prettyprint">
|
||||
* <service android:name="MyIdleService"
|
||||
* android:permission="android.permission.BIND_IDLE_SERVICE" >
|
||||
* ...
|
||||
* </service>
|
||||
* </pre>
|
||||
*
|
||||
* <p>If an idle service is declared in the manifest but not protected with this
|
||||
* permission, that service will be ignored by the OS.
|
||||
*/
|
||||
public static final String PERMISSION_BIND =
|
||||
"android.permission.BIND_IDLE_SERVICE";
|
||||
|
||||
// Trampoline: the callbacks are always run on the main thread
|
||||
IIdleService mBinder = new IIdleService.Stub() {
|
||||
@Override
|
||||
public void startIdleMaintenance(IIdleCallback callbackBinder, int token)
|
||||
throws RemoteException {
|
||||
ensureHandler();
|
||||
Message msg = mHandler.obtainMessage(MSG_START, token, 0, callbackBinder);
|
||||
mHandler.sendMessage(msg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stopIdleMaintenance(IIdleCallback callbackBinder, int token)
|
||||
throws RemoteException {
|
||||
ensureHandler();
|
||||
Message msg = mHandler.obtainMessage(MSG_STOP, token, 0, callbackBinder);
|
||||
mHandler.sendMessage(msg);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Your application may begin doing "idle" maintenance work in the background.
|
||||
* <p>
|
||||
* Your application may continue to run in the background until it receives a call
|
||||
* to {@link #onIdleStop()}, at which point you <i>must</i> cease doing work. The
|
||||
* OS will hold a wakelock on your application's behalf from the time this method is
|
||||
* called until after the following call to {@link #onIdleStop()} returns.
|
||||
* </p>
|
||||
* <p>
|
||||
* Returning {@code false} from this method indicates that you have no ongoing work
|
||||
* to do at present. The OS will respond by immediately calling {@link #onIdleStop()}
|
||||
* and returning your application to its normal stopped state. Returning {@code true}
|
||||
* indicates that the application is indeed performing ongoing work, so the OS will
|
||||
* let your application run in this state until it's no longer appropriate.
|
||||
* </p>
|
||||
* <p>
|
||||
* You will always receive a matching call to {@link #onIdleStop()} even if your
|
||||
* application returns {@code false} from this method.
|
||||
*
|
||||
* @return {@code true} to indicate that the application wishes to perform some ongoing
|
||||
* background work; {@code false} to indicate that it does not need to perform such
|
||||
* work at present.
|
||||
*/
|
||||
public abstract boolean onIdleStart();
|
||||
|
||||
/**
|
||||
* Your app's maintenance opportunity is over. Once the application returns from
|
||||
* this method, the wakelock held by the OS on its behalf will be released.
|
||||
*/
|
||||
public abstract void onIdleStop();
|
||||
|
||||
/**
|
||||
* Tell the OS that you have finished your idle work. Calling this more than once,
|
||||
* or calling it when you have not received an {@link #onIdleStart()} callback, is
|
||||
* an error.
|
||||
*
|
||||
* <p>It is safe to call {@link #finishIdle()} from any thread.
|
||||
*/
|
||||
public final void finishIdle() {
|
||||
ensureHandler();
|
||||
mHandler.sendEmptyMessage(MSG_FINISH);
|
||||
}
|
||||
|
||||
class IdleHandler extends Handler {
|
||||
IdleHandler(Looper looper) {
|
||||
super(looper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleMessage(Message msg) {
|
||||
switch (msg.what) {
|
||||
case MSG_START: {
|
||||
// Call the concrete onIdleStart(), reporting its return value back to
|
||||
// the OS. If onIdleStart() throws, report it as a 'false' return but
|
||||
// rethrow the exception at the offending app.
|
||||
boolean result = false;
|
||||
IIdleCallback callbackBinder = (IIdleCallback) msg.obj;
|
||||
mCallbackBinder = callbackBinder;
|
||||
final int token = mToken = msg.arg1;
|
||||
try {
|
||||
result = IdleService.this.onIdleStart();
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Unable to start idle workload", e);
|
||||
throw new RuntimeException(e);
|
||||
} finally {
|
||||
// don't bother if the service already called finishIdle()
|
||||
if (mCallbackBinder != null) {
|
||||
try {
|
||||
callbackBinder.acknowledgeStart(token, result);
|
||||
} catch (RemoteException re) {
|
||||
Log.e(TAG, "System unreachable to start idle workload");
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case MSG_STOP: {
|
||||
// Structured just like MSG_START for the stop-idle bookend call.
|
||||
IIdleCallback callbackBinder = (IIdleCallback) msg.obj;
|
||||
final int token = msg.arg1;
|
||||
try {
|
||||
IdleService.this.onIdleStop();
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Unable to stop idle workload", e);
|
||||
throw new RuntimeException(e);
|
||||
} finally {
|
||||
if (mCallbackBinder != null) {
|
||||
try {
|
||||
callbackBinder.acknowledgeStop(token);
|
||||
} catch (RemoteException re) {
|
||||
Log.e(TAG, "System unreachable to stop idle workload");
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case MSG_FINISH: {
|
||||
if (mCallbackBinder != null) {
|
||||
try {
|
||||
mCallbackBinder.idleFinished(mToken);
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "System unreachable to finish idling");
|
||||
} finally {
|
||||
mCallbackBinder = null;
|
||||
}
|
||||
} else {
|
||||
Log.e(TAG, "finishIdle() called but the idle service is not started");
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
Slog.w(TAG, "Unknown message " + msg.what);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
@Override
|
||||
public final IBinder onBind(Intent intent) {
|
||||
return mBinder.asBinder();
|
||||
}
|
||||
|
||||
}
|
@ -1727,6 +1727,13 @@
|
||||
android:label="@string/permlab_recovery"
|
||||
android:description="@string/permdesc_recovery" />
|
||||
|
||||
<!-- Allows the system to bind to an application's idle services
|
||||
@hide -->
|
||||
<permission android:name="android.permission.BIND_IDLE_SERVICE"
|
||||
android:protectionLevel="signature"
|
||||
android:label="@string/permlab_bindIdleService"
|
||||
android:description="@string/permdesc_bindIdleService" />
|
||||
|
||||
<!-- ========================================= -->
|
||||
<!-- Permissions for special development tools -->
|
||||
<!-- ========================================= -->
|
||||
@ -2728,6 +2735,14 @@
|
||||
</intent-filter>
|
||||
</service>
|
||||
|
||||
<service android:name="com.android.server.MountServiceIdler"
|
||||
android:exported="false"
|
||||
android:permission="android.permission.BIND_IDLE_SERVICE" >
|
||||
<intent-filter>
|
||||
<action android:name="android.service.idle.IdleService" />
|
||||
</intent-filter>
|
||||
</service>
|
||||
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
|
@ -1175,6 +1175,11 @@
|
||||
<!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. [CHAR LIMIT=NONE]-->
|
||||
<string name="permdesc_manageCaCertificates">Allows the app to install and uninstall CA certificates as trusted credentials.</string>
|
||||
|
||||
<!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
|
||||
<string name="permlab_bindIdleService">bind to idle services</string>
|
||||
<!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
|
||||
<string name="permdesc_bindIdleService">Allows the app to interact with application-defined idle services.</string>
|
||||
|
||||
<!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
|
||||
<string name="permlab_diagnostic">read/write to resources owned by diag</string>
|
||||
<!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
|
||||
|
@ -16,22 +16,38 @@
|
||||
|
||||
package com.android.server;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.ActivityManagerNative;
|
||||
import android.app.AlarmManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.app.maintenance.IIdleCallback;
|
||||
import android.app.maintenance.IIdleService;
|
||||
import android.app.maintenance.IdleService;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.ServiceConnection;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import android.os.Handler;
|
||||
import android.os.PowerManager;
|
||||
import android.os.PowerManager.WakeLock;
|
||||
import android.os.IBinder;
|
||||
import android.os.Looper;
|
||||
import android.os.Message;
|
||||
import android.os.Process;
|
||||
import android.os.RemoteException;
|
||||
import android.os.SystemClock;
|
||||
import android.os.UserHandle;
|
||||
import android.os.WorkSource;
|
||||
import android.util.ArrayMap;
|
||||
import android.util.Log;
|
||||
import android.util.Slog;
|
||||
import android.util.SparseArray;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
/**
|
||||
* This service observes the device state and when applicable sends
|
||||
@ -47,12 +63,15 @@ import android.util.Slog;
|
||||
*
|
||||
* The end of a maintenance window is announced only if: a start was
|
||||
* announced AND the screen turned on or a dream was stopped.
|
||||
*
|
||||
* Method naming note:
|
||||
* Methods whose name ends with "Tm" must only be called from the main thread.
|
||||
*/
|
||||
public class IdleMaintenanceService extends BroadcastReceiver {
|
||||
|
||||
private static final boolean DEBUG = false;
|
||||
|
||||
private static final String LOG_TAG = IdleMaintenanceService.class.getSimpleName();
|
||||
private static final String TAG = IdleMaintenanceService.class.getSimpleName();
|
||||
|
||||
private static final int LAST_USER_ACTIVITY_TIME_INVALID = -1;
|
||||
|
||||
@ -74,36 +93,480 @@ public class IdleMaintenanceService extends BroadcastReceiver {
|
||||
private static final String ACTION_FORCE_IDLE_MAINTENANCE =
|
||||
"com.android.server.IdleMaintenanceService.action.FORCE_IDLE_MAINTENANCE";
|
||||
|
||||
private static final Intent sIdleMaintenanceStartIntent;
|
||||
static {
|
||||
sIdleMaintenanceStartIntent = new Intent(Intent.ACTION_IDLE_MAINTENANCE_START);
|
||||
sIdleMaintenanceStartIntent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
|
||||
};
|
||||
static final int MSG_OP_COMPLETE = 1;
|
||||
static final int MSG_IDLE_FINISHED = 2;
|
||||
static final int MSG_TIMEOUT = 3;
|
||||
|
||||
private static final Intent sIdleMaintenanceEndIntent;
|
||||
static {
|
||||
sIdleMaintenanceEndIntent = new Intent(Intent.ACTION_IDLE_MAINTENANCE_END);
|
||||
sIdleMaintenanceEndIntent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
|
||||
}
|
||||
// when a timeout happened, what were we expecting?
|
||||
static final int VERB_BINDING = 1;
|
||||
static final int VERB_IDLING = 2;
|
||||
static final int VERB_ENDING = 3;
|
||||
|
||||
// What are our relevant timeouts / allocated slices?
|
||||
static final long OP_TIMEOUT = 8 * 1000; // 8 seconds to bind or ack the start
|
||||
static final long IDLE_TIMESLICE = 10 * 60 * 1000; // ten minutes for each idler
|
||||
|
||||
private final AlarmManager mAlarmService;
|
||||
|
||||
private final BatteryService mBatteryService;
|
||||
|
||||
private final PendingIntent mUpdateIdleMaintenanceStatePendingIntent;
|
||||
|
||||
private final Context mContext;
|
||||
|
||||
private final WakeLock mWakeLock;
|
||||
|
||||
private final Handler mHandler;
|
||||
private final WorkSource mSystemWorkSource = new WorkSource(Process.myUid());
|
||||
|
||||
private long mLastIdleMaintenanceStartTimeMillis;
|
||||
|
||||
private long mLastUserActivityElapsedTimeMillis = LAST_USER_ACTIVITY_TIME_INVALID;
|
||||
|
||||
private boolean mIdleMaintenanceStarted;
|
||||
|
||||
final IdleCallback mCallback;
|
||||
final Handler mHandler;
|
||||
|
||||
final Random mTokenGenerator = new Random();
|
||||
|
||||
int makeToken() {
|
||||
int token;
|
||||
do {
|
||||
token = mTokenGenerator.nextInt(Integer.MAX_VALUE);
|
||||
} while (token == 0);
|
||||
return token;
|
||||
}
|
||||
|
||||
class ActiveTask {
|
||||
public IdleServiceInfo who;
|
||||
public int verb;
|
||||
public int token;
|
||||
|
||||
ActiveTask(IdleServiceInfo target, int action) {
|
||||
who = target;
|
||||
verb = action;
|
||||
token = makeToken();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ActiveTask{" + Integer.toHexString(this.hashCode())
|
||||
+ " : verb=" + verb
|
||||
+ " : token=" + token
|
||||
+ " : "+ who + "}";
|
||||
}
|
||||
}
|
||||
|
||||
// What operations are in flight?
|
||||
final SparseArray<ActiveTask> mPendingOperations = new SparseArray<ActiveTask>();
|
||||
|
||||
// Idle service queue management
|
||||
class IdleServiceInfo {
|
||||
public final ComponentName componentName;
|
||||
public final int uid;
|
||||
public IIdleService service;
|
||||
|
||||
IdleServiceInfo(ResolveInfo info, ComponentName cname) {
|
||||
componentName = cname; // derived from 'info' but this avoids an extra object
|
||||
uid = info.serviceInfo.applicationInfo.uid;
|
||||
service = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return componentName.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "IdleServiceInfo{" + componentName
|
||||
+ " / " + (service == null ? "null" : service.asBinder()) + "}";
|
||||
}
|
||||
}
|
||||
|
||||
final ArrayMap<ComponentName, IdleServiceInfo> mIdleServices =
|
||||
new ArrayMap<ComponentName, IdleServiceInfo>();
|
||||
final LinkedList<IdleServiceInfo> mIdleServiceQueue = new LinkedList<IdleServiceInfo>();
|
||||
IdleServiceInfo mCurrentIdler; // set when we've committed to launching an idler
|
||||
IdleServiceInfo mLastIdler; // end of queue when idling begins
|
||||
|
||||
void reportNoTimeout(int token, boolean result) {
|
||||
final Message msg = mHandler.obtainMessage(MSG_OP_COMPLETE, result ? 1 : 0, token);
|
||||
mHandler.sendMessage(msg);
|
||||
}
|
||||
|
||||
// Binder acknowledgment trampoline
|
||||
class IdleCallback extends IIdleCallback.Stub {
|
||||
@Override
|
||||
public void acknowledgeStart(int token, boolean result) throws RemoteException {
|
||||
reportNoTimeout(token, result);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void acknowledgeStop(int token) throws RemoteException {
|
||||
reportNoTimeout(token, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void idleFinished(int token) throws RemoteException {
|
||||
if (DEBUG) {
|
||||
Slog.v(TAG, "idleFinished: " + token);
|
||||
}
|
||||
final Message msg = mHandler.obtainMessage(MSG_IDLE_FINISHED, 0, token);
|
||||
mHandler.sendMessage(msg);
|
||||
}
|
||||
}
|
||||
|
||||
// Stuff that we run on a Handler
|
||||
class IdleHandler extends Handler {
|
||||
public IdleHandler(Looper looper) {
|
||||
super(looper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleMessage(Message msg) {
|
||||
final int token = msg.arg2;
|
||||
|
||||
switch (msg.what) {
|
||||
case MSG_OP_COMPLETE: {
|
||||
if (DEBUG) {
|
||||
Slog.i(TAG, "MSG_OP_COMPLETE of " + token);
|
||||
}
|
||||
ActiveTask task = mPendingOperations.get(token);
|
||||
if (task != null) {
|
||||
mPendingOperations.remove(token);
|
||||
removeMessages(MSG_TIMEOUT);
|
||||
|
||||
handleOpCompleteTm(task, msg.arg1);
|
||||
} else {
|
||||
// Can happen in a race between timeout and actual
|
||||
// (belated) completion of a "begin idling" or similar
|
||||
// operation. In that state we've already processed the
|
||||
// timeout, so we intentionally no-op here.
|
||||
if (DEBUG) {
|
||||
Slog.w(TAG, "Belated op-complete of " + token);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case MSG_IDLE_FINISHED: {
|
||||
if (DEBUG) {
|
||||
Slog.i(TAG, "MSG_IDLE_FINISHED of " + token);
|
||||
}
|
||||
ActiveTask task = mPendingOperations.get(token);
|
||||
if (task != null) {
|
||||
if (DEBUG) {
|
||||
Slog.i(TAG, "... removing task " + token);
|
||||
}
|
||||
mPendingOperations.remove(token);
|
||||
removeMessages(MSG_TIMEOUT);
|
||||
|
||||
handleIdleFinishedTm(task);
|
||||
} else {
|
||||
// Can happen "legitimately" from an app explicitly calling
|
||||
// idleFinished() after already having been told that its slice
|
||||
// has ended.
|
||||
if (DEBUG) {
|
||||
Slog.w(TAG, "Belated idle-finished of " + token);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case MSG_TIMEOUT: {
|
||||
if (DEBUG) {
|
||||
Slog.i(TAG, "MSG_TIMEOUT of " + token);
|
||||
}
|
||||
ActiveTask task = mPendingOperations.get(token);
|
||||
if (task != null) {
|
||||
mPendingOperations.remove(token);
|
||||
removeMessages(MSG_OP_COMPLETE);
|
||||
|
||||
handleTimeoutTm(task);
|
||||
} else {
|
||||
// This one should not happen; we flushed timeout messages
|
||||
// whenever we entered a state after which we have established
|
||||
// that they are not appropriate.
|
||||
Slog.w(TAG, "Unexpected timeout of " + token);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
Slog.w(TAG, "Unknown message: " + msg.what);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void handleTimeoutTm(ActiveTask task) {
|
||||
switch (task.verb) {
|
||||
case VERB_BINDING: {
|
||||
// We were trying to bind to this service, but it wedged or otherwise
|
||||
// failed to respond in time. Let it stay in the queue for the next
|
||||
// time around, but just give up on it for now and go on to the next.
|
||||
startNextIdleServiceTm();
|
||||
break;
|
||||
}
|
||||
case VERB_IDLING: {
|
||||
// The service has reached the end of its designated idle timeslice.
|
||||
// This is not considered an error.
|
||||
if (DEBUG) {
|
||||
Slog.i(TAG, "Idler reached end of timeslice: " + task.who);
|
||||
}
|
||||
sendEndIdleTm(task.who);
|
||||
break;
|
||||
}
|
||||
case VERB_ENDING: {
|
||||
if (mCurrentIdler == task.who) {
|
||||
if (DEBUG) {
|
||||
Slog.i(TAG, "Task timed out when ending; unbind needed");
|
||||
}
|
||||
handleIdleFinishedTm(task);
|
||||
} else {
|
||||
if (DEBUG) {
|
||||
Slog.w(TAG, "Ending timeout for non-current idle service!");
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
Slog.w(TAG, "Unknown timeout state " + task.verb);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void handleOpCompleteTm(ActiveTask task, int result) {
|
||||
if (DEBUG) {
|
||||
Slog.i(TAG, "handleOpComplete : task=" + task + " result=" + result);
|
||||
}
|
||||
if (task.verb == VERB_IDLING) {
|
||||
// If the service was told to begin idling and responded positively, then
|
||||
// it has begun idling and will eventually either explicitly finish, or
|
||||
// reach the end of its allotted timeslice. It's running free now, so we
|
||||
// just schedule the idle-expiration timeout under the token it's already been
|
||||
// given and let it keep going.
|
||||
if (result != 0) {
|
||||
scheduleOpTimeoutTm(task);
|
||||
} else {
|
||||
// The idle service has indicated that it does not, in fact,
|
||||
// need to run at present, so we immediately indicate that it's
|
||||
// to finish idling, and go on to the next idler.
|
||||
if (DEBUG) {
|
||||
Slog.i(TAG, "Idler declined idling; moving along");
|
||||
}
|
||||
sendEndIdleTm(task.who);
|
||||
}
|
||||
} else {
|
||||
// In the idling case, the task will be cleared either as the result of a timeout
|
||||
// or of an explicit idleFinished(). For all other operations (binding, ending) we
|
||||
// are done with the task as such, so we remove it from our bookkeeping.
|
||||
if (DEBUG) {
|
||||
Slog.i(TAG, "Clearing task " + task);
|
||||
}
|
||||
mPendingOperations.remove(task.token);
|
||||
if (task.verb == VERB_ENDING) {
|
||||
// The last bit of handshaking around idle cessation for this target
|
||||
handleIdleFinishedTm(task);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void handleIdleFinishedTm(ActiveTask task) {
|
||||
final IdleServiceInfo who = task.who;
|
||||
if (who == mCurrentIdler) {
|
||||
if (DEBUG) {
|
||||
Slog.i(TAG, "Current idler has finished: " + who);
|
||||
Slog.i(TAG, "Attributing wakelock to system work source");
|
||||
}
|
||||
mContext.unbindService(mConnection);
|
||||
startNextIdleServiceTm();
|
||||
} else {
|
||||
Slog.w(TAG, "finish from non-current idle service? " + who);
|
||||
}
|
||||
}
|
||||
|
||||
void updateIdleServiceQueueTm() {
|
||||
if (DEBUG) {
|
||||
Slog.i(TAG, "Updating idle service queue");
|
||||
}
|
||||
PackageManager pm = mContext.getPackageManager();
|
||||
Intent idleIntent = new Intent(IdleService.SERVICE_INTERFACE);
|
||||
List<ResolveInfo> services = pm.queryIntentServices(idleIntent, 0);
|
||||
for (ResolveInfo info : services) {
|
||||
if (info.serviceInfo != null) {
|
||||
if (IdleService.PERMISSION_BIND.equals(info.serviceInfo.permission)) {
|
||||
final ComponentName componentName = new ComponentName(
|
||||
info.serviceInfo.packageName,
|
||||
info.serviceInfo.name);
|
||||
if (DEBUG) {
|
||||
Slog.i(TAG, " - " + componentName);
|
||||
}
|
||||
if (!mIdleServices.containsKey(componentName)) {
|
||||
if (DEBUG) {
|
||||
Slog.i(TAG, " + not known; adding");
|
||||
}
|
||||
IdleServiceInfo serviceInfo = new IdleServiceInfo(info, componentName);
|
||||
mIdleServices.put(componentName, serviceInfo);
|
||||
mIdleServiceQueue.add(serviceInfo);
|
||||
}
|
||||
} else {
|
||||
if (DEBUG) {
|
||||
Slog.i(TAG, "Idle service " + info.serviceInfo
|
||||
+ " does not have required permission; ignoring");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void startNextIdleServiceTm() {
|
||||
mWakeLock.setWorkSource(mSystemWorkSource);
|
||||
|
||||
if (mLastIdler == null) {
|
||||
// we've run the queue; nothing more to do until the next idle interval.
|
||||
if (DEBUG) {
|
||||
Slog.i(TAG, "Queue already drained; nothing more to do");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (DEBUG) {
|
||||
Slog.i(TAG, "startNextIdleService : last=" + mLastIdler + " cur=" + mCurrentIdler);
|
||||
if (mIdleServiceQueue.size() > 0) {
|
||||
int i = 0;
|
||||
Slog.i(TAG, "Queue (" + mIdleServiceQueue.size() + "):");
|
||||
for (IdleServiceInfo info : mIdleServiceQueue) {
|
||||
Slog.i(TAG, " " + i + " : " + info);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (mCurrentIdler != mLastIdler) {
|
||||
if (mIdleServiceQueue.size() > 0) {
|
||||
IdleServiceInfo target = mIdleServiceQueue.pop();
|
||||
if (DEBUG) {
|
||||
Slog.i(TAG, "starting next idle service " + target);
|
||||
}
|
||||
Intent idleIntent = new Intent(IdleService.SERVICE_INTERFACE);
|
||||
idleIntent.setComponent(target.componentName);
|
||||
mCurrentIdler = target;
|
||||
ActiveTask task = new ActiveTask(target, VERB_BINDING);
|
||||
scheduleOpTimeoutTm(task);
|
||||
boolean bindOk = mContext.bindServiceAsUser(idleIntent, mConnection,
|
||||
Context.BIND_AUTO_CREATE | Context.BIND_WAIVE_PRIORITY, UserHandle.OWNER);
|
||||
if (!bindOk) {
|
||||
if (DEBUG) {
|
||||
Slog.w(TAG, "bindService() to " + target.componentName
|
||||
+ " failed");
|
||||
}
|
||||
} else {
|
||||
mIdleServiceQueue.add(target); // at the end for next time
|
||||
if (DEBUG) { Slog.i(TAG, "Attributing wakelock to target uid " + target.uid); }
|
||||
mWakeLock.setWorkSource(new WorkSource(target.uid));
|
||||
}
|
||||
} else {
|
||||
// Queue is empty but mLastIdler is non-null -- eeep. Clear *everything*
|
||||
// and wind up until the next time around.
|
||||
Slog.e(TAG, "Queue unexpectedly empty; resetting. last="
|
||||
+ mLastIdler + " cur=" + mCurrentIdler);
|
||||
mHandler.removeMessages(MSG_TIMEOUT);
|
||||
mPendingOperations.clear();
|
||||
stopIdleMaintenanceTm();
|
||||
}
|
||||
} else {
|
||||
// we've reached the place we started, so mark the queue as drained
|
||||
if (DEBUG) {
|
||||
Slog.i(TAG, "Reached end of queue.");
|
||||
}
|
||||
stopIdleMaintenanceTm();
|
||||
}
|
||||
}
|
||||
|
||||
void sendStartIdleTm(IdleServiceInfo who) {
|
||||
ActiveTask task = new ActiveTask(who, VERB_IDLING);
|
||||
scheduleOpTimeoutTm(task);
|
||||
try {
|
||||
who.service.startIdleMaintenance(mCallback, task.token);
|
||||
} catch (RemoteException e) {
|
||||
// We bound to it, but now we can't reach it. Bail and go on to the
|
||||
// next service.
|
||||
mContext.unbindService(mConnection);
|
||||
if (DEBUG) { Slog.i(TAG, "Attributing wakelock to system work source"); }
|
||||
mHandler.removeMessages(MSG_TIMEOUT);
|
||||
startNextIdleServiceTm();
|
||||
}
|
||||
}
|
||||
|
||||
void sendEndIdleTm(IdleServiceInfo who) {
|
||||
ActiveTask task = new ActiveTask(who, VERB_ENDING);
|
||||
scheduleOpTimeoutTm(task);
|
||||
if (DEBUG) {
|
||||
Slog.i(TAG, "Sending end-idle to " + who);
|
||||
}
|
||||
try {
|
||||
who.service.stopIdleMaintenance(mCallback, task.token);
|
||||
} catch (RemoteException e) {
|
||||
// We bound to it, but now we can't reach it. Bail and go on to the
|
||||
// next service.
|
||||
mContext.unbindService(mConnection);
|
||||
if (DEBUG) { Slog.i(TAG, "Attributing wakelock to system work source"); }
|
||||
mHandler.removeMessages(MSG_TIMEOUT);
|
||||
startNextIdleServiceTm();
|
||||
}
|
||||
}
|
||||
|
||||
ServiceConnection mConnection = new ServiceConnection() {
|
||||
@Override
|
||||
public void onServiceConnected(ComponentName name, IBinder service) {
|
||||
if (DEBUG) {
|
||||
Slog.i(TAG, "onServiceConnected(" + name + ")");
|
||||
}
|
||||
IdleServiceInfo info = mIdleServices.get(name);
|
||||
if (info != null) {
|
||||
// Bound! Cancel the bind timeout
|
||||
mHandler.removeMessages(MSG_TIMEOUT);
|
||||
// Now tell it to start its idle work
|
||||
info.service = IIdleService.Stub.asInterface(service);
|
||||
sendStartIdleTm(info);
|
||||
} else {
|
||||
// We bound to a service we don't know about. That's ungood.
|
||||
Slog.e(TAG, "Connected to unexpected component " + name);
|
||||
mContext.unbindService(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServiceDisconnected(ComponentName name) {
|
||||
if (DEBUG) {
|
||||
Slog.i(TAG, "onServiceDisconnected(" + name + ")");
|
||||
}
|
||||
IdleServiceInfo who = mIdleServices.get(name);
|
||||
if (who == mCurrentIdler) {
|
||||
// Hm, okay; they didn't tell us they were finished but they
|
||||
// went away. Crashed, probably. Oh well. They're gone, so
|
||||
// we can't finish them cleanly; just force things along.
|
||||
Slog.w(TAG, "Idler unexpectedly vanished: " + mCurrentIdler);
|
||||
mContext.unbindService(this);
|
||||
mHandler.removeMessages(MSG_TIMEOUT);
|
||||
startNextIdleServiceTm();
|
||||
} else {
|
||||
// Not the current idler, so we don't interrupt our process...
|
||||
if (DEBUG) {
|
||||
Slog.w(TAG, "Disconnect of abandoned or unexpected service " + name);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Schedules a timeout / end-of-work based on the task verb
|
||||
void scheduleOpTimeoutTm(ActiveTask task) {
|
||||
final long timeoutMillis = (task.verb == VERB_IDLING) ? IDLE_TIMESLICE : OP_TIMEOUT;
|
||||
if (DEBUG) {
|
||||
Slog.i(TAG, "Scheduling timeout (token " + task.token
|
||||
+ " : verb " + task.verb + ") for " + task + " in " + timeoutMillis);
|
||||
}
|
||||
mPendingOperations.put(task.token, task);
|
||||
mHandler.removeMessages(MSG_TIMEOUT);
|
||||
final Message msg = mHandler.obtainMessage(MSG_TIMEOUT, 0, task.token);
|
||||
mHandler.sendMessageDelayed(msg, timeoutMillis);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------------
|
||||
public IdleMaintenanceService(Context context, BatteryService batteryService) {
|
||||
mContext = context;
|
||||
mBatteryService = batteryService;
|
||||
@ -111,9 +574,10 @@ public class IdleMaintenanceService extends BroadcastReceiver {
|
||||
mAlarmService = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
|
||||
|
||||
PowerManager powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
|
||||
mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, LOG_TAG);
|
||||
mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
|
||||
|
||||
mHandler = new Handler(mContext.getMainLooper());
|
||||
mHandler = new IdleHandler(mContext.getMainLooper());
|
||||
mCallback = new IdleCallback();
|
||||
|
||||
Intent intent = new Intent(ACTION_UPDATE_IDLE_MAINTENANCE_STATE);
|
||||
intent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
|
||||
@ -159,26 +623,28 @@ public class IdleMaintenanceService extends BroadcastReceiver {
|
||||
mAlarmService.cancel(mUpdateIdleMaintenanceStatePendingIntent);
|
||||
}
|
||||
|
||||
private void updateIdleMaintenanceState(boolean noisy) {
|
||||
private void updateIdleMaintenanceStateTm(boolean noisy) {
|
||||
if (mIdleMaintenanceStarted) {
|
||||
// Idle maintenance can be interrupted by user activity, or duration
|
||||
// time out, or low battery.
|
||||
if (!lastUserActivityPermitsIdleMaintenanceRunning()
|
||||
|| !batteryLevelAndMaintenanceTimeoutPermitsIdleMaintenanceRunning()) {
|
||||
final boolean batteryOk
|
||||
= batteryLevelAndMaintenanceTimeoutPermitsIdleMaintenanceRunning();
|
||||
if (!lastUserActivityPermitsIdleMaintenanceRunning() || !batteryOk) {
|
||||
unscheduleUpdateIdleMaintenanceState();
|
||||
mIdleMaintenanceStarted = false;
|
||||
EventLogTags.writeIdleMaintenanceWindowFinish(SystemClock.elapsedRealtime(),
|
||||
mLastUserActivityElapsedTimeMillis, mBatteryService.getBatteryLevel(),
|
||||
isBatteryCharging() ? 1 : 0);
|
||||
sendIdleMaintenanceEndIntent();
|
||||
// We stopped since we don't have enough battery or timed out but the
|
||||
// user is not using the device, so we should be able to run maintenance
|
||||
// in the next maintenance window since the battery may be charged
|
||||
// without interaction and the min interval between maintenances passed.
|
||||
if (!batteryLevelAndMaintenanceTimeoutPermitsIdleMaintenanceRunning()) {
|
||||
if (!batteryOk) {
|
||||
scheduleUpdateIdleMaintenanceState(
|
||||
getNextIdleMaintenanceIntervalStartFromNow());
|
||||
}
|
||||
|
||||
EventLogTags.writeIdleMaintenanceWindowFinish(SystemClock.elapsedRealtime(),
|
||||
mLastUserActivityElapsedTimeMillis, mBatteryService.getBatteryLevel(),
|
||||
isBatteryCharging() ? 1 : 0);
|
||||
scheduleIdleFinishTm();
|
||||
}
|
||||
} else if (deviceStatePermitsIdleMaintenanceStart(noisy)
|
||||
&& lastUserActivityPermitsIdleMaintenanceStart(noisy)
|
||||
@ -191,7 +657,7 @@ public class IdleMaintenanceService extends BroadcastReceiver {
|
||||
mLastUserActivityElapsedTimeMillis, mBatteryService.getBatteryLevel(),
|
||||
isBatteryCharging() ? 1 : 0);
|
||||
mLastIdleMaintenanceStartTimeMillis = SystemClock.elapsedRealtime();
|
||||
sendIdleMaintenanceStartIntent();
|
||||
startIdleMaintenanceTm();
|
||||
} else if (lastUserActivityPermitsIdleMaintenanceStart(noisy)) {
|
||||
if (lastRunPermitsIdleMaintenanceStart(noisy)) {
|
||||
// The user does not use the device and we did not run maintenance in more
|
||||
@ -207,27 +673,57 @@ public class IdleMaintenanceService extends BroadcastReceiver {
|
||||
}
|
||||
}
|
||||
|
||||
void startIdleMaintenanceTm() {
|
||||
if (DEBUG) {
|
||||
Slog.i(TAG, "*** Starting idle maintenance ***");
|
||||
}
|
||||
if (DEBUG) { Slog.i(TAG, "Attributing wakelock to system work source"); }
|
||||
mWakeLock.setWorkSource(mSystemWorkSource);
|
||||
mWakeLock.acquire();
|
||||
updateIdleServiceQueueTm();
|
||||
mCurrentIdler = null;
|
||||
mLastIdler = (mIdleServiceQueue.size() > 0) ? mIdleServiceQueue.peekLast() : null;
|
||||
startNextIdleServiceTm();
|
||||
}
|
||||
|
||||
// Start a graceful wind-down of the idle maintenance state: end the current idler
|
||||
// and pretend that we've finished running the queue. If there's no current idler,
|
||||
// this is a no-op.
|
||||
void scheduleIdleFinishTm() {
|
||||
if (mCurrentIdler != null) {
|
||||
if (DEBUG) {
|
||||
Slog.i(TAG, "*** Finishing idle maintenance ***");
|
||||
}
|
||||
mLastIdler = mCurrentIdler;
|
||||
sendEndIdleTm(mCurrentIdler);
|
||||
} else {
|
||||
if (DEBUG) {
|
||||
Slog.w(TAG, "Asked to finish idle maintenance but we're done already");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Actual finalization of the idle maintenance sequence
|
||||
void stopIdleMaintenanceTm() {
|
||||
if (mLastIdler != null) {
|
||||
if (DEBUG) {
|
||||
Slog.i(TAG, "*** Idle maintenance shutdown ***");
|
||||
}
|
||||
mWakeLock.setWorkSource(mSystemWorkSource);
|
||||
mLastIdler = mCurrentIdler = null;
|
||||
updateIdleMaintenanceStateTm(false); // resets 'started' and schedules next window
|
||||
mWakeLock.release();
|
||||
} else {
|
||||
Slog.e(TAG, "ERROR: idle shutdown but invariants not held. last=" + mLastIdler
|
||||
+ " cur=" + mCurrentIdler + " size=" + mIdleServiceQueue.size());
|
||||
}
|
||||
}
|
||||
|
||||
private long getNextIdleMaintenanceIntervalStartFromNow() {
|
||||
return mLastIdleMaintenanceStartTimeMillis + MIN_IDLE_MAINTENANCE_INTERVAL_MILLIS
|
||||
- SystemClock.elapsedRealtime();
|
||||
}
|
||||
|
||||
private void sendIdleMaintenanceStartIntent() {
|
||||
mWakeLock.acquire();
|
||||
try {
|
||||
ActivityManagerNative.getDefault().performIdleMaintenance();
|
||||
} catch (RemoteException e) {
|
||||
}
|
||||
mContext.sendOrderedBroadcastAsUser(sIdleMaintenanceStartIntent, UserHandle.ALL,
|
||||
null, this, mHandler, Activity.RESULT_OK, null, null);
|
||||
}
|
||||
|
||||
private void sendIdleMaintenanceEndIntent() {
|
||||
mWakeLock.acquire();
|
||||
mContext.sendOrderedBroadcastAsUser(sIdleMaintenanceEndIntent, UserHandle.ALL,
|
||||
null, this, mHandler, Activity.RESULT_OK, null, null);
|
||||
}
|
||||
|
||||
private boolean deviceStatePermitsIdleMaintenanceStart(boolean noisy) {
|
||||
final int minBatteryLevel = isBatteryCharging()
|
||||
? MIN_BATTERY_LEVEL_IDLE_MAINTENANCE_START_CHARGING
|
||||
@ -281,7 +777,7 @@ public class IdleMaintenanceService extends BroadcastReceiver {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
if (DEBUG) {
|
||||
Log.i(LOG_TAG, intent.getAction());
|
||||
Log.i(TAG, intent.getAction());
|
||||
}
|
||||
String action = intent.getAction();
|
||||
if (Intent.ACTION_BATTERY_CHANGED.equals(action)) {
|
||||
@ -292,7 +788,7 @@ public class IdleMaintenanceService extends BroadcastReceiver {
|
||||
// next release. The only client for this for now is internal an holds
|
||||
// a wake lock correctly.
|
||||
if (mIdleMaintenanceStarted) {
|
||||
updateIdleMaintenanceState(false);
|
||||
updateIdleMaintenanceStateTm(false);
|
||||
}
|
||||
} else if (Intent.ACTION_SCREEN_ON.equals(action)
|
||||
|| Intent.ACTION_DREAMING_STOPPED.equals(action)) {
|
||||
@ -302,7 +798,7 @@ public class IdleMaintenanceService extends BroadcastReceiver {
|
||||
unscheduleUpdateIdleMaintenanceState();
|
||||
// If the screen went on/stopped dreaming, we know the user is using the
|
||||
// device which means that idle maintenance should be stopped if running.
|
||||
updateIdleMaintenanceState(false);
|
||||
updateIdleMaintenanceStateTm(false);
|
||||
} else if (Intent.ACTION_SCREEN_OFF.equals(action)
|
||||
|| Intent.ACTION_DREAMING_STARTED.equals(action)) {
|
||||
mLastUserActivityElapsedTimeMillis = SystemClock.elapsedRealtime();
|
||||
@ -311,17 +807,12 @@ public class IdleMaintenanceService extends BroadcastReceiver {
|
||||
// this timeout elapses since the device may go to sleep by then.
|
||||
scheduleUpdateIdleMaintenanceState(MIN_USER_INACTIVITY_IDLE_MAINTENANCE_START);
|
||||
} else if (ACTION_UPDATE_IDLE_MAINTENANCE_STATE.equals(action)) {
|
||||
updateIdleMaintenanceState(false);
|
||||
updateIdleMaintenanceStateTm(false);
|
||||
} else if (ACTION_FORCE_IDLE_MAINTENANCE.equals(action)) {
|
||||
long now = SystemClock.elapsedRealtime() - 1;
|
||||
mLastUserActivityElapsedTimeMillis = now - MIN_USER_INACTIVITY_IDLE_MAINTENANCE_START;
|
||||
mLastIdleMaintenanceStartTimeMillis = now - MIN_IDLE_MAINTENANCE_INTERVAL_MILLIS;
|
||||
updateIdleMaintenanceState(true);
|
||||
} else if (Intent.ACTION_IDLE_MAINTENANCE_START.equals(action)
|
||||
|| Intent.ACTION_IDLE_MAINTENANCE_END.equals(action)) {
|
||||
// We were holding a wake lock while broadcasting the idle maintenance
|
||||
// intents but now that we finished the broadcast release the wake lock.
|
||||
mWakeLock.release();
|
||||
updateIdleMaintenanceStateTm(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -107,6 +107,9 @@ import javax.crypto.spec.PBEKeySpec;
|
||||
class MountService extends IMountService.Stub
|
||||
implements INativeDaemonConnectorCallbacks, Watchdog.Monitor {
|
||||
|
||||
// Static direct instance pointer for the tightly-coupled idle service to use
|
||||
static MountService sSelf = null;
|
||||
|
||||
// TODO: listen for user creation/deletion
|
||||
|
||||
private static final boolean LOCAL_LOGD = false;
|
||||
@ -345,6 +348,7 @@ class MountService extends IMountService.Stub
|
||||
private static final int H_UNMOUNT_PM_DONE = 2;
|
||||
private static final int H_UNMOUNT_MS = 3;
|
||||
private static final int H_SYSTEM_READY = 4;
|
||||
private static final int H_FSTRIM = 5;
|
||||
|
||||
private static final int RETRY_UNMOUNT_DELAY = 30; // in ms
|
||||
private static final int MAX_UNMOUNT_RETRIES = 4;
|
||||
@ -494,6 +498,24 @@ class MountService extends IMountService.Stub
|
||||
}
|
||||
break;
|
||||
}
|
||||
case H_FSTRIM: {
|
||||
waitForReady();
|
||||
Slog.i(TAG, "Running fstrim idle maintenance");
|
||||
try {
|
||||
// This method must be run on the main (handler) thread,
|
||||
// so it is safe to directly call into vold.
|
||||
mConnector.execute("fstrim", "dotrim");
|
||||
EventLogTags.writeFstrimStart(SystemClock.elapsedRealtime());
|
||||
} catch (NativeDaemonConnectorException ndce) {
|
||||
Slog.e(TAG, "Failed to run fstrim!");
|
||||
}
|
||||
// invoke the completion callback, if any
|
||||
Runnable callback = (Runnable) msg.obj;
|
||||
if (callback != null) {
|
||||
callback.run();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -608,27 +630,6 @@ class MountService extends IMountService.Stub
|
||||
}
|
||||
};
|
||||
|
||||
private final BroadcastReceiver mIdleMaintenanceReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
waitForReady();
|
||||
String action = intent.getAction();
|
||||
// Since fstrim will be run on a daily basis we do not expect
|
||||
// fstrim to be too long, so it is not interruptible. We will
|
||||
// implement interruption only in case we see issues.
|
||||
if (Intent.ACTION_IDLE_MAINTENANCE_START.equals(action)) {
|
||||
try {
|
||||
// This method runs on the handler thread,
|
||||
// so it is safe to directly call into vold.
|
||||
mConnector.execute("fstrim", "dotrim");
|
||||
EventLogTags.writeFstrimStart(SystemClock.elapsedRealtime());
|
||||
} catch (NativeDaemonConnectorException ndce) {
|
||||
Slog.e(TAG, "Failed to run fstrim!");
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private final class MountServiceBinderListener implements IBinder.DeathRecipient {
|
||||
final IMountServiceListener mListener;
|
||||
|
||||
@ -646,6 +647,10 @@ class MountService extends IMountService.Stub
|
||||
}
|
||||
}
|
||||
|
||||
void runIdleMaintenance(Runnable callback) {
|
||||
mHandler.sendMessage(mHandler.obtainMessage(H_FSTRIM, callback));
|
||||
}
|
||||
|
||||
private void doShareUnshareVolume(String path, String method, boolean enable) {
|
||||
// TODO: Add support for multiple share methods
|
||||
if (!method.equals("ums")) {
|
||||
@ -1337,6 +1342,8 @@ class MountService extends IMountService.Stub
|
||||
* @param context Binder context for this service
|
||||
*/
|
||||
public MountService(Context context) {
|
||||
sSelf = this;
|
||||
|
||||
mContext = context;
|
||||
|
||||
synchronized (mVolumesLock) {
|
||||
@ -1363,12 +1370,6 @@ class MountService extends IMountService.Stub
|
||||
mUsbReceiver, new IntentFilter(UsbManager.ACTION_USB_STATE), null, mHandler);
|
||||
}
|
||||
|
||||
// Watch for idle maintenance changes
|
||||
IntentFilter idleMaintenanceFilter = new IntentFilter();
|
||||
idleMaintenanceFilter.addAction(Intent.ACTION_IDLE_MAINTENANCE_START);
|
||||
mContext.registerReceiverAsUser(mIdleMaintenanceReceiver, UserHandle.ALL,
|
||||
idleMaintenanceFilter, null, mHandler);
|
||||
|
||||
// Add OBB Action Handler to MountService thread.
|
||||
mObbActionHandler = new ObbActionHandler(IoThread.get().getLooper());
|
||||
|
||||
|
49
services/core/java/com/android/server/MountServiceIdler.java
Normal file
49
services/core/java/com/android/server/MountServiceIdler.java
Normal file
@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright (C) 2014 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.maintenance.IdleService;
|
||||
import android.util.Slog;
|
||||
|
||||
public class MountServiceIdler extends IdleService {
|
||||
private static final String TAG = "MountServiceIdler";
|
||||
|
||||
private Runnable mFinishCallback = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Slog.i(TAG, "Got mount service completion callback");
|
||||
finishIdle();
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public boolean onIdleStart() {
|
||||
// The mount service will run an fstrim operation asynchronously
|
||||
// on a designated separate thread, so we provide it with a callback
|
||||
// that lets us cleanly end our idle timeslice. It's safe to call
|
||||
// finishIdle() from any thread.
|
||||
MountService ms = MountService.sSelf;
|
||||
if (ms != null) {
|
||||
ms.runIdleMaintenance(mFinishCallback);
|
||||
}
|
||||
return ms != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onIdleStop() {
|
||||
}
|
||||
}
|
13
tests/IdleServiceTest/Android.mk
Normal file
13
tests/IdleServiceTest/Android.mk
Normal file
@ -0,0 +1,13 @@
|
||||
LOCAL_PATH:= $(call my-dir)
|
||||
include $(CLEAR_VARS)
|
||||
|
||||
LOCAL_MODULE_TAGS := tests
|
||||
|
||||
LOCAL_SRC_FILES := $(call all-subdir-java-files)
|
||||
|
||||
LOCAL_PACKAGE_NAME := IdleServiceTest
|
||||
LOCAL_CERTIFICATE := platform
|
||||
|
||||
LOCAL_PROGUARD_ENABLED := disabled
|
||||
|
||||
include $(BUILD_PACKAGE)
|
59
tests/IdleServiceTest/AndroidManifest.xml
Normal file
59
tests/IdleServiceTest/AndroidManifest.xml
Normal file
@ -0,0 +1,59 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2014 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.
|
||||
-->
|
||||
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.android.idleservicetest">
|
||||
|
||||
<application>
|
||||
<service android:name="TestService"
|
||||
android:exported="true"
|
||||
android:enabled="true"
|
||||
android:permission="android.permission.BIND_IDLE_SERVICE" >
|
||||
<intent-filter>
|
||||
<action android:name="android.service.idle.IdleService" />
|
||||
</intent-filter>
|
||||
</service>
|
||||
|
||||
<service android:name="CrashingTestService"
|
||||
android:exported="true"
|
||||
android:enabled="true"
|
||||
android:permission="android.permission.BIND_IDLE_SERVICE" >
|
||||
<intent-filter>
|
||||
<action android:name="android.service.idle.IdleService" />
|
||||
</intent-filter>
|
||||
</service>
|
||||
|
||||
<service android:name="TimeoutTestService"
|
||||
android:exported="true"
|
||||
android:enabled="true"
|
||||
android:permission="android.permission.BIND_IDLE_SERVICE" >
|
||||
<intent-filter>
|
||||
<action android:name="android.service.idle.IdleService" />
|
||||
</intent-filter>
|
||||
</service>
|
||||
|
||||
<!-- UnpermissionedTestService should never run because it does
|
||||
not require the necessary permission in its <service> block -->
|
||||
<service android:name="UnpermissionedTestService"
|
||||
android:exported="true"
|
||||
android:enabled="true" >
|
||||
<intent-filter>
|
||||
<action android:name="android.service.idle.IdleService" />
|
||||
</intent-filter>
|
||||
</service>
|
||||
|
||||
</application>
|
||||
</manifest>
|
@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright (C) 2014 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.idleservicetest;
|
||||
|
||||
import android.app.maintenance.IdleService;
|
||||
import android.os.Handler;
|
||||
import android.util.Log;
|
||||
|
||||
public class CrashingTestService extends IdleService {
|
||||
static final String TAG = "CrashingTestService";
|
||||
|
||||
String mNull = null;
|
||||
|
||||
@Override
|
||||
public boolean onIdleStart() {
|
||||
Log.i(TAG, "Idle maintenance: onIdleStart()");
|
||||
|
||||
Handler h = new Handler();
|
||||
Runnable r = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Log.i(TAG, "Explicitly crashing");
|
||||
if (mNull.equals("")) {
|
||||
Log.i(TAG, "won't happen");
|
||||
}
|
||||
}
|
||||
};
|
||||
Log.i(TAG, "Posting explicit crash in 15 seconds");
|
||||
h.postDelayed(r, 15 * 1000);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onIdleStop() {
|
||||
Log.i(TAG, "Idle maintenance: onIdleStop()");
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright (C) 2014 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.idleservicetest;
|
||||
|
||||
import android.app.maintenance.IdleService;
|
||||
import android.os.Handler;
|
||||
import android.util.Log;
|
||||
|
||||
public class TestService extends IdleService {
|
||||
static final String TAG = "TestService";
|
||||
|
||||
@Override
|
||||
public boolean onIdleStart() {
|
||||
Log.i(TAG, "Idle maintenance: onIdleStart()");
|
||||
|
||||
Handler h = new Handler();
|
||||
Runnable r = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Log.i(TAG, "Explicitly finishing idle");
|
||||
finishIdle();
|
||||
}
|
||||
};
|
||||
Log.i(TAG, "Posting explicit finish in 15 seconds");
|
||||
h.postDelayed(r, 15 * 1000);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onIdleStop() {
|
||||
Log.i(TAG, "Idle maintenance: onIdleStop()");
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright (C) 2014 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.idleservicetest;
|
||||
|
||||
import android.app.maintenance.IdleService;
|
||||
import android.util.Log;
|
||||
|
||||
public class TimeoutTestService extends IdleService {
|
||||
private static final String TAG = "TimeoutTestService";
|
||||
|
||||
@Override
|
||||
public boolean onIdleStart() {
|
||||
Log.i(TAG, "onIdleStart() but anticipating time-slice timeout");
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onIdleStop() {
|
||||
Log.i(TAG, "onIdleStop() so we're done");
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright (C) 2014 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.idleservicetest;
|
||||
|
||||
import android.app.maintenance.IdleService;
|
||||
import android.util.Log;
|
||||
|
||||
// Should never be invoked because its manifest declaration does not
|
||||
// require the necessary permission.
|
||||
public class UnpermissionedTestService extends IdleService {
|
||||
private static final String TAG = "UnpermissionedTestService";
|
||||
|
||||
@Override
|
||||
public boolean onIdleStart() {
|
||||
Log.e(TAG, "onIdleStart() for this service should never be called!");
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onIdleStop() {
|
||||
Log.e(TAG, "onIdleStop() for this service should never be called!");
|
||||
}
|
||||
|
||||
}
|
Reference in New Issue
Block a user