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;
18 
19 import android.Manifest;
20 import android.annotation.CallbackExecutor;
21 import android.annotation.NonNull;
22 import android.annotation.Nullable;
23 import android.annotation.RequiresPermission;
24 import android.annotation.StringDef;
25 import android.annotation.SystemApi;
26 import android.annotation.WorkerThread;
27 import android.content.Context;
28 import android.content.pm.IPackageManager;
29 import android.content.pm.PackageManager;
30 import android.os.Binder;
31 import android.os.RemoteException;
32 import android.os.ServiceManager;
33 import android.telephony.CarrierConfigManager;
34 import android.telephony.SubscriptionManager;
35 import android.telephony.ims.aidl.IImsConfigCallback;
36 import android.telephony.ims.feature.MmTelFeature;
37 import android.telephony.ims.stub.ImsConfigImplBase;
38 import android.telephony.ims.stub.ImsRegistrationImplBase;
39 
40 import com.android.internal.telephony.ITelephony;
41 
42 import java.lang.annotation.Retention;
43 import java.lang.annotation.RetentionPolicy;
44 import java.util.concurrent.Executor;
45 
46 /**
47  * Manages IMS provisioning and configuration parameters, as well as callbacks for apps to listen
48  * to changes in these configurations.
49  *
50  * IMS provisioning keys are defined per carrier or OEM using OMA-DM or other provisioning
51  * applications and may vary. It is up to the carrier and OEM applications to ensure that the
52  * correct provisioning keys are being used when integrating with a vendor's ImsService.
53  *
54  * Note: For compatibility purposes, the integer values [0 - 99] used in
55  * {@link #setProvisioningIntValue(int, int)} have been reserved for existing provisioning keys
56  * previously defined in the Android framework. Please do not redefine new provisioning keys in this
57  * range or it may generate collisions with existing keys. Some common constants have also been
58  * defined in this class to make integrating with other system apps easier.
59  * @hide
60  */
61 @SystemApi
62 public class ProvisioningManager {
63 
64     /**@hide*/
65     @StringDef(prefix = "STRING_QUERY_RESULT_ERROR_", value = {
66             STRING_QUERY_RESULT_ERROR_GENERIC,
67             STRING_QUERY_RESULT_ERROR_NOT_READY
68     })
69     @Retention(RetentionPolicy.SOURCE)
70     public @interface StringResultError {}
71 
72     /**
73      * The query from {@link #getProvisioningStringValue(int)} has resulted in an unspecified error.
74      */
75     public static final String STRING_QUERY_RESULT_ERROR_GENERIC =
76             "STRING_QUERY_RESULT_ERROR_GENERIC";
77 
78     /**
79      * The query from {@link #getProvisioningStringValue(int)} has resulted in an error because the
80      * ImsService implementation was not ready for provisioning queries.
81      */
82     public static final String STRING_QUERY_RESULT_ERROR_NOT_READY =
83             "STRING_QUERY_RESULT_ERROR_NOT_READY";
84 
85     /**
86      * The integer result of provisioning for the queried key is disabled.
87      */
88     public static final int PROVISIONING_VALUE_DISABLED = 0;
89 
90     /**
91      * The integer result of provisioning for the queried key is enabled.
92      */
93     public static final int PROVISIONING_VALUE_ENABLED = 1;
94 
95 
96     /**
97      * Override the user-defined WiFi Roaming enabled setting for this subscription, defined in
98      * {@link SubscriptionManager#WFC_ROAMING_ENABLED_CONTENT_URI}, for the purposes of provisioning
99      * the subscription for WiFi Calling.
100      *
101      * @see #getProvisioningIntValue(int)
102      * @see #setProvisioningIntValue(int, int)
103      */
104     public static final int KEY_VOICE_OVER_WIFI_ROAMING_ENABLED_OVERRIDE = 26;
105 
106     /**
107      * Override the user-defined WiFi mode for this subscription, defined in
108      * {@link SubscriptionManager#WFC_MODE_CONTENT_URI}, for the purposes of provisioning
109      * this subscription for WiFi Calling.
110      *
111      * Valid values for this key are:
112      * {@link ImsMmTelManager#WIFI_MODE_WIFI_ONLY},
113      * {@link ImsMmTelManager#WIFI_MODE_CELLULAR_PREFERRED}, or
114      * {@link ImsMmTelManager#WIFI_MODE_WIFI_PREFERRED}.
115      *
116      * @see #getProvisioningIntValue(int)
117      * @see #setProvisioningIntValue(int, int)
118      */
119     public static final int KEY_VOICE_OVER_WIFI_MODE_OVERRIDE = 27;
120 
121     /**
122      * Callback for IMS provisioning changes.
123      */
124     public static class Callback {
125 
126         private static class CallbackBinder extends IImsConfigCallback.Stub {
127 
128             private final Callback mLocalConfigurationCallback;
129             private Executor mExecutor;
130 
CallbackBinder(Callback localConfigurationCallback)131             private CallbackBinder(Callback localConfigurationCallback) {
132                 mLocalConfigurationCallback = localConfigurationCallback;
133             }
134 
135             @Override
onIntConfigChanged(int item, int value)136             public final void onIntConfigChanged(int item, int value) {
137                 Binder.withCleanCallingIdentity(() ->
138                         mExecutor.execute(() ->
139                                 mLocalConfigurationCallback.onProvisioningIntChanged(item, value)));
140             }
141 
142             @Override
onStringConfigChanged(int item, String value)143             public final void onStringConfigChanged(int item, String value) {
144                 Binder.withCleanCallingIdentity(() ->
145                         mExecutor.execute(() ->
146                                 mLocalConfigurationCallback.onProvisioningStringChanged(item,
147                                         value)));
148             }
149 
setExecutor(Executor executor)150             private void setExecutor(Executor executor) {
151                 mExecutor = executor;
152             }
153         }
154 
155         private final CallbackBinder mBinder = new CallbackBinder(this);
156 
157         /**
158          * Called when a provisioning item has changed.
159          * @param item the IMS provisioning key constant, as defined by the OEM.
160          * @param value the new integer value of the IMS provisioning key.
161          */
onProvisioningIntChanged(int item, int value)162         public void onProvisioningIntChanged(int item, int value) {
163             // Base Implementation
164         }
165 
166         /**
167          * Called when a provisioning item has changed.
168          * @param item the IMS provisioning key constant, as defined by the OEM.
169          * @param value the new String value of the IMS configuration constant.
170          */
onProvisioningStringChanged(int item, @NonNull String value)171         public void onProvisioningStringChanged(int item, @NonNull String value) {
172             // Base Implementation
173         }
174 
175         /**@hide*/
getBinder()176         public final IImsConfigCallback getBinder() {
177             return mBinder;
178         }
179 
180         /**@hide*/
setExecutor(Executor executor)181         public void setExecutor(Executor executor) {
182             mBinder.setExecutor(executor);
183         }
184     }
185 
186     private int mSubId;
187 
188     /**
189      * Create a new {@link ProvisioningManager} for the subscription specified.
190      *
191      * @param subId The ID of the subscription that this ProvisioningManager will use.
192      * @see android.telephony.SubscriptionManager#getActiveSubscriptionInfoList()
193      * @throws IllegalArgumentException if the subscription is invalid.
194      */
createForSubscriptionId(int subId)195     public static @NonNull ProvisioningManager createForSubscriptionId(int subId) {
196         if (!SubscriptionManager.isValidSubscriptionId(subId)) {
197             throw new IllegalArgumentException("Invalid subscription ID");
198         }
199 
200         return new ProvisioningManager(subId);
201     }
202 
ProvisioningManager(int subId)203     private ProvisioningManager(int subId) {
204         mSubId = subId;
205     }
206 
207     /**
208      * Register a new {@link Callback} to listen to changes to changes in IMS provisioning.
209      *
210      * When the subscription associated with this callback is removed (SIM removed, ESIM swap,
211      * etc...), this callback will automatically be removed.
212      * @param executor The {@link Executor} to call the callback methods on
213      * @param callback The provisioning callbackto be registered.
214      * @see #unregisterProvisioningChangedCallback(Callback)
215      * @see SubscriptionManager.OnSubscriptionsChangedListener
216      * @throws IllegalArgumentException if the subscription associated with this callback is not
217      * active (SIM is not inserted, ESIM inactive) or the subscription is invalid.
218      * @throws ImsException if the subscription associated with this callback is valid, but
219      * the {@link ImsService} associated with the subscription is not available. This can happen if
220      * the service crashed, for example. See {@link ImsException#getCode()} for a more detailed
221      * reason.
222      */
223     @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
registerProvisioningChangedCallback(@onNull @allbackExecutor Executor executor, @NonNull Callback callback)224     public void registerProvisioningChangedCallback(@NonNull @CallbackExecutor Executor executor,
225             @NonNull Callback callback) throws ImsException {
226         if (!isImsAvailableOnDevice()) {
227             throw new ImsException("IMS not available on device.",
228                     ImsException.CODE_ERROR_UNSUPPORTED_OPERATION);
229         }
230         callback.setExecutor(executor);
231         try {
232             getITelephony().registerImsProvisioningChangedCallback(mSubId, callback.getBinder());
233         } catch (RemoteException | IllegalStateException e) {
234             throw new ImsException(e.getMessage(), ImsException.CODE_ERROR_SERVICE_UNAVAILABLE);
235         }
236     }
237 
238     /**
239      * Unregister an existing {@link Callback}. When the subscription associated with this
240      * callback is removed (SIM removed, ESIM swap, etc...), this callback will automatically be
241      * removed. If this method is called for an inactive subscription, it will result in a no-op.
242      * @param callback The existing {@link Callback} to be removed.
243      * @see #registerProvisioningChangedCallback(Executor, Callback)
244      *
245      * @throws IllegalArgumentException if the subscription associated with this callback is
246      * invalid.
247      */
248     @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
unregisterProvisioningChangedCallback(@onNull Callback callback)249     public void unregisterProvisioningChangedCallback(@NonNull Callback callback) {
250         try {
251             getITelephony().unregisterImsProvisioningChangedCallback(mSubId, callback.getBinder());
252         } catch (RemoteException e) {
253             throw e.rethrowAsRuntimeException();
254         }
255     }
256 
257     /**
258      * Query for the integer value associated with the provided key.
259      *
260      * This operation is blocking and should not be performed on the UI thread.
261      *
262      * @param key An integer that represents the provisioning key, which is defined by the OEM.
263      * @return an integer value for the provided key, or
264      * {@link ImsConfigImplBase#CONFIG_RESULT_UNKNOWN} if the key doesn't exist.
265      * @throws IllegalArgumentException if the key provided was invalid.
266      */
267     @WorkerThread
268     @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
getProvisioningIntValue(int key)269     public int getProvisioningIntValue(int key) {
270         try {
271             return getITelephony().getImsProvisioningInt(mSubId, key);
272         } catch (RemoteException e) {
273             throw e.rethrowAsRuntimeException();
274         }
275     }
276 
277     /**
278      * Query for the String value associated with the provided key.
279      *
280      * This operation is blocking and should not be performed on the UI thread.
281      *
282      * @param key A String that represents the provisioning key, which is defined by the OEM.
283      * @return a String value for the provided key, {@code null} if the key doesn't exist, or
284      * {@link StringResultError} if there was an error getting the value for the provided key.
285      * @throws IllegalArgumentException if the key provided was invalid.
286      */
287     @WorkerThread
288     @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
getProvisioningStringValue(int key)289     public @Nullable @StringResultError String getProvisioningStringValue(int key) {
290         try {
291             return getITelephony().getImsProvisioningString(mSubId, key);
292         } catch (RemoteException e) {
293             throw e.rethrowAsRuntimeException();
294         }
295     }
296 
297     /**
298      * Set the integer value associated with the provided key.
299      *
300      * This operation is blocking and should not be performed on the UI thread.
301      *
302      * Use {@link #setProvisioningStringValue(int, String)} with proper namespacing (to be defined
303      * per OEM or carrier) when possible instead to avoid key collision if needed.
304      * @param key An integer that represents the provisioning key, which is defined by the OEM.
305      * @param value a integer value for the provided key.
306      * @return the result of setting the configuration value.
307      */
308     @WorkerThread
309     @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
setProvisioningIntValue(int key, int value)310     public @ImsConfigImplBase.SetConfigResult int setProvisioningIntValue(int key, int value) {
311         try {
312             return getITelephony().setImsProvisioningInt(mSubId, key, value);
313         } catch (RemoteException e) {
314             throw e.rethrowAsRuntimeException();
315         }
316     }
317 
318     /**
319      * Set the String value associated with the provided key.
320      *
321      * This operation is blocking and should not be performed on the UI thread.
322      *
323      * @param key A String that represents the provisioning key, which is defined by the OEM and
324      *     should be appropriately namespaced to avoid collision.
325      * @param value a String value for the provided key.
326      * @return the result of setting the configuration value.
327      */
328     @WorkerThread
329     @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
setProvisioningStringValue(int key, @NonNull String value)330     public @ImsConfigImplBase.SetConfigResult int setProvisioningStringValue(int key,
331             @NonNull String value) {
332         try {
333             return getITelephony().setImsProvisioningString(mSubId, key, value);
334         } catch (RemoteException e) {
335             throw e.rethrowAsRuntimeException();
336         }
337     }
338 
339     /**
340      * Set the provisioning status for the IMS MmTel capability using the specified subscription.
341      *
342      * Provisioning may or may not be required, depending on the carrier configuration. If
343      * provisioning is not required for the carrier associated with this subscription or the device
344      * does not support the capability/technology combination specified, this operation will be a
345      * no-op.
346      *
347      * @see CarrierConfigManager#KEY_CARRIER_UT_PROVISIONING_REQUIRED_BOOL
348      * @see CarrierConfigManager#KEY_CARRIER_VOLTE_PROVISIONING_REQUIRED_BOOL
349      * @param isProvisioned true if the device is provisioned for UT over IMS, false otherwise.
350      */
351     @WorkerThread
352     @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
setProvisioningStatusForCapability( @mTelFeature.MmTelCapabilities.MmTelCapability int capability, @ImsRegistrationImplBase.ImsRegistrationTech int tech, boolean isProvisioned)353     public void setProvisioningStatusForCapability(
354             @MmTelFeature.MmTelCapabilities.MmTelCapability int capability,
355             @ImsRegistrationImplBase.ImsRegistrationTech int tech,  boolean isProvisioned) {
356         try {
357             getITelephony().setImsProvisioningStatusForCapability(mSubId, capability, tech,
358                     isProvisioned);
359         } catch (RemoteException e) {
360             throw e.rethrowAsRuntimeException();
361         }
362     }
363 
364     /**
365      * Get the provisioning status for the IMS MmTel capability specified.
366      *
367      * If provisioning is not required for the queried
368      * {@link MmTelFeature.MmTelCapabilities.MmTelCapability} and
369      * {@link ImsRegistrationImplBase.ImsRegistrationTech} combination specified, this method will
370      * always return {@code true}.
371      *
372      * @see CarrierConfigManager#KEY_CARRIER_UT_PROVISIONING_REQUIRED_BOOL
373      * @see CarrierConfigManager#KEY_CARRIER_VOLTE_PROVISIONING_REQUIRED_BOOL
374      * @return true if the device is provisioned for the capability or does not require
375      * provisioning, false if the capability does require provisioning and has not been
376      * provisioned yet.
377      */
378     @WorkerThread
379     @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
getProvisioningStatusForCapability( @mTelFeature.MmTelCapabilities.MmTelCapability int capability, @ImsRegistrationImplBase.ImsRegistrationTech int tech)380     public boolean getProvisioningStatusForCapability(
381             @MmTelFeature.MmTelCapabilities.MmTelCapability int capability,
382             @ImsRegistrationImplBase.ImsRegistrationTech int tech) {
383         try {
384             return getITelephony().getImsProvisioningStatusForCapability(mSubId, capability, tech);
385         } catch (RemoteException e) {
386             throw e.rethrowAsRuntimeException();
387         }
388     }
389 
isImsAvailableOnDevice()390     private static boolean isImsAvailableOnDevice() {
391         IPackageManager pm = IPackageManager.Stub.asInterface(ServiceManager.getService("package"));
392         if (pm == null) {
393             // For some reason package manger is not available.. This will fail internally anyways,
394             // so do not throw error and allow.
395             return true;
396         }
397         try {
398             return pm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY_IMS, 0);
399         } catch (RemoteException e) {
400             // For some reason package manger is not available.. This will fail internally anyways,
401             // so do not throw error and allow.
402         }
403         return true;
404     }
405 
getITelephony()406     private static ITelephony getITelephony() {
407         ITelephony binder = ITelephony.Stub.asInterface(
408                 ServiceManager.getService(Context.TELEPHONY_SERVICE));
409         if (binder == null) {
410             throw new RuntimeException("Could not find Telephony Service.");
411         }
412         return binder;
413     }
414 }
415