1 /*
2  * Copyright (C) 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 android.telephony.ims.stub;
18 
19 import android.annotation.IntDef;
20 import android.annotation.NonNull;
21 import android.annotation.SystemApi;
22 import android.content.Context;
23 import android.os.PersistableBundle;
24 import android.os.RemoteException;
25 import android.telephony.ims.ImsService;
26 import android.telephony.ims.ProvisioningManager;
27 import android.telephony.ims.RcsClientConfiguration;
28 import android.telephony.ims.RcsConfig;
29 import android.telephony.ims.aidl.IImsConfig;
30 import android.telephony.ims.aidl.IImsConfigCallback;
31 import android.telephony.ims.aidl.IRcsConfigCallback;
32 import android.util.Log;
33 
34 import com.android.ims.ImsConfig;
35 import com.android.internal.annotations.VisibleForTesting;
36 import com.android.internal.telephony.util.RemoteCallbackListExt;
37 import com.android.internal.telephony.util.TelephonyUtils;
38 
39 import java.lang.annotation.Retention;
40 import java.lang.annotation.RetentionPolicy;
41 import java.lang.ref.WeakReference;
42 import java.util.Arrays;
43 import java.util.HashMap;
44 import java.util.concurrent.CancellationException;
45 import java.util.concurrent.CompletableFuture;
46 import java.util.concurrent.CompletionException;
47 import java.util.concurrent.ExecutionException;
48 import java.util.concurrent.Executor;
49 import java.util.concurrent.atomic.AtomicReference;
50 import java.util.function.Supplier;
51 
52 
53 /**
54  * Controls the modification of IMS specific configurations. For more information on the supported
55  * IMS configuration constants, see {@link ImsConfig}.
56  *
57  * The inner class {@link ImsConfigStub} implements methods of IImsConfig AIDL interface.
58  * The IImsConfig AIDL interface is called by ImsConfig, which may exist in many other processes.
59  * ImsConfigImpl access to the configuration parameters may be arbitrarily slow, especially in
60  * during initialization, or times when a lot of configuration parameters are being set/get
61  * (such as during boot up or SIM card change). By providing a cache in ImsConfigStub, we can speed
62  * up access to these configuration parameters, so a query to the ImsConfigImpl does not have to be
63  * performed every time.
64  * @hide
65  */
66 @SystemApi
67 public class ImsConfigImplBase {
68 
69     private static final String TAG = "ImsConfigImplBase";
70 
71     /**
72      * Implements the IImsConfig AIDL interface, which is called by potentially many processes
73      * in order to get/set configuration parameters.
74      *
75      * It holds an object of ImsConfigImplBase class which is usually extended by ImsConfigImpl
76      * with actual implementations from vendors. This class caches provisioned values from
77      * ImsConfigImpl layer because queries through ImsConfigImpl can be slow. When query goes in,
78      * it first checks cache layer. If missed, it will call the vendor implementation of
79      * ImsConfigImplBase API.
80      * and cache the return value if the set succeeds.
81      *
82      * Provides APIs to get/set the IMS service feature/capability/parameters.
83      * The config items include:
84      * 1) Items provisioned by the operator.
85      * 2) Items configured by user. Mainly service feature class.
86      *
87      * @hide
88      */
89     @VisibleForTesting
90     static public class ImsConfigStub extends IImsConfig.Stub {
91         WeakReference<ImsConfigImplBase> mImsConfigImplBaseWeakReference;
92         private HashMap<Integer, Integer> mProvisionedIntValue = new HashMap<>();
93         private HashMap<Integer, String> mProvisionedStringValue = new HashMap<>();
94         private final Object mLock = new Object();
95         private Executor mExecutor;
96 
97         @VisibleForTesting
ImsConfigStub(ImsConfigImplBase imsConfigImplBase, Executor executor)98         public ImsConfigStub(ImsConfigImplBase imsConfigImplBase, Executor executor) {
99             mExecutor = executor;
100             mImsConfigImplBaseWeakReference =
101                     new WeakReference<ImsConfigImplBase>(imsConfigImplBase);
102         }
103 
104         @Override
addImsConfigCallback(IImsConfigCallback c)105         public void addImsConfigCallback(IImsConfigCallback c) throws RemoteException {
106             AtomicReference<RemoteException> exceptionRef = new AtomicReference<>();
107             executeMethodAsync(()-> {
108                 try {
109                     getImsConfigImpl().addImsConfigCallback(c);
110                 } catch (RemoteException e) {
111                     exceptionRef.set(e);
112                 }
113             }, "addImsConfigCallback");
114 
115             if (exceptionRef.get() != null) {
116                 Log.d(TAG, "ImsConfigImplBase Exception addImsConfigCallback");
117                 throw exceptionRef.get();
118             }
119         }
120 
121         @Override
removeImsConfigCallback(IImsConfigCallback c)122         public void removeImsConfigCallback(IImsConfigCallback c) throws RemoteException {
123             AtomicReference<RemoteException> exceptionRef = new AtomicReference<>();
124             executeMethodAsync(()-> {
125                 try {
126                     getImsConfigImpl().removeImsConfigCallback(c);
127                 } catch (RemoteException e) {
128                     exceptionRef.set(e);
129                 }
130             }, "removeImsConfigCallback");
131 
132             if (exceptionRef.get() != null) {
133                 Log.d(TAG, "ImsConfigImplBase Exception removeImsConfigCallback");
134                 throw exceptionRef.get();
135             }
136         }
137 
138         /**
139          * Gets the value for ims service/capabilities parameters. It first checks its local cache,
140          * if missed, it will call ImsConfigImplBase.getConfigInt.
141          * Synchronous blocking call.
142          *
143          * @param item integer key
144          * @return value in Integer format or {@link #CONFIG_RESULT_UNKNOWN} if
145          * unavailable.
146          */
147         @Override
getConfigInt(int item)148         public int getConfigInt(int item) throws RemoteException {
149             AtomicReference<RemoteException> exceptionRef = new AtomicReference<>();
150             int retVal = executeMethodAsyncForResult(()-> {
151                 int returnVal = ImsConfig.OperationStatusConstants.UNKNOWN;
152                 synchronized (mLock) {
153                     if (mProvisionedIntValue.containsKey(item)) {
154                         return mProvisionedIntValue.get(item);
155                     } else {
156                         try {
157                             returnVal = getImsConfigImpl().getConfigInt(item);
158                             if (returnVal != ImsConfig.OperationStatusConstants.UNKNOWN) {
159                                 mProvisionedIntValue.put(item, returnVal);
160                             }
161                         } catch (RemoteException e) {
162                             exceptionRef.set(e);
163                             return returnVal;
164                         }
165                     }
166                 }
167                 return returnVal;
168             }, "getConfigInt");
169 
170             if (exceptionRef.get() != null) {
171                 Log.d(TAG, "ImsConfigImplBase Exception getConfigString");
172                 throw exceptionRef.get();
173             }
174 
175             return retVal;
176         }
177 
178         /**
179          * Gets the value for ims service/capabilities parameters. It first checks its local cache,
180          * if missed, it will call #ImsConfigImplBase.getConfigString.
181          * Synchronous blocking call.
182          *
183          * @param item integer key
184          * @return value in String format.
185          */
186         @Override
getConfigString(int item)187         public String getConfigString(int item) throws RemoteException {
188             AtomicReference<RemoteException> exceptionRef = new AtomicReference<>();
189             String retVal = executeMethodAsyncForResult(()-> {
190                 String returnVal = null;
191                 synchronized (mLock) {
192                     if (mProvisionedStringValue.containsKey(item)) {
193                         returnVal = mProvisionedStringValue.get(item);
194                     } else {
195                         try {
196                             returnVal = getImsConfigImpl().getConfigString(item);
197                             if (returnVal != null) {
198                                 mProvisionedStringValue.put(item, returnVal);
199                             }
200                         } catch (RemoteException e) {
201                             exceptionRef.set(e);
202                             return returnVal;
203                         }
204                     }
205                 }
206                 return returnVal;
207             }, "getConfigString");
208 
209             if (exceptionRef.get() != null) {
210                 Log.d(TAG, "ImsConfigImplBase Exception getConfigString");
211                 throw exceptionRef.get();
212             }
213 
214             return retVal;
215         }
216 
217         /**
218          * Sets the value for IMS service/capabilities parameters by the operator device
219          * management entity. It sets the config item value in the provisioned storage
220          * from which the main value is derived, and write it into local cache.
221          * Synchronous blocking call.
222          *
223          * @param item integer key
224          * @param value in Integer format.
225          * @return the result of setting the configuration value, defined as either
226          * {@link #CONFIG_RESULT_FAILED} or {@link #CONFIG_RESULT_SUCCESS}.
227          */
228         @Override
setConfigInt(int item, int value)229         public int setConfigInt(int item, int value) throws RemoteException {
230             AtomicReference<RemoteException> exceptionRef = new AtomicReference<>();
231             int retVal = executeMethodAsyncForResult(()-> {
232                 int returnVal = ImsConfig.OperationStatusConstants.UNKNOWN;
233                 try {
234                     synchronized (mLock) {
235                         mProvisionedIntValue.remove(item);
236                         returnVal = getImsConfigImpl().setConfig(item, value);
237                         if (returnVal == ImsConfig.OperationStatusConstants.SUCCESS) {
238                             mProvisionedIntValue.put(item, value);
239                         } else {
240                             Log.d(TAG, "Set provision value of " + item
241                                     + " to " + value + " failed with error code " + returnVal);
242                         }
243                     }
244                     notifyImsConfigChanged(item, value);
245                     return returnVal;
246                 } catch (RemoteException e) {
247                     exceptionRef.set(e);
248                     return returnVal;
249                 }
250             }, "setConfigInt");
251 
252             if (exceptionRef.get() != null) {
253                 Log.d(TAG, "ImsConfigImplBase Exception setConfigInt");
254                 throw exceptionRef.get();
255             }
256 
257             return retVal;
258         }
259 
260         /**
261          * Sets the value for IMS service/capabilities parameters by the operator device
262          * management entity. It sets the config item value in the provisioned storage
263          * from which the main value is derived, and write it into local cache.
264          * Synchronous blocking call.
265          *
266          * @param item as defined in com.android.ims.ImsConfig#ConfigConstants.
267          * @param value in String format.
268          * @return the result of setting the configuration value, defined as either
269          * {@link #CONFIG_RESULT_FAILED} or {@link #CONFIG_RESULT_SUCCESS}.
270          */
271         @Override
setConfigString(int item, String value)272         public int setConfigString(int item, String value)
273                 throws RemoteException {
274             AtomicReference<RemoteException> exceptionRef = new AtomicReference<>();
275             int retVal = executeMethodAsyncForResult(()-> {
276                 int returnVal = ImsConfig.OperationStatusConstants.UNKNOWN;
277                 try {
278                     synchronized (mLock) {
279                         mProvisionedStringValue.remove(item);
280                         returnVal = getImsConfigImpl().setConfig(item, value);
281                         if (returnVal == ImsConfig.OperationStatusConstants.SUCCESS) {
282                             mProvisionedStringValue.put(item, value);
283                         }
284                     }
285                     notifyImsConfigChanged(item, value);
286                     return returnVal;
287                 } catch (RemoteException e) {
288                     exceptionRef.set(e);
289                     return returnVal;
290                 }
291             }, "setConfigString");
292 
293             if (exceptionRef.get() != null) {
294                 Log.d(TAG, "ImsConfigImplBase Exception setConfigInt");
295                 throw exceptionRef.get();
296             }
297 
298             return retVal;
299         }
300 
301         @Override
updateImsCarrierConfigs(PersistableBundle bundle)302         public void updateImsCarrierConfigs(PersistableBundle bundle) throws RemoteException {
303             AtomicReference<RemoteException> exceptionRef = new AtomicReference<>();
304             executeMethodAsync(()-> {
305                 try {
306                     getImsConfigImpl().updateImsCarrierConfigs(bundle);
307                 } catch (RemoteException e) {
308                     exceptionRef.set(e);
309                 }
310             }, "updateImsCarrierConfigs");
311 
312             if (exceptionRef.get() != null) {
313                 Log.d(TAG, "ImsConfigImplBase Exception updateImsCarrierConfigs");
314                 throw exceptionRef.get();
315             }
316         }
317 
getImsConfigImpl()318         private ImsConfigImplBase getImsConfigImpl() throws RemoteException {
319             ImsConfigImplBase ref = mImsConfigImplBaseWeakReference.get();
320             if (ref == null) {
321                 throw new RemoteException("Fail to get ImsConfigImpl");
322             } else {
323                 return ref;
324             }
325         }
326 
327         @Override
notifyRcsAutoConfigurationReceived(byte[] config, boolean isCompressed)328         public void notifyRcsAutoConfigurationReceived(byte[] config, boolean isCompressed)
329                 throws RemoteException {
330             AtomicReference<RemoteException> exceptionRef = new AtomicReference<>();
331             executeMethodAsync(()-> {
332                 try {
333                     getImsConfigImpl().onNotifyRcsAutoConfigurationReceived(config, isCompressed);
334                 } catch (RemoteException e) {
335                     exceptionRef.set(e);
336                 }
337             }, "notifyRcsAutoConfigurationReceived");
338 
339             if (exceptionRef.get() != null) {
340                 Log.d(TAG, "ImsConfigImplBase Exception notifyRcsAutoConfigurationReceived");
341                 throw exceptionRef.get();
342             }
343         }
344 
345         @Override
notifyRcsAutoConfigurationRemoved()346         public void notifyRcsAutoConfigurationRemoved()
347                 throws RemoteException {
348             AtomicReference<RemoteException> exceptionRef = new AtomicReference<>();
349             executeMethodAsync(()-> {
350                 try {
351                     getImsConfigImpl().onNotifyRcsAutoConfigurationRemoved();
352                 } catch (RemoteException e) {
353                     exceptionRef.set(e);
354                 }
355             }, "notifyRcsAutoConfigurationRemoved");
356 
357             if (exceptionRef.get() != null) {
358                 Log.d(TAG, "ImsConfigImplBase Exception notifyRcsAutoConfigurationRemoved");
359                 throw exceptionRef.get();
360             }
361         }
362 
notifyImsConfigChanged(int item, int value)363         private void notifyImsConfigChanged(int item, int value) throws RemoteException {
364             getImsConfigImpl().notifyConfigChanged(item, value);
365         }
366 
notifyImsConfigChanged(int item, String value)367         private void notifyImsConfigChanged(int item, String value) throws RemoteException {
368             getImsConfigImpl().notifyConfigChanged(item, value);
369         }
370 
updateCachedValue(int item, int value)371         protected void updateCachedValue(int item, int value) {
372             synchronized (mLock) {
373                 mProvisionedIntValue.put(item, value);
374             }
375         }
376 
updateCachedValue(int item, String value)377         protected void updateCachedValue(int item, String value) {
378             synchronized (mLock) {
379                 mProvisionedStringValue.put(item, value);
380             }
381         }
382 
383         @Override
addRcsConfigCallback(IRcsConfigCallback c)384         public void addRcsConfigCallback(IRcsConfigCallback c) throws RemoteException {
385             AtomicReference<RemoteException> exceptionRef = new AtomicReference<>();
386             executeMethodAsync(()-> {
387                 try {
388                     getImsConfigImpl().addRcsConfigCallback(c);
389                 } catch (RemoteException e) {
390                     exceptionRef.set(e);
391                 }
392             }, "addRcsConfigCallback");
393 
394             if (exceptionRef.get() != null) {
395                 Log.d(TAG, "ImsConfigImplBase Exception addRcsConfigCallback");
396                 throw exceptionRef.get();
397             }
398         }
399 
400         @Override
removeRcsConfigCallback(IRcsConfigCallback c)401         public void removeRcsConfigCallback(IRcsConfigCallback c) throws RemoteException {
402             AtomicReference<RemoteException> exceptionRef = new AtomicReference<>();
403             executeMethodAsync(()-> {
404                 try {
405                     getImsConfigImpl().removeRcsConfigCallback(c);
406                 } catch (RemoteException e) {
407                     exceptionRef.set(e);
408                 }
409             }, "removeRcsConfigCallback");
410 
411             if (exceptionRef.get() != null) {
412                 Log.d(TAG, "ImsConfigImplBase Exception removeRcsConfigCallback");
413                 throw exceptionRef.get();
414             }
415         }
416 
417         @Override
triggerRcsReconfiguration()418         public void triggerRcsReconfiguration() throws RemoteException {
419             AtomicReference<RemoteException> exceptionRef = new AtomicReference<>();
420             executeMethodAsync(()-> {
421                 try {
422                     getImsConfigImpl().triggerAutoConfiguration();
423                 } catch (RemoteException e) {
424                     exceptionRef.set(e);
425                 }
426             }, "triggerRcsReconfiguration");
427 
428             if (exceptionRef.get() != null) {
429                 Log.d(TAG, "ImsConfigImplBase Exception triggerRcsReconfiguration");
430                 throw exceptionRef.get();
431             }
432         }
433 
434         @Override
setRcsClientConfiguration(RcsClientConfiguration rcc)435         public void setRcsClientConfiguration(RcsClientConfiguration rcc) throws RemoteException {
436             AtomicReference<RemoteException> exceptionRef = new AtomicReference<>();
437             executeMethodAsync(()-> {
438                 try {
439                     getImsConfigImpl().setRcsClientConfiguration(rcc);
440                 } catch (RemoteException e) {
441                     exceptionRef.set(e);
442                 }
443             }, "setRcsClientConfiguration");
444 
445             if (exceptionRef.get() != null) {
446                 Log.d(TAG, "ImsConfigImplBase Exception setRcsClientConfiguration");
447                 throw exceptionRef.get();
448             }
449         }
450 
451         @Override
notifyIntImsConfigChanged(int item, int value)452         public void notifyIntImsConfigChanged(int item, int value) throws RemoteException {
453             AtomicReference<RemoteException> exceptionRef = new AtomicReference<>();
454             executeMethodAsync(()-> {
455                 try {
456                     notifyImsConfigChanged(item, value);
457                 } catch (RemoteException e) {
458                     exceptionRef.set(e);
459                 }
460             }, "notifyIntImsConfigChanged");
461 
462             if (exceptionRef.get() != null) {
463                 Log.d(TAG, "ImsConfigImplBase Exception notifyIntImsConfigChanged");
464                 throw exceptionRef.get();
465             }
466         }
467 
468         @Override
notifyStringImsConfigChanged(int item, String value)469         public void notifyStringImsConfigChanged(int item, String value) throws RemoteException {
470             AtomicReference<RemoteException> exceptionRef = new AtomicReference<>();
471             executeMethodAsync(()-> {
472                 try {
473                     notifyImsConfigChanged(item, value);
474                 } catch (RemoteException e) {
475                     exceptionRef.set(e);
476                 }
477             }, "notifyStringImsConfigChanged");
478 
479             if (exceptionRef.get() != null) {
480                 Log.d(TAG, "ImsConfigImplBase Exception notifyStringImsConfigChanged");
481                 throw exceptionRef.get();
482             }
483         }
484 
485         /**
486          * Clear cached configuration value.
487          */
clearCachedValue()488         public void clearCachedValue() {
489             Log.i(TAG, "clearCachedValue");
490             synchronized (mLock) {
491                 mProvisionedIntValue.clear();
492                 mProvisionedStringValue.clear();
493             }
494         }
495 
496         // Call the methods with a clean calling identity on the executor and wait indefinitely for
497         // the future to return.
executeMethodAsync(Runnable r, String errorLogName)498         private void executeMethodAsync(Runnable r, String errorLogName) throws RemoteException {
499             try {
500                 CompletableFuture.runAsync(
501                         () -> TelephonyUtils.runWithCleanCallingIdentity(r), mExecutor).join();
502             } catch (CancellationException | CompletionException e) {
503                 Log.w(TAG, "ImsConfigImplBase Binder - " + errorLogName + " exception: "
504                         + e.getMessage());
505                 throw new RemoteException(e.getMessage());
506             }
507         }
508 
executeMethodAsyncForResult(Supplier<T> r, String errorLogName)509         private <T> T executeMethodAsyncForResult(Supplier<T> r,
510                 String errorLogName) throws RemoteException {
511             CompletableFuture<T> future = CompletableFuture.supplyAsync(
512                     () -> TelephonyUtils.runWithCleanCallingIdentity(r), mExecutor);
513             try {
514                 return future.get();
515             } catch (ExecutionException | InterruptedException e) {
516                 Log.w(TAG, "ImsConfigImplBase Binder - " + errorLogName + " exception: "
517                         + e.getMessage());
518                 throw new RemoteException(e.getMessage());
519             }
520         }
521     }
522 
523     /**
524      * The configuration requested resulted in an unknown result. This may happen if the
525      * IMS configurations are unavailable.
526      */
527     public static final int CONFIG_RESULT_UNKNOWN = ProvisioningManager.PROVISIONING_RESULT_UNKNOWN;
528 
529     /**
530      * Setting the configuration value completed.
531      */
532     public static final int CONFIG_RESULT_SUCCESS = 0;
533     /**
534      * Setting the configuration value failed.
535      */
536     public static final int CONFIG_RESULT_FAILED =  1;
537 
538     /**
539      * @hide
540      */
541     @Retention(RetentionPolicy.SOURCE)
542     @IntDef(prefix = "CONFIG_RESULT_", value = {
543             CONFIG_RESULT_SUCCESS,
544             CONFIG_RESULT_FAILED
545     })
546     public @interface SetConfigResult {}
547 
548     private final RemoteCallbackListExt<IImsConfigCallback> mCallbacks =
549             new RemoteCallbackListExt<>();
550     private final RemoteCallbackListExt<IRcsConfigCallback> mRcsCallbacks =
551             new RemoteCallbackListExt<>();
552     private byte[] mRcsConfigData;
553     private final Object mRcsConfigDataLock = new Object();
554     ImsConfigStub mImsConfigStub;
555 
556     /**
557      * Create an ImsConfig using the Executor specified for methods being called by the
558      * framework.
559      * @param executor The executor for the framework to use when executing the methods overridden
560      * by the implementation of ImsConfig.
561      */
ImsConfigImplBase(@onNull Executor executor)562     public ImsConfigImplBase(@NonNull Executor executor) {
563         mImsConfigStub = new ImsConfigStub(this, executor);
564     }
565 
566     /**
567      * @hide
568      */
ImsConfigImplBase(@onNull Context context)569     public ImsConfigImplBase(@NonNull Context context) {
570         mImsConfigStub = new ImsConfigStub(this, null);
571     }
572 
573     /**
574      * Create an ImsConfig using the Executor defined in {@link ImsService#getExecutor}
575      */
ImsConfigImplBase()576     public ImsConfigImplBase() {
577         mImsConfigStub = new ImsConfigStub(this, null);
578     }
579 
580     /**
581      * Adds a {@link android.telephony.ims.ProvisioningManager.Callback} to the list of callbacks
582      * notified when a value in the configuration changes.
583      * @param c callback to add.
584      */
addImsConfigCallback(IImsConfigCallback c)585     private void addImsConfigCallback(IImsConfigCallback c) {
586         mCallbacks.register(c);
587     }
588     /**
589      * Removes a {@link android.telephony.ims.ProvisioningManager.Callback} to the list of callbacks
590      * notified when a value in the configuration changes.
591      * @param c callback to remove.
592      */
removeImsConfigCallback(IImsConfigCallback c)593     private void removeImsConfigCallback(IImsConfigCallback c) {
594         mCallbacks.unregister(c);
595     }
596 
597     /**
598      * @param item
599      * @param value
600      */
notifyConfigChanged(int item, int value)601     private final void notifyConfigChanged(int item, int value) {
602         // can be null in testing
603         if (mCallbacks == null) {
604             return;
605         }
606         synchronized (mCallbacks) {
607             mCallbacks.broadcastAction(c -> {
608                 try {
609                     c.onIntConfigChanged(item, value);
610                 } catch (RemoteException e) {
611                     Log.w(TAG, "notifyConfigChanged(int): dead binder in notify, skipping.");
612                 }
613             });
614         }
615     }
616 
notifyConfigChanged(int item, String value)617     private void notifyConfigChanged(int item, String value) {
618         // can be null in testing
619         if (mCallbacks == null) {
620             return;
621         }
622         synchronized (mCallbacks) {
623             mCallbacks.broadcastAction(c -> {
624                 try {
625                     c.onStringConfigChanged(item, value);
626                 } catch (RemoteException e) {
627                     Log.w(TAG, "notifyConfigChanged(string): dead binder in notify, skipping.");
628                 }
629             });
630         }
631     }
632 
addRcsConfigCallback(IRcsConfigCallback c)633     private void addRcsConfigCallback(IRcsConfigCallback c) {
634         mRcsCallbacks.register(c);
635 
636         // This is used to avoid calling the binder out of the synchronized scope.
637         byte[] cloneRcsConfigData;
638         synchronized (mRcsConfigDataLock) {
639             if (mRcsConfigData == null) {
640                 return;
641             }
642             cloneRcsConfigData = mRcsConfigData.clone();
643         }
644 
645         try {
646             c.onConfigurationChanged(cloneRcsConfigData);
647         } catch (RemoteException e) {
648             Log.w(TAG, "dead binder to call onConfigurationChanged, skipping.");
649         }
650     }
651 
removeRcsConfigCallback(IRcsConfigCallback c)652     private void removeRcsConfigCallback(IRcsConfigCallback c) {
653         mRcsCallbacks.unregister(c);
654     }
655 
onNotifyRcsAutoConfigurationReceived(byte[] config, boolean isCompressed)656     private void onNotifyRcsAutoConfigurationReceived(byte[] config, boolean isCompressed) {
657         // cache uncompressed config
658         final byte[] rcsConfigData = isCompressed ? RcsConfig.decompressGzip(config) : config;
659 
660         synchronized (mRcsConfigDataLock) {
661             if (Arrays.equals(mRcsConfigData, config)) {
662                 return;
663             }
664             mRcsConfigData = rcsConfigData;
665         }
666 
667         // can be null in testing
668         if (mRcsCallbacks != null) {
669             synchronized (mRcsCallbacks) {
670                 mRcsCallbacks.broadcastAction(c -> {
671                     try {
672                         // config is cloned here so modifications to the config passed to the
673                         // vendor do not accidentally modify the cache.
674                         c.onConfigurationChanged(rcsConfigData.clone());
675                     } catch (RemoteException e) {
676                         Log.w(TAG, "dead binder in notifyRcsAutoConfigurationReceived, skipping.");
677                     }
678                 });
679             }
680         }
681         notifyRcsAutoConfigurationReceived(config, isCompressed);
682     }
683 
onNotifyRcsAutoConfigurationRemoved()684     private void onNotifyRcsAutoConfigurationRemoved() {
685         synchronized (mRcsConfigDataLock) {
686             mRcsConfigData = null;
687         }
688         if (mRcsCallbacks != null) {
689             synchronized (mRcsCallbacks) {
690                 mRcsCallbacks.broadcastAction(c -> {
691                     try {
692                         c.onConfigurationReset();
693                     } catch (RemoteException e) {
694                         Log.w(TAG, "dead binder in notifyRcsAutoConfigurationRemoved, skipping.");
695                     }
696                 });
697             }
698         }
699         notifyRcsAutoConfigurationRemoved();
700     }
701 
702     /**
703      * @hide
704      */
getIImsConfig()705     public IImsConfig getIImsConfig() { return mImsConfigStub; }
706 
707     /**
708      * Updates provisioning value and notifies the framework of the change.
709      * Doesn't call {@link #setConfig(int,int)} and assumes the result succeeded.
710      * This should only be used when the IMS implementer implicitly changed provisioned values.
711      *
712      * @param item an integer key.
713      * @param value in Integer format.
714      */
notifyProvisionedValueChanged(int item, int value)715     public final void notifyProvisionedValueChanged(int item, int value) {
716         mImsConfigStub.updateCachedValue(item, value);
717 
718         try {
719             mImsConfigStub.notifyImsConfigChanged(item, value);
720         } catch (RemoteException e) {
721             Log.w(TAG, "notifyProvisionedValueChanged(int): Framework connection is dead.");
722         }
723     }
724 
725     /**
726      * Updates provisioning value and notifies the framework of the change.
727      * Doesn't call {@link #setConfig(int,String)} and assumes the result succeeded.
728      * This should only be used when the IMS implementer implicitly changed provisioned values.
729      *
730      * @param item an integer key.
731      * @param value in String format.
732      */
notifyProvisionedValueChanged(int item, String value)733     public final void notifyProvisionedValueChanged(int item, String value) {
734         mImsConfigStub.updateCachedValue(item, value);
735 
736         try {
737             mImsConfigStub.notifyImsConfigChanged(item, value);
738         } catch (RemoteException e) {
739             Log.w(TAG, "notifyProvisionedValueChanged(string): Framework connection is dead.");
740         }
741     }
742 
743     /**
744      * The framework has received an RCS autoconfiguration XML file for provisioning.
745      *
746      * @param config The XML file to be read, if not compressed, it should be in ASCII/UTF8 format.
747      * @param isCompressed The XML file is compressed in gzip format and must be decompressed
748      *         before being read.
749      *
750      */
notifyRcsAutoConfigurationReceived(@onNull byte[] config, boolean isCompressed)751     public void notifyRcsAutoConfigurationReceived(@NonNull byte[] config, boolean isCompressed) {
752     }
753 
754     /**
755      * The RCS autoconfiguration XML file is removed or invalid.
756      */
notifyRcsAutoConfigurationRemoved()757     public void notifyRcsAutoConfigurationRemoved() {
758     }
759 
760     /**
761      * Sets the configuration value for this ImsService.
762      *
763      * @param item an integer key.
764      * @param value an integer containing the configuration value.
765      * @return the result of setting the configuration value.
766      */
setConfig(int item, int value)767     public @SetConfigResult int setConfig(int item, int value) {
768         // Base Implementation - To be overridden.
769         return CONFIG_RESULT_FAILED;
770     }
771 
772     /**
773      * Sets the configuration value for this ImsService.
774      *
775      * @param item an integer key.
776      * @param value a String containing the new configuration value.
777      * @return Result of setting the configuration value.
778      */
setConfig(int item, String value)779     public @SetConfigResult int setConfig(int item, String value) {
780         // Base Implementation - To be overridden.
781         return CONFIG_RESULT_FAILED;
782     }
783 
784     /**
785      * Gets the currently stored value configuration value from the ImsService for {@code item}.
786      *
787      * @param item an integer key.
788      * @return configuration value, stored in integer format or {@link #CONFIG_RESULT_UNKNOWN} if
789      * unavailable.
790      */
getConfigInt(int item)791     public int getConfigInt(int item) {
792         // Base Implementation - To be overridden.
793         return CONFIG_RESULT_UNKNOWN;
794     }
795 
796     /**
797      * Gets the currently stored value configuration value from the ImsService for {@code item}.
798      *
799      * @param item an integer key.
800      * @return configuration value, stored in String format or {@code null} if unavailable.
801      */
getConfigString(int item)802     public String getConfigString(int item) {
803         // Base Implementation - To be overridden.
804         return null;
805     }
806 
807     /**
808      * @hide
809      */
updateImsCarrierConfigs(PersistableBundle bundle)810     public void updateImsCarrierConfigs(PersistableBundle bundle) {
811         // Base Implementation - Should be overridden
812     }
813 
814     /**
815      * Default messaging application parameters are sent to the ACS client
816      * using this interface.
817      * @param rcc RCS client configuration {@link RcsClientConfiguration}
818      */
setRcsClientConfiguration(@onNull RcsClientConfiguration rcc)819     public void setRcsClientConfiguration(@NonNull RcsClientConfiguration rcc) {
820         // Base Implementation - Should be overridden
821     }
822 
823     /**
824      * Reconfiguration triggered by the RCS application. Most likely cause
825      * is the 403 forbidden to a SIP/HTTP request
826      */
triggerAutoConfiguration()827     public void triggerAutoConfiguration() {
828         // Base Implementation - Should be overridden
829     }
830 
831     /**
832      * Errors during autoconfiguration connection setup are notified by the
833      * ACS client using this interface.
834      * @param errorCode HTTP error received during connection setup.
835      * @param errorString reason phrase received with the error
836      */
notifyAutoConfigurationErrorReceived(int errorCode, @NonNull String errorString)837     public final void notifyAutoConfigurationErrorReceived(int errorCode,
838             @NonNull String errorString) {
839         // can be null in testing
840         if (mRcsCallbacks == null) {
841             return;
842         }
843         synchronized (mRcsCallbacks) {
844             mRcsCallbacks.broadcastAction(c -> {
845                 try {
846                     c.onAutoConfigurationErrorReceived(errorCode, errorString);
847                 } catch (RemoteException e) {
848                     Log.w(TAG, "dead binder in notifyAutoConfigurationErrorReceived, skipping.");
849                 }
850             });
851         }
852     }
853 
854     /**
855      * Notifies application that pre-provisioning config is received.
856      *
857      * <p>Some carriers using ACS (auto configuration server) may send a carrier-specific
858      * pre-provisioning configuration XML if the user has not been provisioned for RCS
859      * services yet. When such provisioning XML is received, ACS client must call this
860      * method to notify the application with the XML.
861      *
862      * @param configXml the pre-provisioning config in carrier specified format.
863      */
notifyPreProvisioningReceived(@onNull byte[] configXml)864     public final void notifyPreProvisioningReceived(@NonNull byte[] configXml) {
865         // can be null in testing
866         if (mRcsCallbacks == null) {
867             return;
868         }
869         synchronized (mRcsCallbacks) {
870             mRcsCallbacks.broadcastAction(c -> {
871                 try {
872                     c.onPreProvisioningReceived(configXml);
873                 } catch (RemoteException e) {
874                     Log.w(TAG, "dead binder in notifyPreProvisioningReceived, skipping.");
875                 }
876             });
877         }
878     }
879 
880     /**
881      * Set default Executor from ImsService.
882      * @param executor The default executor for the framework to use when executing the methods
883      * overridden by the implementation of ImsConfig.
884      * @hide
885      */
setDefaultExecutor(@onNull Executor executor)886     public final void setDefaultExecutor(@NonNull Executor executor) {
887         if (mImsConfigStub.mExecutor == null) {
888             mImsConfigStub.mExecutor = executor;
889         }
890     }
891 
892     /**
893      * Clear all cached config data. This will be called when the config data is no longer valid
894      * such as when the SIM was removed.
895      * @hide
896      */
clearConfigurationCache()897     public final void clearConfigurationCache() {
898         mImsConfigStub.clearCachedValue();
899 
900         synchronized (mRcsConfigDataLock) {
901             mRcsConfigData = null;
902         }
903     }
904 }
905