Home | History | Annotate | Download | only in systemalarm
      1 /*
      2  * Copyright 2018 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package androidx.work.impl.background.systemalarm;
     18 
     19 import android.content.Context;
     20 import android.content.Intent;
     21 import android.os.PowerManager;
     22 import android.support.annotation.NonNull;
     23 import android.support.annotation.Nullable;
     24 import android.support.annotation.RestrictTo;
     25 import android.support.annotation.WorkerThread;
     26 import android.util.Log;
     27 
     28 import androidx.work.impl.ExecutionListener;
     29 import androidx.work.impl.constraints.WorkConstraintsCallback;
     30 import androidx.work.impl.constraints.WorkConstraintsTracker;
     31 import androidx.work.impl.model.WorkSpec;
     32 import androidx.work.impl.utils.WakeLocks;
     33 
     34 import java.util.Collections;
     35 import java.util.List;
     36 
     37 /**
     38  * This is a command handler which attempts to run a work spec given its id.
     39  * Also handles constraints gracefully.
     40  *
     41  * @hide
     42  */
     43 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
     44 public class DelayMetCommandHandler implements
     45         WorkConstraintsCallback,
     46         ExecutionListener,
     47         WorkTimer.TimeLimitExceededListener {
     48 
     49     private static final String TAG = "DelayMetCommandHandler";
     50 
     51     private final Context mContext;
     52     private final int mStartId;
     53     private final String mWorkSpecId;
     54     private final SystemAlarmDispatcher mDispatcher;
     55     private final WorkConstraintsTracker mWorkConstraintsTracker;
     56     private final Object mLock;
     57     private boolean mHasPendingStopWorkCommand;
     58 
     59     @Nullable private PowerManager.WakeLock mWakeLock;
     60     private boolean mHasConstraints;
     61 
     62     DelayMetCommandHandler(
     63             @NonNull Context context,
     64             int startId,
     65             @NonNull String workSpecId,
     66             @NonNull SystemAlarmDispatcher dispatcher) {
     67 
     68         mContext = context;
     69         mStartId = startId;
     70         mDispatcher = dispatcher;
     71         mWorkSpecId = workSpecId;
     72         mWorkConstraintsTracker = new WorkConstraintsTracker(mContext, this);
     73         mHasConstraints = false;
     74         mHasPendingStopWorkCommand = false;
     75         mLock = new Object();
     76     }
     77 
     78     @Override
     79     public void onAllConstraintsMet(@NonNull List<String> ignored) {
     80         Log.d(TAG, String.format("onAllConstraintsMet for %s", mWorkSpecId));
     81         // Constraints met, schedule execution
     82 
     83         // Not using WorkManagerImpl#startWork() here because we need to know if the processor
     84         // actually enqueued the work here.
     85         // TODO(rahulrav@) Once WorkManagerImpl provides a callback for acknowledging if
     86         // work was enqueued, call WorkManagerImpl#startWork().
     87         boolean isEnqueued = mDispatcher.getProcessor().startWork(mWorkSpecId);
     88 
     89         if (isEnqueued) {
     90             // setup timers to enforce quotas on workers that have
     91             // been enqueued
     92             mDispatcher.getWorkTimer()
     93                     .startTimer(mWorkSpecId, CommandHandler.WORK_PROCESSING_TIME_IN_MS, this);
     94         } else {
     95             // if we did not actually enqueue the work, it was enqueued before
     96             // cleanUp and pretend this never happened.
     97             cleanUp();
     98         }
     99     }
    100 
    101     @Override
    102     public void onExecuted(
    103             @NonNull String workSpecId,
    104             boolean isSuccessful,
    105             boolean needsReschedule) {
    106 
    107         Log.d(TAG, String.format(
    108                 "onExecuted %s, %s, %s", workSpecId, isSuccessful, needsReschedule));
    109 
    110         cleanUp();
    111 
    112         if (mHasConstraints) {
    113             // The WorkSpec had constraints. Once the execution of the worker is complete,
    114             // we might need to disable constraint proxies which were previously enabled for
    115             // this WorkSpec. Hence, trigger a constraints changed command.
    116             Intent intent = CommandHandler.createConstraintsChangedIntent(mContext);
    117             mDispatcher.postOnMainThread(
    118                     new SystemAlarmDispatcher.AddRunnable(mDispatcher, intent, mStartId));
    119         }
    120     }
    121 
    122     @Override
    123     public void onTimeLimitExceeded(@NonNull String workSpecId) {
    124         Log.d(TAG, String.format("Exceeded time limits on execution for %s", workSpecId));
    125         stopWork();
    126     }
    127 
    128     @Override
    129     public void onAllConstraintsNotMet(@NonNull List<String> ignored) {
    130         stopWork();
    131     }
    132 
    133     @WorkerThread
    134     void handleProcessWork() {
    135         mWakeLock = WakeLocks.newWakeLock(
    136                 mContext,
    137                 String.format("%s (%s)", mWorkSpecId, mStartId));
    138         Log.d(TAG, String.format("Acquiring wakelock %s for WorkSpec %s", mWakeLock, mWorkSpecId));
    139         mWakeLock.acquire();
    140 
    141         WorkSpec workSpec = mDispatcher.getWorkManager()
    142                 .getWorkDatabase()
    143                 .workSpecDao()
    144                 .getWorkSpec(mWorkSpecId);
    145 
    146         // Keep track of whether the WorkSpec had constraints. This is useful for updating the
    147         // state of constraint proxies when onExecuted().
    148         mHasConstraints = workSpec.hasConstraints();
    149 
    150         if (!mHasConstraints) {
    151             Log.d(TAG, String.format("No constraints for %s", mWorkSpecId));
    152             onAllConstraintsMet(Collections.singletonList(mWorkSpecId));
    153         } else {
    154             // Allow tracker to report constraint changes
    155             mWorkConstraintsTracker.replace(Collections.singletonList(workSpec));
    156         }
    157     }
    158 
    159     private void stopWork() {
    160         // No need to release the wake locks here. The stopWork command will eventually call
    161         // onExecuted() if there is a corresponding pending delay met command handler; which in
    162         // turn calls cleanUp().
    163 
    164         // Needs to be synchronized, as the stopWork() request can potentially come from the
    165         // WorkTimer thread as well as the command executor service in SystemAlarmDispatcher.
    166         synchronized (mLock) {
    167             if (!mHasPendingStopWorkCommand) {
    168                 Log.d(TAG, String.format("Stopping work for workspec %s", mWorkSpecId));
    169                 Intent stopWork = CommandHandler.createStopWorkIntent(mContext, mWorkSpecId);
    170                 mDispatcher.postOnMainThread(
    171                         new SystemAlarmDispatcher.AddRunnable(mDispatcher, stopWork, mStartId));
    172                 // There are cases where the work may not have been enqueued at all, and therefore
    173                 // the processor is completely unaware of such a workSpecId in which case a
    174                 // reschedule should not happen. For e.g. DELAY_MET when constraints are not met,
    175                 // should not result in a reschedule.
    176                 if (mDispatcher.getProcessor().isEnqueued(mWorkSpecId)) {
    177                     Log.d(TAG, String.format("WorkSpec %s needs to be rescheduled", mWorkSpecId));
    178                     Intent reschedule = CommandHandler.createScheduleWorkIntent(mContext,
    179                             mWorkSpecId);
    180                     mDispatcher.postOnMainThread(
    181                             new SystemAlarmDispatcher.AddRunnable(mDispatcher, reschedule,
    182                                     mStartId));
    183                 } else {
    184                     Log.d(TAG, String.format(
    185                             "Processor does not have WorkSpec %s. No need to reschedule ",
    186                             mWorkSpecId));
    187                 }
    188                 mHasPendingStopWorkCommand = true;
    189             } else {
    190                 Log.d(TAG, String.format("Already stopped work for %s", mWorkSpecId));
    191             }
    192         }
    193     }
    194 
    195     private void cleanUp() {
    196         // cleanUp() may occur from one of 2 threads.
    197         // * In the call to bgProcessor.startWork() returns false,
    198         //   it probably means that the worker is already being processed
    199         //   so we just need to call cleanUp to release wakelocks on the command processor thread.
    200         // * It could also happen on the onExecutionCompleted() pass of the bgProcessor.
    201         // To avoid calling mWakeLock.release() twice, we are synchronizing here.
    202         synchronized (mLock) {
    203             // stop timers
    204             mDispatcher.getWorkTimer().stopTimer(mWorkSpecId);
    205 
    206             // release wake locks
    207             if (mWakeLock != null && mWakeLock.isHeld()) {
    208                 Log.d(TAG, String.format(
    209                         "Releasing wakelock %s for WorkSpec %s", mWakeLock, mWorkSpecId));
    210                 mWakeLock.release();
    211             }
    212         }
    213     }
    214 }
    215