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