diff options
5 files changed, 412 insertions, 146 deletions
diff --git a/services/core/java/com/android/server/job/controllers/IdleController.java b/services/core/java/com/android/server/job/controllers/IdleController.java index 644f2c4d6864..e3c311f9e327 100644 --- a/services/core/java/com/android/server/job/controllers/IdleController.java +++ b/services/core/java/com/android/server/job/controllers/IdleController.java @@ -16,41 +16,33 @@ package com.android.server.job.controllers; -import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock; - -import android.app.AlarmManager; -import android.content.BroadcastReceiver; import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; +import android.content.pm.PackageManager; import android.os.UserHandle; import android.util.ArraySet; -import android.util.Log; import android.util.Slog; import android.util.proto.ProtoOutputStream; import com.android.internal.util.IndentingPrintWriter; -import com.android.server.am.ActivityManagerService; import com.android.server.job.JobSchedulerService; import com.android.server.job.StateControllerProto; +import com.android.server.job.controllers.idle.CarIdlenessTracker; +import com.android.server.job.controllers.idle.DeviceIdlenessTracker; +import com.android.server.job.controllers.idle.IdlenessListener; +import com.android.server.job.controllers.idle.IdlenessTracker; import java.util.function.Predicate; -public final class IdleController extends StateController { - private static final String TAG = "JobScheduler.Idle"; - private static final boolean DEBUG = JobSchedulerService.DEBUG - || Log.isLoggable(TAG, Log.DEBUG); - +public final class IdleController extends StateController implements IdlenessListener { + private static final String TAG = "JobScheduler.IdleController"; // Policy: we decide that we're "idle" if the device has been unused / // screen off or dreaming or wireless charging dock idle for at least this long - private long mInactivityIdleThreshold; - private long mIdleWindowSlop; final ArraySet<JobStatus> mTrackedTasks = new ArraySet<>(); IdlenessTracker mIdleTracker; public IdleController(JobSchedulerService service) { super(service); - initIdleStateTracking(); + initIdleStateTracking(mContext); } /** @@ -74,9 +66,10 @@ public final class IdleController extends StateController { } /** - * Interaction with the task manager service + * State-change notifications from the idleness tracker */ - void reportNewIdleState(boolean isIdle) { + @Override + public void reportNewIdleState(boolean isIdle) { synchronized (mLock) { for (int i = mTrackedTasks.size()-1; i >= 0; i--) { mTrackedTasks.valueAt(i).setIdleConstraintSatisfied(isIdle); @@ -89,141 +82,22 @@ public final class IdleController extends StateController { * Idle state tracking, and messaging with the task manager when * significant state changes occur */ - private void initIdleStateTracking() { - mInactivityIdleThreshold = mContext.getResources().getInteger( - com.android.internal.R.integer.config_jobSchedulerInactivityIdleThreshold); - mIdleWindowSlop = mContext.getResources().getInteger( - com.android.internal.R.integer.config_jobSchedulerIdleWindowSlop); - mIdleTracker = new IdlenessTracker(); - mIdleTracker.startTracking(); - } - - final class IdlenessTracker extends BroadcastReceiver { - private AlarmManager mAlarm; - - // After construction, mutations of idle/screen-on state will only happen - // on the main looper thread, either in onReceive() or in an alarm callback. - private boolean mIdle; - private boolean mScreenOn; - private boolean mDockIdle; - - private AlarmManager.OnAlarmListener mIdleAlarmListener = () -> { - handleIdleTrigger(); - }; - - public IdlenessTracker() { - mAlarm = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE); - - // At boot we presume that the user has just "interacted" with the - // device in some meaningful way. - mIdle = false; - mScreenOn = true; - mDockIdle = false; - } - - public boolean isIdle() { - return mIdle; - } - - public void startTracking() { - IntentFilter filter = new IntentFilter(); - - // Screen state - filter.addAction(Intent.ACTION_SCREEN_ON); - filter.addAction(Intent.ACTION_SCREEN_OFF); - - // Dreaming state - filter.addAction(Intent.ACTION_DREAMING_STARTED); - filter.addAction(Intent.ACTION_DREAMING_STOPPED); - - // Debugging/instrumentation - filter.addAction(ActivityManagerService.ACTION_TRIGGER_IDLE); - - // Wireless charging dock state - filter.addAction(Intent.ACTION_DOCK_IDLE); - filter.addAction(Intent.ACTION_DOCK_ACTIVE); - - mContext.registerReceiver(this, filter); - } - - @Override - public void onReceive(Context context, Intent intent) { - final String action = intent.getAction(); - if (action.equals(Intent.ACTION_SCREEN_ON) - || action.equals(Intent.ACTION_DREAMING_STOPPED) - || action.equals(Intent.ACTION_DOCK_ACTIVE)) { - if (action.equals(Intent.ACTION_DOCK_ACTIVE)) { - if (!mScreenOn) { - // Ignore this intent during screen off - return; - } else { - mDockIdle = false; - } - } else { - mScreenOn = true; - mDockIdle = false; - } - if (DEBUG) { - Slog.v(TAG,"exiting idle : " + action); - } - //cancel the alarm - mAlarm.cancel(mIdleAlarmListener); - if (mIdle) { - // possible transition to not-idle - mIdle = false; - reportNewIdleState(mIdle); - } - } else if (action.equals(Intent.ACTION_SCREEN_OFF) - || action.equals(Intent.ACTION_DREAMING_STARTED) - || action.equals(Intent.ACTION_DOCK_IDLE)) { - // when the screen goes off or dreaming starts or wireless charging dock in idle, - // we schedule the alarm that will tell us when we have decided the device is - // truly idle. - if (action.equals(Intent.ACTION_DOCK_IDLE)) { - if (!mScreenOn) { - // Ignore this intent during screen off - return; - } else { - mDockIdle = true; - } - } else { - mScreenOn = false; - mDockIdle = false; - } - final long nowElapsed = sElapsedRealtimeClock.millis(); - final long when = nowElapsed + mInactivityIdleThreshold; - if (DEBUG) { - Slog.v(TAG, "Scheduling idle : " + action + " now:" + nowElapsed + " when=" - + when); - } - mAlarm.setWindow(AlarmManager.ELAPSED_REALTIME_WAKEUP, - when, mIdleWindowSlop, "JS idleness", mIdleAlarmListener, null); - } else if (action.equals(ActivityManagerService.ACTION_TRIGGER_IDLE)) { - handleIdleTrigger(); - } - } - - private void handleIdleTrigger() { - // idle time starts now. Do not set mIdle if screen is on. - if (!mIdle && (!mScreenOn || mDockIdle)) { - if (DEBUG) { - Slog.v(TAG, "Idle trigger fired @ " + sElapsedRealtimeClock.millis()); - } - mIdle = true; - reportNewIdleState(mIdle); - } else { - if (DEBUG) { - Slog.v(TAG, "TRIGGER_IDLE received but not changing state; idle=" - + mIdle + " screen=" + mScreenOn); - } - } + private void initIdleStateTracking(Context ctx) { + final boolean isCar = mContext.getPackageManager().hasSystemFeature( + PackageManager.FEATURE_AUTOMOTIVE); + if (isCar) { + mIdleTracker = new CarIdlenessTracker(); + } else { + mIdleTracker = new DeviceIdlenessTracker(); } + mIdleTracker.startTracking(ctx, this); } @Override public void dumpControllerStateLocked(IndentingPrintWriter pw, Predicate<JobStatus> predicate) { pw.println("Currently idle: " + mIdleTracker.isIdle()); + pw.println("Idleness tracker:"); mIdleTracker.dump(pw); pw.println(); for (int i = 0; i < mTrackedTasks.size(); i++) { diff --git a/services/core/java/com/android/server/job/controllers/idle/CarIdlenessTracker.java b/services/core/java/com/android/server/job/controllers/idle/CarIdlenessTracker.java new file mode 100644 index 000000000000..a3949a4521c2 --- /dev/null +++ b/services/core/java/com/android/server/job/controllers/idle/CarIdlenessTracker.java @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2018 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.job.controllers.idle; + +import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; + +import android.util.Log; +import android.util.Slog; +import com.android.server.am.ActivityManagerService; +import com.android.server.job.JobSchedulerService; + +import java.io.PrintWriter; + +public final class CarIdlenessTracker extends BroadcastReceiver implements IdlenessTracker { + private static final String TAG = "JobScheduler.CarIdlenessTracker"; + private static final boolean DEBUG = JobSchedulerService.DEBUG + || Log.isLoggable(TAG, Log.DEBUG); + + public static final String ACTION_FORCE_IDLE = "com.android.server.ACTION_FORCE_IDLE"; + public static final String ACTION_UNFORCE_IDLE = "com.android.server.ACTION_UNFORCE_IDLE"; + + // After construction, mutations of idle/screen-on state will only happen + // on the main looper thread, either in onReceive() or in an alarm callback. + private boolean mIdle; + private boolean mScreenOn; + private IdlenessListener mIdleListener; + + public CarIdlenessTracker() { + // At boot we presume that the user has just "interacted" with the + // device in some meaningful way. + mIdle = false; + mScreenOn = true; + } + + @Override + public boolean isIdle() { + return mIdle; + } + + @Override + public void startTracking(Context context, IdlenessListener listener) { + mIdleListener = listener; + + IntentFilter filter = new IntentFilter(); + + // Screen state + filter.addAction(Intent.ACTION_SCREEN_ON); + filter.addAction(Intent.ACTION_SCREEN_OFF); + + // Debugging/instrumentation + filter.addAction(ACTION_FORCE_IDLE); + filter.addAction(ACTION_UNFORCE_IDLE); + filter.addAction(ActivityManagerService.ACTION_TRIGGER_IDLE); + + context.registerReceiver(this, filter); + } + + @Override + public void dump(PrintWriter pw) { + pw.print(" mIdle: "); pw.println(mIdle); + pw.print(" mScreenOn: "); pw.println(mScreenOn); + } + + @Override + public void onReceive(Context context, Intent intent) { + final String action = intent.getAction(); + logIfDebug("Received action: " + action); + + // Check for forced actions + if (action.equals(ACTION_FORCE_IDLE)) { + logIfDebug("Forcing idle..."); + enterIdleState(true); + } else if (action.equals(ACTION_UNFORCE_IDLE)) { + logIfDebug("Unforcing idle..."); + exitIdleState(true); + } else if (action.equals(Intent.ACTION_SCREEN_OFF)) { + logIfDebug("Going idle..."); + mScreenOn = false; + enterIdleState(false); + } else if (action.equals(Intent.ACTION_SCREEN_ON)) { + logIfDebug("exiting idle..."); + mScreenOn = true; + exitIdleState(true); + } else if (action.equals(ActivityManagerService.ACTION_TRIGGER_IDLE)) { + if (!mScreenOn) { + logIfDebug("Idle trigger fired..."); + enterIdleState(false); + } else { + logIfDebug("TRIGGER_IDLE received but not changing state; idle=" + + mIdle + " screen=" + mScreenOn); + } + } + } + + private void enterIdleState(boolean forced) { + if (!forced && mIdle) { + // Already idle and don't need to trigger callbacks since not forced + logIfDebug("Device is already considered idle"); + return; + } + mIdle = true; + mIdleListener.reportNewIdleState(mIdle); + } + + private void exitIdleState(boolean forced) { + if (!forced && !mIdle) { + // Already out of idle and don't need to trigger callbacks since not forced + logIfDebug("Device is already considered not idle"); + return; + } + mIdle = false; + mIdleListener.reportNewIdleState(mIdle); + } + + private void logIfDebug(String msg) { + if (DEBUG) { + Slog.v(TAG, msg); + } + } +}
\ No newline at end of file diff --git a/services/core/java/com/android/server/job/controllers/idle/DeviceIdlenessTracker.java b/services/core/java/com/android/server/job/controllers/idle/DeviceIdlenessTracker.java new file mode 100644 index 000000000000..a85bd4066ad3 --- /dev/null +++ b/services/core/java/com/android/server/job/controllers/idle/DeviceIdlenessTracker.java @@ -0,0 +1,175 @@ +/* + * Copyright (C) 2018 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.job.controllers.idle; + +import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock; + +import android.app.AlarmManager; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; + +import android.util.Log; +import android.util.Slog; +import com.android.server.am.ActivityManagerService; +import com.android.server.job.JobSchedulerService; + +import java.io.PrintWriter; + +public final class DeviceIdlenessTracker extends BroadcastReceiver implements IdlenessTracker { + private static final String TAG = "JobScheduler.DeviceIdlenessTracker"; + private static final boolean DEBUG = JobSchedulerService.DEBUG + || Log.isLoggable(TAG, Log.DEBUG); + + private AlarmManager mAlarm; + + // After construction, mutations of idle/screen-on state will only happen + // on the main looper thread, either in onReceive() or in an alarm callback. + private long mInactivityIdleThreshold; + private long mIdleWindowSlop; + private boolean mIdle; + private boolean mScreenOn; + private boolean mDockIdle; + private IdlenessListener mIdleListener; + + private AlarmManager.OnAlarmListener mIdleAlarmListener = () -> { + handleIdleTrigger(); + }; + + public DeviceIdlenessTracker() { + // At boot we presume that the user has just "interacted" with the + // device in some meaningful way. + mIdle = false; + mScreenOn = true; + mDockIdle = false; + } + + @Override + public boolean isIdle() { + return mIdle; + } + + @Override + public void startTracking(Context context, IdlenessListener listener) { + mIdleListener = listener; + mInactivityIdleThreshold = context.getResources().getInteger( + com.android.internal.R.integer.config_jobSchedulerInactivityIdleThreshold); + mIdleWindowSlop = context.getResources().getInteger( + com.android.internal.R.integer.config_jobSchedulerIdleWindowSlop); + mAlarm = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); + + IntentFilter filter = new IntentFilter(); + + // Screen state + filter.addAction(Intent.ACTION_SCREEN_ON); + filter.addAction(Intent.ACTION_SCREEN_OFF); + + // Dreaming state + filter.addAction(Intent.ACTION_DREAMING_STARTED); + filter.addAction(Intent.ACTION_DREAMING_STOPPED); + + // Debugging/instrumentation + filter.addAction(ActivityManagerService.ACTION_TRIGGER_IDLE); + + // Wireless charging dock state + filter.addAction(Intent.ACTION_DOCK_IDLE); + filter.addAction(Intent.ACTION_DOCK_ACTIVE); + + context.registerReceiver(this, filter); + } + + @Override + public void dump(PrintWriter pw) { + pw.print(" mIdle: "); pw.println(mIdle); + pw.print(" mScreenOn: "); pw.println(mScreenOn); + pw.print(" mDockIdle: "); pw.println(mDockIdle); + } + + @Override + public void onReceive(Context context, Intent intent) { + final String action = intent.getAction(); + if (action.equals(Intent.ACTION_SCREEN_ON) + || action.equals(Intent.ACTION_DREAMING_STOPPED) + || action.equals(Intent.ACTION_DOCK_ACTIVE)) { + if (action.equals(Intent.ACTION_DOCK_ACTIVE)) { + if (!mScreenOn) { + // Ignore this intent during screen off + return; + } else { + mDockIdle = false; + } + } else { + mScreenOn = true; + mDockIdle = false; + } + if (DEBUG) { + Slog.v(TAG,"exiting idle : " + action); + } + //cancel the alarm + mAlarm.cancel(mIdleAlarmListener); + if (mIdle) { + // possible transition to not-idle + mIdle = false; + mIdleListener.reportNewIdleState(mIdle); + } + } else if (action.equals(Intent.ACTION_SCREEN_OFF) + || action.equals(Intent.ACTION_DREAMING_STARTED) + || action.equals(Intent.ACTION_DOCK_IDLE)) { + // when the screen goes off or dreaming starts or wireless charging dock in idle, + // we schedule the alarm that will tell us when we have decided the device is + // truly idle. + if (action.equals(Intent.ACTION_DOCK_IDLE)) { + if (!mScreenOn) { + // Ignore this intent during screen off + return; + } else { + mDockIdle = true; + } + } else { + mScreenOn = false; + mDockIdle = false; + } + final long nowElapsed = sElapsedRealtimeClock.millis(); + final long when = nowElapsed + mInactivityIdleThreshold; + if (DEBUG) { + Slog.v(TAG, "Scheduling idle : " + action + " now:" + nowElapsed + " when=" + + when); + } + mAlarm.setWindow(AlarmManager.ELAPSED_REALTIME_WAKEUP, + when, mIdleWindowSlop, "JS idleness", mIdleAlarmListener, null); + } else if (action.equals(ActivityManagerService.ACTION_TRIGGER_IDLE)) { + handleIdleTrigger(); + } + } + + private void handleIdleTrigger() { + // idle time starts now. Do not set mIdle if screen is on. + if (!mIdle && (!mScreenOn || mDockIdle)) { + if (DEBUG) { + Slog.v(TAG, "Idle trigger fired @ " + sElapsedRealtimeClock.millis()); + } + mIdle = true; + mIdleListener.reportNewIdleState(mIdle); + } else { + if (DEBUG) { + Slog.v(TAG, "TRIGGER_IDLE received but not changing state; idle=" + + mIdle + " screen=" + mScreenOn); + } + } + } +}
\ No newline at end of file diff --git a/services/core/java/com/android/server/job/controllers/idle/IdlenessListener.java b/services/core/java/com/android/server/job/controllers/idle/IdlenessListener.java new file mode 100644 index 000000000000..7ffd7cd3e2e0 --- /dev/null +++ b/services/core/java/com/android/server/job/controllers/idle/IdlenessListener.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2018 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.job.controllers.idle; + +/** + * Interface through which an IdlenessTracker informs the job scheduler of + * changes in the device's inactivity state. + */ +public interface IdlenessListener { + /** + * Tell the job scheduler that the device's idle state has changed. + * + * @param deviceIsIdle {@code true} to indicate that the device is now considered + * to be idle; {@code false} to indicate that the device is now being interacted with, + * so jobs with idle constraints should not be run. + */ + void reportNewIdleState(boolean deviceIsIdle); +} diff --git a/services/core/java/com/android/server/job/controllers/idle/IdlenessTracker.java b/services/core/java/com/android/server/job/controllers/idle/IdlenessTracker.java new file mode 100644 index 000000000000..09f01c2e4113 --- /dev/null +++ b/services/core/java/com/android/server/job/controllers/idle/IdlenessTracker.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2018 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.job.controllers.idle; + +import android.content.Context; + +import java.io.PrintWriter; + +public interface IdlenessTracker { + /** + * One-time initialization: this method is called once, after construction of + * the IdlenessTracker instance. This is when the tracker should actually begin + * monitoring whatever signals it consumes in deciding when the device is in a + * non-interacting state. When the idle state changes thereafter, the given + * listener must be called to report the new state. + */ + void startTracking(Context context, IdlenessListener listener); + + /** + * Report whether the device is currently considered "idle" for purposes of + * running scheduled jobs with idleness constraints. + * + * @return {@code true} if the job scheduler should consider idleness + * constraints to be currently satisfied; {@code false} otherwise. + */ + boolean isIdle(); + + /** + * Dump useful information about tracked idleness-related state in plaintext. + */ + void dump(PrintWriter pw); +} |