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