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.Bundle;
     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.WorkDatabase;
     30 import androidx.work.impl.WorkManagerImpl;
     31 import androidx.work.impl.model.WorkSpec;
     32 import androidx.work.impl.model.WorkSpecDao;
     33 
     34 import java.util.HashMap;
     35 import java.util.Map;
     36 
     37 /**
     38  * The command handler used by {@link SystemAlarmDispatcher}.
     39  *
     40  * @hide
     41  */
     42 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
     43 public class CommandHandler implements ExecutionListener {
     44 
     45     private static final String TAG = "CommandHandler";
     46 
     47     // actions
     48     static final String ACTION_SCHEDULE_WORK = "ACTION_SCHEDULE_WORK";
     49     static final String ACTION_DELAY_MET = "ACTION_DELAY_MET";
     50     static final String ACTION_STOP_WORK = "ACTION_STOP_WORK";
     51     static final String ACTION_CONSTRAINTS_CHANGED = "ACTION_CONSTRAINTS_CHANGED";
     52     static final String ACTION_RESCHEDULE = "ACTION_RESCHEDULE";
     53     static final String ACTION_EXECUTION_COMPLETED = "ACTION_EXECUTION_COMPLETED";
     54 
     55     // keys
     56     private static final String KEY_WORKSPEC_ID = "KEY_WORKSPEC_ID";
     57     private static final String KEY_IS_SUCCESSFUL = "KEY_IS_SUCCESSFUL";
     58     private static final String KEY_NEEDS_RESCHEDULE = "KEY_NEEDS_RESCHEDULE";
     59 
     60     // constants
     61     static final long WORK_PROCESSING_TIME_IN_MS = 10 * 60 * 1000L;
     62 
     63     // utilities
     64     static Intent createScheduleWorkIntent(@NonNull Context context, @NonNull String workSpecId) {
     65         Intent intent = new Intent(context, SystemAlarmService.class);
     66         intent.setAction(ACTION_SCHEDULE_WORK);
     67         intent.putExtra(KEY_WORKSPEC_ID, workSpecId);
     68         return intent;
     69     }
     70 
     71     static Intent createDelayMetIntent(@NonNull Context context, @NonNull String workSpecId) {
     72         Intent intent = new Intent(context, SystemAlarmService.class);
     73         intent.setAction(ACTION_DELAY_MET);
     74         intent.putExtra(KEY_WORKSPEC_ID, workSpecId);
     75         return intent;
     76     }
     77 
     78     static Intent createStopWorkIntent(@NonNull Context context, @NonNull String workSpecId) {
     79         Intent intent = new Intent(context, SystemAlarmService.class);
     80         intent.setAction(ACTION_STOP_WORK);
     81         intent.putExtra(KEY_WORKSPEC_ID, workSpecId);
     82         return intent;
     83     }
     84 
     85     static Intent createConstraintsChangedIntent(@NonNull Context context) {
     86         Intent intent = new Intent(context, SystemAlarmService.class);
     87         intent.setAction(ACTION_CONSTRAINTS_CHANGED);
     88         return intent;
     89     }
     90 
     91     static Intent createRescheduleIntent(@NonNull Context context) {
     92         Intent intent = new Intent(context, SystemAlarmService.class);
     93         intent.setAction(ACTION_RESCHEDULE);
     94         return intent;
     95     }
     96 
     97     static Intent createExecutionCompletedIntent(
     98             @NonNull Context context,
     99             @NonNull String workSpecId,
    100             boolean isSuccessful,
    101             boolean needsReschedule) {
    102 
    103         Intent intent = new Intent(context, SystemAlarmService.class);
    104         intent.setAction(ACTION_EXECUTION_COMPLETED);
    105         intent.putExtra(KEY_WORKSPEC_ID, workSpecId);
    106         intent.putExtra(KEY_IS_SUCCESSFUL, isSuccessful);
    107         intent.putExtra(KEY_NEEDS_RESCHEDULE, needsReschedule);
    108         return intent;
    109     }
    110 
    111     // members
    112     private final Context mContext;
    113     private final Map<String, ExecutionListener> mPendingDelayMet;
    114     private final Object mLock;
    115 
    116     CommandHandler(@NonNull Context context) {
    117         mContext = context;
    118         mPendingDelayMet = new HashMap<>();
    119         mLock = new Object();
    120     }
    121 
    122     @Override
    123     public void onExecuted(
    124             @NonNull String workSpecId,
    125             boolean isSuccessful,
    126             boolean needsReschedule) {
    127 
    128         synchronized (mLock) {
    129             // This listener is only necessary for knowing when a pending work is complete.
    130             // Delegate to the underlying execution listener itself.
    131             ExecutionListener listener = mPendingDelayMet.remove(workSpecId);
    132             if (listener != null) {
    133                 listener.onExecuted(workSpecId, isSuccessful, needsReschedule);
    134             }
    135         }
    136     }
    137 
    138     /**
    139      * @return <code>true</code> if there is work pending.
    140      */
    141     boolean hasPendingCommands() {
    142         // Needs to be synchronized as this could be checked from
    143         // both the command processing thread, as well as the
    144         // onExecuted callback.
    145         synchronized (mLock) {
    146             // If we have pending work being executed on the background
    147             // processor - we are not done yet.
    148             return !mPendingDelayMet.isEmpty();
    149         }
    150     }
    151 
    152     /**
    153      * The actual command handler.
    154      */
    155     @WorkerThread
    156     void onHandleIntent(
    157             @NonNull Intent intent,
    158             int startId,
    159             @NonNull SystemAlarmDispatcher dispatcher) {
    160 
    161         String action = intent.getAction();
    162 
    163         if (ACTION_CONSTRAINTS_CHANGED.equals(action)) {
    164             handleConstraintsChanged(intent, startId, dispatcher);
    165         } else if (ACTION_RESCHEDULE.equals(action)) {
    166             handleReschedule(intent, startId, dispatcher);
    167         } else {
    168             Bundle extras = intent.getExtras();
    169             if (!hasKeys(extras, KEY_WORKSPEC_ID)) {
    170                 Log.e(TAG, String.format(
    171                         "Invalid request for %s, requires %s.", action, KEY_WORKSPEC_ID));
    172             } else {
    173                 if (ACTION_SCHEDULE_WORK.equals(action)) {
    174                     handleScheduleWorkIntent(intent, startId, dispatcher);
    175                 } else if (ACTION_DELAY_MET.equals(action)) {
    176                     handleDelayMet(intent, startId, dispatcher);
    177                 } else if (ACTION_STOP_WORK.equals(action)) {
    178                     handleStopWork(intent, startId, dispatcher);
    179                 } else if (ACTION_EXECUTION_COMPLETED.equals(action)) {
    180                     handleExecutionCompleted(intent, startId, dispatcher);
    181                 } else {
    182                     Log.w(TAG, String.format("Ignoring intent %s", intent));
    183                 }
    184             }
    185         }
    186     }
    187 
    188     private void handleScheduleWorkIntent(
    189             @NonNull Intent intent,
    190             int startId,
    191             @NonNull SystemAlarmDispatcher dispatcher) {
    192 
    193         Bundle extras = intent.getExtras();
    194         String workSpecId = extras.getString(KEY_WORKSPEC_ID);
    195         Log.d(TAG, String.format("Handling schedule work for %s", workSpecId));
    196 
    197         WorkManagerImpl workManager = dispatcher.getWorkManager();
    198         WorkDatabase workDatabase = workManager.getWorkDatabase();
    199         WorkSpecDao workSpecDao = workDatabase.workSpecDao();
    200 
    201         WorkSpec workSpec = workSpecDao.getWorkSpec(workSpecId);
    202         long triggerAt = workSpec.calculateNextRunTime();
    203 
    204         if (!workSpec.hasConstraints()) {
    205             Log.d(TAG, String.format("Setting up Alarms for %s", workSpecId));
    206             Alarms.setAlarm(mContext, dispatcher.getWorkManager(), workSpecId, triggerAt);
    207         } else {
    208             // Schedule an alarm irrespective of whether all constraints matched.
    209             Log.d(TAG, String.format("Opportunistically setting an alarm for %s", workSpecId));
    210             Alarms.setAlarm(
    211                     mContext,
    212                     dispatcher.getWorkManager(),
    213                     workSpecId,
    214                     triggerAt);
    215 
    216             // Schedule an update for constraint proxies
    217             // This in turn sets enables us to track changes in constraints
    218             Intent constraintsUpdate = CommandHandler.createConstraintsChangedIntent(mContext);
    219             dispatcher.postOnMainThread(
    220                     new SystemAlarmDispatcher.AddRunnable(
    221                             dispatcher,
    222                             constraintsUpdate,
    223                             startId));
    224         }
    225     }
    226 
    227     private void handleDelayMet(
    228             @NonNull Intent intent,
    229             int startId,
    230             @NonNull SystemAlarmDispatcher dispatcher) {
    231 
    232         Bundle extras = intent.getExtras();
    233         synchronized (mLock) {
    234             String workSpecId = extras.getString(KEY_WORKSPEC_ID);
    235             Log.d(TAG, String.format("Handing delay met for %s", workSpecId));
    236             DelayMetCommandHandler delayMetCommandHandler =
    237                     new DelayMetCommandHandler(mContext, startId, workSpecId, dispatcher);
    238             mPendingDelayMet.put(workSpecId, delayMetCommandHandler);
    239             delayMetCommandHandler.handleProcessWork();
    240         }
    241     }
    242 
    243     private void handleStopWork(
    244             @NonNull Intent intent, int startId,
    245             @NonNull SystemAlarmDispatcher dispatcher) {
    246 
    247         Bundle extras = intent.getExtras();
    248         String workSpecId = extras.getString(KEY_WORKSPEC_ID);
    249         Log.d(TAG, String.format("Handing stopWork work for %s", workSpecId));
    250 
    251         dispatcher.getWorkManager().stopWork(workSpecId);
    252         Alarms.cancelAlarm(mContext, dispatcher.getWorkManager(), workSpecId);
    253 
    254         // Notify dispatcher, so it can clean up.
    255         dispatcher.onExecuted(workSpecId, false, false /* never reschedule */);
    256     }
    257 
    258     private void handleConstraintsChanged(
    259             @NonNull Intent intent, int startId,
    260             @NonNull SystemAlarmDispatcher dispatcher) {
    261 
    262         Log.d(TAG, String.format("Handling constraints changed %s", intent));
    263         // Constraints changed command handler is synchronous. No cleanup
    264         // is necessary.
    265         ConstraintsCommandHandler changedCommandHandler =
    266                 new ConstraintsCommandHandler(mContext, startId, dispatcher);
    267         changedCommandHandler.handleConstraintsChanged();
    268     }
    269 
    270     private void handleReschedule(
    271             @NonNull Intent intent,
    272             int startId,
    273             @NonNull SystemAlarmDispatcher dispatcher) {
    274 
    275         Log.d(TAG, String.format("Handling reschedule %s, %s", intent, startId));
    276         dispatcher.getWorkManager().rescheduleEligibleWork();
    277     }
    278 
    279     private void handleExecutionCompleted(
    280             @NonNull Intent intent,
    281             int startId,
    282             @NonNull SystemAlarmDispatcher dispatcher) {
    283 
    284         Bundle extras = intent.getExtras();
    285         String workSpecId = extras.getString(KEY_WORKSPEC_ID);
    286         boolean isSuccessful = extras.getBoolean(KEY_IS_SUCCESSFUL);
    287         boolean needsReschedule = extras.getBoolean(KEY_NEEDS_RESCHEDULE);
    288         Log.d(TAG, String.format("Handling onExecutionCompleted %s, %s", intent, startId));
    289         // Delegate onExecuted() to the command handler.
    290         onExecuted(workSpecId, isSuccessful, needsReschedule);
    291         // Check if we need to stop service.
    292         dispatcher.postOnMainThread(
    293                 new SystemAlarmDispatcher.CheckForCompletionRunnable(dispatcher));
    294     }
    295 
    296     private static boolean hasKeys(@Nullable Bundle bundle, @NonNull String... keys) {
    297         if (bundle == null || bundle.isEmpty()) {
    298             return false;
    299         } else {
    300             for (String key : keys) {
    301                 if (!bundle.containsKey(key) || bundle.get(key) == null) {
    302                     return false;
    303                 }
    304             }
    305             return true;
    306         }
    307     }
    308 }
    309