1 /*
2  * Copyright (C) 2022 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 com.android.internal.telephony.ims;
18 
19 import android.content.ComponentName;
20 import android.os.Handler;
21 import android.os.IBinder;
22 import android.os.Looper;
23 import android.os.Message;
24 import android.os.RemoteException;
25 import android.telephony.SubscriptionManager;
26 import android.telephony.ims.aidl.IImsServiceController;
27 import android.util.Log;
28 import android.util.SparseArray;
29 
30 import com.android.internal.annotations.VisibleForTesting;
31 import com.android.internal.util.IState;
32 import com.android.internal.util.State;
33 import com.android.internal.util.StateMachine;
34 
35 import java.util.HashMap;
36 import java.util.Map;
37 
38 /**
39  * This class will abstract away all the new enablement logic and take the reset/enable/disable
40  * IMS commands as inputs.
41  * The IMS commands will call enableIms, disableIms or resetIms to match the enablement state only
42  * when it changes.
43  */
44 public class ImsEnablementTracker {
45     private static final String LOG_TAG = "ImsEnablementTracker";
46     private static final long REQUEST_THROTTLE_TIME_MS = 3 * 1000L; // 3 seconds
47 
48     private static final int COMMAND_NONE_MSG = 0;
49     // Indicate that the enableIms command has been received.
50     @VisibleForTesting
51     public static final int COMMAND_ENABLE_MSG = 1;
52     // Indicate that the disableIms command has been received.
53     @VisibleForTesting
54     public static final int COMMAND_DISABLE_MSG = 2;
55     // Indicate that the resetIms command has been received.
56     private static final int COMMAND_RESET_MSG = 3;
57     // Indicate that the internal enable message with delay has been received.
58     private static final int COMMAND_ENABLING_DONE = 4;
59     // Indicate that the internal disable message with delay has been received.
60     private static final int COMMAND_DISABLING_DONE = 5;
61     // Indicate that the internal reset message with delay has been received.
62     @VisibleForTesting
63     public static final int COMMAND_RESETTING_DONE = 6;
64     // The ImsServiceController binder is connected.
65     private static final int COMMAND_CONNECTED_MSG = 7;
66     // The ImsServiceController binder is disconnected.
67     private static final int COMMAND_DISCONNECTED_MSG = 8;
68     // The subId is changed to INVALID_SUBSCRIPTION_ID.
69     private static final int COMMAND_INVALID_SUBID_MSG = 9;
70     // Indicate that the internal post reset message with delay has been received.
71     @VisibleForTesting
72     public static final int COMMAND_POST_RESETTING_DONE = 10;
73 
74     private static final Map<Integer, String> EVENT_DESCRIPTION = new HashMap<>();
75     static {
EVENT_DESCRIPTION.put(COMMAND_NONE_MSG, "COMMAND_NONE_MSG")76         EVENT_DESCRIPTION.put(COMMAND_NONE_MSG, "COMMAND_NONE_MSG");
EVENT_DESCRIPTION.put(COMMAND_ENABLE_MSG, "COMMAND_ENABLE_MSG")77         EVENT_DESCRIPTION.put(COMMAND_ENABLE_MSG, "COMMAND_ENABLE_MSG");
EVENT_DESCRIPTION.put(COMMAND_DISABLE_MSG, "COMMAND_DISABLE_MSG")78         EVENT_DESCRIPTION.put(COMMAND_DISABLE_MSG, "COMMAND_DISABLE_MSG");
EVENT_DESCRIPTION.put(COMMAND_RESET_MSG, "COMMAND_RESET_MSG")79         EVENT_DESCRIPTION.put(COMMAND_RESET_MSG, "COMMAND_RESET_MSG");
EVENT_DESCRIPTION.put(COMMAND_ENABLING_DONE, "COMMAND_ENABLING_DONE")80         EVENT_DESCRIPTION.put(COMMAND_ENABLING_DONE, "COMMAND_ENABLING_DONE");
EVENT_DESCRIPTION.put(COMMAND_DISABLING_DONE, "COMMAND_DISABLING_DONE")81         EVENT_DESCRIPTION.put(COMMAND_DISABLING_DONE, "COMMAND_DISABLING_DONE");
EVENT_DESCRIPTION.put(COMMAND_RESETTING_DONE, "COMMAND_RESETTING_DONE")82         EVENT_DESCRIPTION.put(COMMAND_RESETTING_DONE, "COMMAND_RESETTING_DONE");
EVENT_DESCRIPTION.put(COMMAND_CONNECTED_MSG, "COMMAND_CONNECTED_MSG")83         EVENT_DESCRIPTION.put(COMMAND_CONNECTED_MSG, "COMMAND_CONNECTED_MSG");
EVENT_DESCRIPTION.put(COMMAND_DISCONNECTED_MSG, "COMMAND_DISCONNECTED_MSG")84         EVENT_DESCRIPTION.put(COMMAND_DISCONNECTED_MSG, "COMMAND_DISCONNECTED_MSG");
EVENT_DESCRIPTION.put(COMMAND_INVALID_SUBID_MSG, "COMMAND_INVALID_SUBID_MSG")85         EVENT_DESCRIPTION.put(COMMAND_INVALID_SUBID_MSG, "COMMAND_INVALID_SUBID_MSG");
86     }
87 
88     @VisibleForTesting
89     protected static final int STATE_IMS_DISCONNECTED = 0;
90     @VisibleForTesting
91     protected static final int STATE_IMS_DEFAULT = 1;
92     @VisibleForTesting
93     protected static final int STATE_IMS_ENABLED = 2;
94     @VisibleForTesting
95     protected static final int STATE_IMS_DISABLING = 3;
96     @VisibleForTesting
97     protected static final int STATE_IMS_DISABLED = 4;
98     @VisibleForTesting
99     protected static final int STATE_IMS_ENABLING = 5;
100     @VisibleForTesting
101     protected static final int STATE_IMS_RESETTING = 6;
102 
103     @VisibleForTesting
104     protected static final int STATE_IMS_POSTRESETTING = 7;
105 
106     protected final Object mLock = new Object();
107     private IImsServiceController mIImsServiceController;
108     private long mLastImsOperationTimeMs = 0L;
109     private final ComponentName mComponentName;
110     private final SparseArray<ImsEnablementTrackerStateMachine> mStateMachines;
111 
112     private final Looper mLooper;
113     private final int mState;
114 
115     /**
116      * Provides Ims Enablement Tracker State Machine responsible for ims enable/disable/reset
117      * command interactions with Ims service controller binder.
118      * The enable/disable/reset ims commands have a time interval of at least
119      * {@link ImsEnablementTracker#REQUEST_THROTTLE_TIME_MS} second between
120      * processing each command.
121      * For example, the enableIms command is received and the binder's enableIms is called.
122      * After that, if the disableIms command is received, the binder's disableIms will be
123      * called after {@link ImsEnablementTracker#REQUEST_THROTTLE_TIME_MS} second.
124      * A time of {@link ImsEnablementTracker#REQUEST_THROTTLE_TIME_MS} will be used
125      * {@link Handler#sendMessageDelayed(Message, long)},
126      * and the enabled, disabled and reset states are responsible for waiting for
127      * that delay message.
128      */
129     class ImsEnablementTrackerStateMachine extends StateMachine {
130         /**
131          * The initial state of this class and waiting for an ims commands.
132          */
133         @VisibleForTesting
134         public final Default mDefault;
135 
136         /**
137          * Indicates that {@link IImsServiceController#enableIms(int, int)} has been called and
138          * waiting for an ims commands.
139          * Common transitions are to
140          * {@link #mDisabling} state when the disable command is received
141          * or {@link #mResetting} state when the reset command is received
142          * or {@link #mDisconnected} if the binder is disconnected.
143          */
144         @VisibleForTesting
145         public final Enabled mEnabled;
146 
147         /**
148          * Indicates that the state waiting for the throttle time to elapse before calling
149          * {@link IImsServiceController#disableIms(int, int)}.
150          * Common transitions are to
151          * {@link #mEnabled} when the enable command is received.
152          * or {@link #mResetting} when the reset command is received.
153          * or {@link #mDisabled} the previous binder API call has passed
154          * {@link ImsEnablementTracker#REQUEST_THROTTLE_TIME_MS} second, and if
155          * {@link IImsServiceController#disableIms(int, int)} called.
156          * or {@link #mDisabling} received a disableIms message and the previous binder API call
157          * has not passed {@link ImsEnablementTracker#REQUEST_THROTTLE_TIME_MS} second.
158          * Then send a disableIms message with delay.
159          * or {@link #mDisconnected} if the binder is disconnected.
160          */
161         @VisibleForTesting
162         public final Disabling mDisabling;
163 
164         /**
165          * Indicates that {@link IImsServiceController#disableIms(int, int)} has been called and
166          * waiting for an ims commands.
167          * Common transitions are to
168          * {@link #mEnabling} state when the enable command is received.
169          * or {@link #mDisconnected} if the binder is disconnected.
170          */
171         @VisibleForTesting
172         public final Disabled mDisabled;
173 
174         /**
175          * Indicates that the state waiting for the throttle time to elapse before calling
176          * {@link IImsServiceController#enableIms(int, int)}.
177          * Common transitions are to
178          * {@link #mEnabled} the previous binder API call has passed
179          * {@link ImsEnablementTracker#REQUEST_THROTTLE_TIME_MS} second, and
180          * {@link IImsServiceController#enableIms(int, int)} called.
181          * or {@link #mDisabled} when the disable command is received.
182          * or {@link #mEnabling} received an enableIms message and the previous binder API call
183          * has not passed {@link ImsEnablementTracker#REQUEST_THROTTLE_TIME_MS} second.
184          * Then send an enableIms message with delay.
185          * or {@link #mDisconnected} if the binder is disconnected.
186          */
187         @VisibleForTesting
188         public final Enabling mEnabling;
189 
190         /**
191          * Indicates that the state waiting for the throttle time to elapse before calling
192          * {@link IImsServiceController#resetIms(int, int)}.
193          * Common transitions are to
194          * {@link #mPostResetting} state to call either enableIms or disableIms after calling
195          * {@link IImsServiceController#resetIms(int, int)}
196          * or {@link #mDisconnected} if the binder is disconnected.
197          */
198         @VisibleForTesting
199         public final Resetting mResetting;
200 
201         /**
202          * Indicates that the state waiting after resetIms for the throttle time to elapse before
203          * calling {@link IImsServiceController#enableIms(int, int)} or
204          * {@link IImsServiceController#disableIms(int, int)}.
205          * Common transitions are to
206          * {@link #mEnabled} state when the disable command is received,
207          * {@link #mDisabled} state when the enable command is received after calling
208          * {@link IImsServiceController#enableIms(int, int)},
209          * {@link IImsServiceController#disableIms(int, int)}
210          * or {@link #mDisconnected} if the binder is disconnected.
211          */
212         public final PostResetting mPostResetting;
213 
214         /**
215          * Indicates that {@link IImsServiceController} has not been set.
216          * Common transition is to
217          * {@link #mDefault} state when the binder is set.
218          * or {@link #mDisabling} If the disable command is received while the binder is
219          * disconnected
220          * or {@link #mEnabling} If the enable command is received while the binder is
221          * disconnected
222          */
223 
224         private final Disconnected mDisconnected;
225         private int mSlotId;
226         private int mSubId;
227 
228         private final int mPhoneId;
229 
230         private IState mPreviousState;
231 
232         private int mLastMsg = COMMAND_NONE_MSG;
233 
ImsEnablementTrackerStateMachine(String name, Looper looper, int state, int slotId)234         ImsEnablementTrackerStateMachine(String name, Looper looper, int state, int slotId) {
235             super(name, looper);
236             mPhoneId = slotId;
237             mDefault = new Default();
238             mEnabled = new Enabled();
239             mDisabling = new Disabling();
240             mDisabled = new Disabled();
241             mEnabling = new Enabling();
242             mResetting = new Resetting();
243             mDisconnected = new Disconnected();
244             mPostResetting = new PostResetting();
245 
246             addState(mDefault);
247             addState(mEnabled);
248             addState(mDisabling);
249             addState(mDisabled);
250             addState(mEnabling);
251             addState(mResetting);
252             addState(mDisconnected);
253             addState(mPostResetting);
254 
255             setInitialState(getState(state));
256             mPreviousState = getState(state);
257         }
258 
clearAllMessage()259         public void clearAllMessage() {
260             Log.d(LOG_TAG, "clearAllMessage");
261             removeMessages(COMMAND_ENABLE_MSG);
262             removeMessages(COMMAND_DISABLE_MSG);
263             removeMessages(COMMAND_RESET_MSG);
264             removeMessages(COMMAND_ENABLING_DONE);
265             removeMessages(COMMAND_DISABLING_DONE);
266             removeMessages(COMMAND_RESETTING_DONE);
267             removeMessages(COMMAND_POST_RESETTING_DONE);
268         }
269 
serviceBinderConnected()270         public void serviceBinderConnected() {
271             clearAllMessage();
272             sendMessage(COMMAND_CONNECTED_MSG);
273         }
274 
serviceBinderDisconnected()275         public void serviceBinderDisconnected() {
276             clearAllMessage();
277             sendMessage(COMMAND_DISCONNECTED_MSG);
278         }
279 
280         @VisibleForTesting
isState(int state)281         public boolean isState(int state) {
282             State expect = null;
283             switch (state) {
284                 case Default.STATE_NO:
285                     expect = mDefault;
286                     break;
287                 case Enabled.STATE_NO:
288                     expect = mEnabled;
289                     break;
290                 case Disabling.STATE_NO:
291                     expect = mDisabling;
292                     break;
293                 case Disabled.STATE_NO:
294                     expect = mDisabled;
295                     break;
296                 case Enabling.STATE_NO:
297                     expect = mEnabling;
298                     break;
299                 case Resetting.STATE_NO:
300                     expect = mResetting;
301                     break;
302                 case Disconnected.STATE_NO:
303                     expect = mDisconnected;
304                     break;
305                 case PostResetting.STATE_NO:
306                     expect = mPostResetting;
307                     break;
308                 default:
309                     break;
310             }
311             return (getCurrentState() == expect) ? true : false;
312         }
313 
getState(int state)314         private State getState(int state) {
315             switch (state) {
316                 case ImsEnablementTracker.STATE_IMS_ENABLED:
317                     return mEnabled;
318                 case ImsEnablementTracker.STATE_IMS_DISABLING:
319                     return mDisabling;
320                 case ImsEnablementTracker.STATE_IMS_DISABLED:
321                     return mDisabled;
322                 case ImsEnablementTracker.STATE_IMS_ENABLING:
323                     return mEnabling;
324                 case ImsEnablementTracker.STATE_IMS_RESETTING:
325                     return mResetting;
326                 case ImsEnablementTracker.STATE_IMS_DISCONNECTED:
327                     return mDisconnected;
328                 case ImsEnablementTracker.STATE_IMS_POSTRESETTING:
329                     return mPostResetting;
330                 default:
331                     return mDefault;
332             }
333         }
334 
handleInvalidSubIdMessage()335         private void handleInvalidSubIdMessage() {
336             clearAllMessage();
337             transitionState(mDefault);
338         }
339 
transitionState(State state)340         private void transitionState(State state) {
341             mPreviousState = getCurrentState();
342             transitionTo(state);
343         }
344 
345         class Default extends State {
346             private static final int STATE_NO = STATE_IMS_DEFAULT;
347 
348             @Override
enter()349             public void enter() {
350                 Log.d(LOG_TAG, "[" + mPhoneId + "]Default state:enter");
351                 mSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
352             }
353 
354             @Override
processMessage(Message message)355             public boolean processMessage(Message message) {
356                 Log.d(LOG_TAG, "[" + mPhoneId + "]Default state:processMessage. msg.what="
357                         + EVENT_DESCRIPTION.get(message.what) + ",component:" + mComponentName);
358 
359                 switch (message.what) {
360                     // When enableIms() is called, enableIms of binder is call and the state
361                     // change to the enabled state.
362                     case COMMAND_ENABLE_MSG:
363                         sendEnableIms(message.arg1, message.arg2);
364                         transitionState(mEnabled);
365                         return HANDLED;
366                     // When disableIms() is called, disableIms of binder is call and the state
367                     // change to the disabled state.
368                     case COMMAND_DISABLE_MSG:
369                         sendDisableIms(message.arg1, message.arg2);
370                         transitionState(mDisabled);
371                         return HANDLED;
372                     // When resetIms() is called, change to the resetting state to call enableIms
373                     // after calling resetIms of binder.
374                     case COMMAND_RESET_MSG:
375                         mSlotId = message.arg1;
376                         mSubId = message.arg2;
377                         transitionState(mResetting);
378                         return HANDLED;
379                     case COMMAND_DISCONNECTED_MSG:
380                         transitionState(mDisconnected);
381                         return HANDLED;
382                     default:
383                         return NOT_HANDLED;
384                 }
385             }
386         }
387 
388         class Enabled extends State {
389             private static final int STATE_NO = STATE_IMS_ENABLED;
390 
391             @Override
enter()392             public void enter() {
393                 Log.d(LOG_TAG, "[" + mPhoneId + "]Enabled state:enter");
394             }
395 
396             @Override
processMessage(Message message)397             public boolean processMessage(Message message) {
398                 Log.d(LOG_TAG, "[" + mPhoneId + "]Enabled state:processMessage. msg.what="
399                         + EVENT_DESCRIPTION.get(message.what) + ",component:" + mComponentName);
400 
401                 switch (message.what) {
402                     // the disableIms() is called.
403                     case COMMAND_DISABLE_MSG:
404                         mSlotId = message.arg1;
405                         mSubId = message.arg2;
406                         transitionState(mDisabling);
407                         return HANDLED;
408                     // the resetIms() is called.
409                     case COMMAND_RESET_MSG:
410                         mSlotId = message.arg1;
411                         mSubId = message.arg2;
412                         transitionState(mResetting);
413                         return HANDLED;
414                     case COMMAND_DISCONNECTED_MSG:
415                         transitionState(mDisconnected);
416                         return HANDLED;
417                     case COMMAND_INVALID_SUBID_MSG:
418                         handleInvalidSubIdMessage();
419                         return HANDLED;
420                     default:
421                         return NOT_HANDLED;
422                 }
423             }
424         }
425 
426         class Disabling extends State {
427             private static final int STATE_NO = STATE_IMS_DISABLING;
428 
429             @Override
enter()430             public void enter() {
431                 Log.d(LOG_TAG, "[" + mPhoneId + "]Disabling state:enter");
432                 sendMessageDelayed(COMMAND_DISABLING_DONE, mSlotId, mSubId,
433                         getRemainThrottleTime());
434             }
435 
436             @Override
processMessage(Message message)437             public boolean processMessage(Message message) {
438                 Log.d(LOG_TAG, "[" + mPhoneId + "]Disabling state:processMessage. msg.what="
439                         + EVENT_DESCRIPTION.get(message.what) + ",component:" + mComponentName);
440 
441                 switch (message.what) {
442                     case COMMAND_ENABLE_MSG:
443                         mSlotId = message.arg1;
444                         mSubId = message.arg2;
445                         clearAllMessage();
446                         if (mPreviousState == mResetting) {
447                             // if we are moving from Resetting -> Disabling and receive
448                             // the COMMAND_ENABLE_MSG, we need to send the enableIms command,
449                             // so move to Enabling state.
450                             transitionState(mEnabling);
451                         } else {
452                             // When moving from Enabled -> Disabling and we receive an ENABLE_MSG,
453                             // we can move straight back to Enabled state because we have not sent
454                             // the disableIms command to IMS yet.
455                             transitionState(mEnabled);
456                         }
457                         return HANDLED;
458                     case COMMAND_DISABLING_DONE:
459                         // If the disable command is received before disableIms is processed,
460                         // it will be ignored because the disable command processing is in progress.
461                         removeMessages(COMMAND_DISABLE_MSG);
462                         sendDisableIms(message.arg1, message.arg2);
463                         transitionState(mDisabled);
464                         return HANDLED;
465                     case COMMAND_RESET_MSG:
466                         mSlotId = message.arg1;
467                         mSubId = message.arg2;
468                         clearAllMessage();
469                         transitionState(mResetting);
470                         return HANDLED;
471                     case COMMAND_DISCONNECTED_MSG:
472                         transitionState(mDisconnected);
473                         return HANDLED;
474                     case COMMAND_INVALID_SUBID_MSG:
475                         handleInvalidSubIdMessage();
476                         return HANDLED;
477                     default:
478                         return NOT_HANDLED;
479                 }
480             }
481         }
482 
483         class Disabled extends State {
484             private static final int STATE_NO = STATE_IMS_DISABLED;
485 
486             @Override
enter()487             public void enter() {
488                 Log.d(LOG_TAG, "[" + mPhoneId + "]Disabled state:enter");
489             }
490 
491             @Override
processMessage(Message message)492             public boolean processMessage(Message message) {
493                 Log.d(LOG_TAG, "[" + mPhoneId + "]Disabled state:processMessage. msg.what="
494                         + EVENT_DESCRIPTION.get(message.what) + ",component:" + mComponentName);
495 
496                 switch (message.what) {
497                     case COMMAND_ENABLE_MSG:
498                         mSlotId = message.arg1;
499                         mSubId = message.arg2;
500                         transitionState(mEnabling);
501                         return HANDLED;
502                     case COMMAND_RESET_MSG:
503                         mSlotId = message.arg1;
504                         mSubId = message.arg2;
505                         transitionState(mResetting);
506                         return HANDLED;
507                     case COMMAND_DISCONNECTED_MSG:
508                         transitionState(mDisconnected);
509                         return HANDLED;
510                     case COMMAND_INVALID_SUBID_MSG:
511                         handleInvalidSubIdMessage();
512                         return HANDLED;
513                     default:
514                         return NOT_HANDLED;
515                 }
516             }
517         }
518 
519         class Enabling extends State {
520             private static final int STATE_NO = STATE_IMS_ENABLING;
521 
522             @Override
enter()523             public void enter() {
524                 Log.d(LOG_TAG, "[" + mPhoneId + "]Enabling state:enter");
525                 sendMessageDelayed(COMMAND_ENABLING_DONE, mSlotId, mSubId, getRemainThrottleTime());
526             }
527 
528             @Override
processMessage(Message message)529             public boolean processMessage(Message message) {
530                 Log.d(LOG_TAG, "[" + mPhoneId + "]Enabling state:processMessage. msg.what="
531                         + EVENT_DESCRIPTION.get(message.what) + ",component:" + mComponentName);
532 
533                 switch (message.what) {
534                     // Enabling state comes from Resetting and disableIms() is called.
535                     // In this case disableIms() of binder should be called.
536                     // When enabling state comes from disabled, just change state to the disabled.
537                     case COMMAND_DISABLE_MSG:
538                         mSlotId = message.arg1;
539                         mSubId = message.arg2;
540                         clearAllMessage();
541                         if (mPreviousState == mResetting) {
542                             transitionState(mDisabling);
543                         } else {
544                             transitionState(mDisabled);
545                         }
546                         return HANDLED;
547                     case COMMAND_RESET_MSG:
548                         mSlotId = message.arg1;
549                         mSubId = message.arg2;
550                         transitionState(mResetting);
551                         return HANDLED;
552                     case COMMAND_ENABLING_DONE:
553                         // If the enable command is received before enableIms is processed,
554                         // it will be ignored because the enable command processing is in progress.
555                         removeMessages(COMMAND_ENABLE_MSG);
556                         sendEnableIms(message.arg1, message.arg2);
557                         transitionState(mEnabled);
558                         return HANDLED;
559                     case COMMAND_DISCONNECTED_MSG:
560                         transitionState(mDisconnected);
561                         return HANDLED;
562                     case COMMAND_INVALID_SUBID_MSG:
563                         handleInvalidSubIdMessage();
564                         return HANDLED;
565                     default:
566                         return NOT_HANDLED;
567                 }
568             }
569         }
570 
571         class Resetting extends State {
572             private static final int STATE_NO = STATE_IMS_RESETTING;
573 
574             @Override
enter()575             public void enter() {
576                 Log.d(LOG_TAG, "[" + mPhoneId + "]Resetting state:enter");
577                 sendMessageDelayed(COMMAND_RESETTING_DONE, mSlotId, mSubId,
578                         getRemainThrottleTime());
579             }
580 
581             @Override
processMessage(Message message)582             public boolean processMessage(Message message) {
583                 Log.d(LOG_TAG, "[" + mPhoneId + "]Resetting state:processMessage. msg.what="
584                         + EVENT_DESCRIPTION.get(message.what) + ",component:" + mComponentName);
585 
586                 switch (message.what) {
587                     case COMMAND_DISABLE_MSG:
588                         mLastMsg = COMMAND_DISABLE_MSG;
589                         return HANDLED;
590                     case COMMAND_ENABLE_MSG:
591                         mLastMsg = COMMAND_ENABLE_MSG;
592                         return HANDLED;
593                     case COMMAND_RESETTING_DONE:
594                         mSlotId = message.arg1;
595                         mSubId = message.arg2;
596                         // If the reset command is received before disableIms is processed,
597                         // it will be ignored because the reset command processing is in progress.
598                         removeMessages(COMMAND_RESET_MSG);
599                         sendResetIms(mSlotId, mSubId);
600                         transitionState(mPostResetting);
601                         return HANDLED;
602                     case COMMAND_DISCONNECTED_MSG:
603                         transitionState(mDisconnected);
604                         return HANDLED;
605                     case COMMAND_INVALID_SUBID_MSG:
606                         handleInvalidSubIdMessage();
607                         return HANDLED;
608                     default:
609                         return NOT_HANDLED;
610                 }
611             }
612         }
613 
614         class Disconnected extends State {
615             private static final int STATE_NO = STATE_IMS_DISCONNECTED;
616 
617             private int mLastMsg = COMMAND_NONE_MSG;
618 
619             @Override
enter()620             public void enter() {
621                 Log.d(LOG_TAG, "[" + mPhoneId + "]Disconnected state:enter");
622                 clearAllMessage();
623             }
624 
625             @Override
processMessage(Message message)626             public boolean processMessage(Message message) {
627                 Log.d(LOG_TAG, "[" + mPhoneId + "]Disconnected state:processMessage. msg.what="
628                         + EVENT_DESCRIPTION.get(message.what) + ",component:" + mComponentName);
629 
630                 switch (message.what) {
631                     case COMMAND_CONNECTED_MSG:
632                         clearAllMessage();
633                         transitionState(mDefault);
634                         if (mLastMsg != COMMAND_NONE_MSG) {
635                             sendMessageDelayed(mLastMsg, mSlotId, mSubId, 0);
636                             mLastMsg = COMMAND_NONE_MSG;
637                         }
638                         return HANDLED;
639                     case COMMAND_ENABLE_MSG:
640                     case COMMAND_DISABLE_MSG:
641                     case COMMAND_RESET_MSG:
642                         mLastMsg = message.what;
643                         mSlotId = message.arg1;
644                         mSubId = message.arg2;
645                         return HANDLED;
646                     default:
647                         return NOT_HANDLED;
648                 }
649             }
650         }
651 
652         class PostResetting extends State {
653             private static final int STATE_NO = STATE_IMS_POSTRESETTING;
654 
655             @Override
enter()656             public void enter() {
657                 Log.d(LOG_TAG, "[" + mPhoneId + "]PostResetting state:enter");
658                 sendMessageDelayed(COMMAND_POST_RESETTING_DONE, mSlotId, mSubId,
659                         getRemainThrottleTime());
660             }
661 
662             @Override
exit()663             public void exit() {
664                 mLastMsg = COMMAND_NONE_MSG;
665             }
666 
667             @Override
processMessage(Message message)668             public boolean processMessage(Message message) {
669                 Log.d(LOG_TAG, "[" + mPhoneId + "]PostResetting state:processMessage. msg.what="
670                         + EVENT_DESCRIPTION.get(message.what) + ",component:" + mComponentName);
671 
672                 switch (message.what) {
673                     case COMMAND_POST_RESETTING_DONE:
674                         mSlotId = message.arg1;
675                         mSubId = message.arg2;
676                         if (mLastMsg == COMMAND_DISABLE_MSG) {
677                             sendDisableIms(mSlotId, mSubId);
678                             transitionState(mDisabled);
679                         } else {
680                             // if mLastMsg is COMMAND_NONE_MSG or COMMAND_ENABLE_MSG
681                             sendEnableIms(mSlotId, mSubId);
682                             transitionState(mEnabled);
683                         }
684                         return HANDLED;
685                     case COMMAND_ENABLE_MSG:
686                     case COMMAND_DISABLE_MSG:
687                         mLastMsg = message.what;
688                         mSlotId = message.arg1;
689                         mSubId = message.arg2;
690                         return HANDLED;
691                     case COMMAND_RESET_MSG:
692                         // when resetIms() called again, skip to call
693                         // IImsServiceController.resetIms(slotId, subId), but after throttle time
694                         // IImsServiceController.enableIms(slotId, subId) should be called.
695                         mLastMsg = COMMAND_ENABLE_MSG;
696                         mSlotId = message.arg1;
697                         mSubId = message.arg2;
698                         return HANDLED;
699                     case COMMAND_DISCONNECTED_MSG:
700                         transitionState(mDisconnected);
701                         return HANDLED;
702                     case COMMAND_INVALID_SUBID_MSG:
703                         handleInvalidSubIdMessage();
704                         return HANDLED;
705                     default:
706                         return NOT_HANDLED;
707                 }
708             }
709         }
710     }
711 
ImsEnablementTracker(Looper looper, ComponentName componentName)712     public ImsEnablementTracker(Looper looper, ComponentName componentName) {
713         mIImsServiceController = null;
714         mStateMachines = new SparseArray<>();
715         mLooper = looper;
716         mState = ImsEnablementTracker.STATE_IMS_DISCONNECTED;
717         mComponentName = componentName;
718     }
719 
720     @VisibleForTesting
ImsEnablementTracker(Looper looper, IImsServiceController controller, int state, int numSlots)721     public ImsEnablementTracker(Looper looper, IImsServiceController controller, int state,
722             int numSlots) {
723         mIImsServiceController = controller;
724         mStateMachines = new SparseArray<>();
725         mLooper = looper;
726         mState = state;
727         mComponentName = null;
728 
729         setNumOfSlots(numSlots);
730     }
731 
732     /**
733      * Set the number of SIM slots.
734      * @param numOfSlots the number of SIM slots.
735      */
setNumOfSlots(int numOfSlots)736     public void setNumOfSlots(int numOfSlots) {
737         int oldNumSlots = mStateMachines.size();
738         Log.d(LOG_TAG, "set the slots: old[" + oldNumSlots + "], new[" + numOfSlots + "],"
739                 + "component:" + mComponentName);
740         if (numOfSlots == oldNumSlots) {
741             return;
742         }
743         ImsEnablementTrackerStateMachine enablementStateMachine = null;
744         if (oldNumSlots < numOfSlots) {
745             for (int i = oldNumSlots; i < numOfSlots; i++) {
746                 enablementStateMachine = new ImsEnablementTrackerStateMachine(
747                         "ImsEnablementTracker", mLooper, mState, i);
748                 enablementStateMachine.start();
749                 mStateMachines.put(i, enablementStateMachine);
750             }
751         } else if (oldNumSlots > numOfSlots) {
752             for (int i = (oldNumSlots - 1); i > (numOfSlots - 1); i--) {
753                 enablementStateMachine = mStateMachines.get(i);
754                 mStateMachines.remove(i);
755                 enablementStateMachine.quitNow();
756             }
757         }
758     }
759 
760     @VisibleForTesting
getHandler(int slotId)761     public Handler getHandler(int slotId) {
762         return mStateMachines.get(slotId).getHandler();
763     }
764 
765     /**
766      * Check that the current state and the input state are the same.
767      * @param state the input state.
768      * @return true if the current state and input state are the same or false.
769      */
770     @VisibleForTesting
isState(int slotId, int state)771     public boolean isState(int slotId, int state) {
772         return mStateMachines.get(slotId).isState(state);
773     }
774 
775     /**
776      * Notify the state machine that the subId has changed to invalid.
777      * @param slotId subscription id
778      */
subIdChangedToInvalid(int slotId)779     public void subIdChangedToInvalid(int slotId) {
780         Log.d(LOG_TAG, "[" + slotId + "] subId changed to invalid, component:" + mComponentName);
781         ImsEnablementTrackerStateMachine stateMachine = mStateMachines.get(slotId);
782         if (stateMachine != null) {
783             stateMachine.sendMessage(COMMAND_INVALID_SUBID_MSG, slotId);
784         } else {
785             Log.w(LOG_TAG, "There is no state machine associated with this slotId.");
786         }
787     }
788 
789     /**
790      * Notify ImsService to enable IMS for the framework. This will trigger IMS registration and
791      * trigger ImsFeature status updates.
792      * @param slotId slot id
793      * @param subId subscription id
794      */
enableIms(int slotId, int subId)795     public void enableIms(int slotId, int subId) {
796         Log.d(LOG_TAG, "[" + slotId + "][" + subId + "]enableIms, component:" + mComponentName);
797         ImsEnablementTrackerStateMachine stateMachine = mStateMachines.get(slotId);
798         if (stateMachine != null) {
799             stateMachine.sendMessage(COMMAND_ENABLE_MSG, slotId, subId);
800         } else {
801             Log.w(LOG_TAG, "There is no state machine associated with this slotId.");
802         }
803     }
804 
805     /**
806      * Notify ImsService to disable IMS for the framework. This will trigger IMS de-registration and
807      * trigger ImsFeature capability status to become false.
808      * @param slotId slot id
809      * @param subId subscription id
810      */
disableIms(int slotId, int subId)811     public void disableIms(int slotId, int subId) {
812         Log.d(LOG_TAG, "[" + slotId + "][" + subId + "]disableIms, component:" + mComponentName);
813         ImsEnablementTrackerStateMachine stateMachine = mStateMachines.get(slotId);
814         if (stateMachine != null) {
815             stateMachine.sendMessage(COMMAND_DISABLE_MSG, slotId, subId);
816         } else {
817             Log.w(LOG_TAG, "There is no state machine associated with this slotId.");
818         }
819     }
820 
821     /**
822      * Notify ImsService to reset IMS for the framework. This will trigger ImsService to perform
823      * de-registration and release all resource. After that, if enaleIms is called, the ImsService
824      * performs registration and appropriate initialization to bring up all ImsFeatures.
825      * @param slotId slot id
826      * @param subId subscription id
827      */
resetIms(int slotId, int subId)828     public void resetIms(int slotId, int subId) {
829         Log.d(LOG_TAG, "[" + slotId + "][" + subId + "]resetIms, component:" + mComponentName);
830         ImsEnablementTrackerStateMachine stateMachine = mStateMachines.get(slotId);
831         if (stateMachine != null) {
832             stateMachine.sendMessage(COMMAND_RESET_MSG, slotId, subId);
833         } else {
834             Log.w(LOG_TAG, "There is no state machine associated with this slotId.");
835         }
836     }
837 
838     /**
839      * Sets the IImsServiceController instance.
840      */
setServiceController(IBinder serviceController)841     protected void setServiceController(IBinder serviceController) {
842         synchronized (mLock) {
843             mIImsServiceController = IImsServiceController.Stub.asInterface(serviceController);
844             Log.d(LOG_TAG, "setServiceController with Binder:" + mIImsServiceController
845                     + ", component:" + mComponentName);
846             ImsEnablementTrackerStateMachine stateMachine = null;
847             for (int i = 0; i < mStateMachines.size(); i++) {
848                 stateMachine = mStateMachines.get(i);
849                 if (stateMachine == null) {
850                     Log.w(LOG_TAG, "There is no state machine associated with"
851                             + "the slotId[" + i + "]");
852                     continue;
853                 }
854                 if (isServiceControllerAvailable()) {
855                     stateMachine.serviceBinderConnected();
856                 } else {
857                     stateMachine.serviceBinderDisconnected();
858                 }
859             }
860         }
861     }
862 
getLastOperationTimeMillis()863     protected long getLastOperationTimeMillis() {
864         return mLastImsOperationTimeMs;
865     }
866 
867     /**
868      * Get remaining throttle time value
869      * @return remaining throttle time value
870      */
871     @VisibleForTesting
getRemainThrottleTime()872     public long getRemainThrottleTime() {
873         long remainTime = REQUEST_THROTTLE_TIME_MS - (System.currentTimeMillis()
874                 - getLastOperationTimeMillis());
875 
876         if (remainTime < 0) {
877             remainTime = 0L;
878         }
879         Log.d(LOG_TAG, "getRemainThrottleTime:" + remainTime);
880 
881         return remainTime;
882     }
883 
884     /**
885      * Check to see if the service controller is available.
886      * @return true if available, false otherwise
887      */
isServiceControllerAvailable()888     private boolean isServiceControllerAvailable() {
889         if (mIImsServiceController != null) {
890             return true;
891         }
892         Log.d(LOG_TAG, "isServiceControllerAvailable : binder is not alive");
893         return false;
894     }
895 
sendEnableIms(int slotId, int subId)896     private void sendEnableIms(int slotId, int subId) {
897         try {
898             synchronized (mLock) {
899                 if (isServiceControllerAvailable()) {
900                     Log.d(LOG_TAG, "[" + slotId + "][" + subId + "]sendEnableIms,"
901                             + "componentName[" + mComponentName + "]");
902                     mIImsServiceController.enableIms(slotId, subId);
903                     mLastImsOperationTimeMs = System.currentTimeMillis();
904                 }
905             }
906         } catch (RemoteException e) {
907             Log.w(LOG_TAG, "Couldn't enable IMS: " + e.getMessage());
908         }
909     }
910 
sendDisableIms(int slotId, int subId)911     private void sendDisableIms(int slotId, int subId) {
912         try {
913             synchronized (mLock) {
914                 if (isServiceControllerAvailable()) {
915                     Log.d(LOG_TAG, "[" + slotId + "][" + subId + "]sendDisableIms"
916                             + " componentName[" + mComponentName + "]");
917                     mIImsServiceController.disableIms(slotId, subId);
918                     mLastImsOperationTimeMs = System.currentTimeMillis();
919                 }
920             }
921         } catch (RemoteException e) {
922             Log.w(LOG_TAG, "Couldn't disable IMS: " + e.getMessage());
923         }
924     }
925 
sendResetIms(int slotId, int subId)926     private void sendResetIms(int slotId, int subId) {
927         try {
928             synchronized (mLock) {
929                 if (isServiceControllerAvailable()) {
930                     Log.d(LOG_TAG, "[" + slotId + "][" + subId + "]sendResetIms");
931                     mIImsServiceController.resetIms(slotId, subId);
932                     mLastImsOperationTimeMs = System.currentTimeMillis();
933                 }
934             }
935         } catch (RemoteException e) {
936             Log.w(LOG_TAG, "Couldn't reset IMS: " + e.getMessage());
937         }
938     }
939 }
940