OSDN Git Service

DO NOT MERGE. Grant MMS Uri permissions as the calling UID.
[android-x86/frameworks-base.git] / telephony / java / android / telephony / SubscriptionManager.java
1 /*
2  * Copyright (C) 2014 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;
18
19 import android.annotation.NonNull;
20 import android.annotation.SdkConstant;
21 import android.annotation.SdkConstant.SdkConstantType;
22 import android.content.Context;
23 import android.content.Intent;
24 import android.content.res.Configuration;
25 import android.content.res.Resources;
26 import android.net.Uri;
27 import android.telephony.Rlog;
28 import android.os.Handler;
29 import android.os.Message;
30 import android.os.ServiceManager;
31 import android.os.RemoteException;
32 import android.util.DisplayMetrics;
33
34 import com.android.internal.telephony.ISub;
35 import com.android.internal.telephony.IOnSubscriptionsChangedListener;
36 import com.android.internal.telephony.ITelephonyRegistry;
37 import com.android.internal.telephony.PhoneConstants;
38 import java.util.ArrayList;
39 import java.util.List;
40
41 /**
42  * SubscriptionManager is the application interface to SubscriptionController
43  * and provides information about the current Telephony Subscriptions.
44  * * <p>
45  * You do not instantiate this class directly; instead, you retrieve
46  * a reference to an instance through {@link #from}.
47  * <p>
48  * All SDK public methods require android.Manifest.permission.READ_PHONE_STATE.
49  */
50 public class SubscriptionManager {
51     private static final String LOG_TAG = "SubscriptionManager";
52     private static final boolean DBG = false;
53     private static final boolean VDBG = false;
54
55     /** An invalid subscription identifier */
56     public static final int INVALID_SUBSCRIPTION_ID = -1;
57
58     /** Base value for Dummy SUBSCRIPTION_ID's. */
59     /** FIXME: Remove DummySubId's, but for now have them map just below INVALID_SUBSCRIPTION_ID
60     /** @hide */
61     public static final int DUMMY_SUBSCRIPTION_ID_BASE = INVALID_SUBSCRIPTION_ID - 1;
62
63     /** An invalid phone identifier */
64     /** @hide */
65     public static final int INVALID_PHONE_INDEX = -1;
66
67     /** An invalid slot identifier */
68     /** @hide */
69     public static final int INVALID_SIM_SLOT_INDEX = -1;
70
71     /** Indicates the caller wants the default sub id. */
72     /** @hide */
73     public static final int DEFAULT_SUBSCRIPTION_ID = Integer.MAX_VALUE;
74
75     /**
76      * Indicates the caller wants the default phone id.
77      * Used in SubscriptionController and Phone but do we really need it???
78      * @hide
79      */
80     public static final int DEFAULT_PHONE_INDEX = Integer.MAX_VALUE;
81
82     /** Indicates the caller wants the default slot id. NOT used remove? */
83     /** @hide */
84     public static final int DEFAULT_SIM_SLOT_INDEX = Integer.MAX_VALUE;
85
86     /** Minimum possible subid that represents a subscription */
87     /** @hide */
88     public static final int MIN_SUBSCRIPTION_ID_VALUE = 0;
89
90     /** Maximum possible subid that represents a subscription */
91     /** @hide */
92     public static final int MAX_SUBSCRIPTION_ID_VALUE = DEFAULT_SUBSCRIPTION_ID - 1;
93
94     /** @hide */
95     public static final Uri CONTENT_URI = Uri.parse("content://telephony/siminfo");
96
97     /**
98      * TelephonyProvider unique key column name is the subscription id.
99      * <P>Type: TEXT (String)</P>
100      */
101     /** @hide */
102     public static final String UNIQUE_KEY_SUBSCRIPTION_ID = "_id";
103
104     /**
105      * TelephonyProvider column name for SIM ICC Identifier
106      * <P>Type: TEXT (String)</P>
107      */
108     /** @hide */
109     public static final String ICC_ID = "icc_id";
110
111     /**
112      * TelephonyProvider column name for user SIM_SlOT_INDEX
113      * <P>Type: INTEGER (int)</P>
114      */
115     /** @hide */
116     public static final String SIM_SLOT_INDEX = "sim_id";
117
118     /** SIM is not inserted */
119     /** @hide */
120     public static final int SIM_NOT_INSERTED = -1;
121
122     /**
123      * TelephonyProvider column name for user displayed name.
124      * <P>Type: TEXT (String)</P>
125      */
126     /** @hide */
127     public static final String DISPLAY_NAME = "display_name";
128
129     /**
130      * TelephonyProvider column name for the service provider name for the SIM.
131      * <P>Type: TEXT (String)</P>
132      */
133     /** @hide */
134     public static final String CARRIER_NAME = "carrier_name";
135
136     /**
137      * Default name resource
138      * @hide
139      */
140     public static final int DEFAULT_NAME_RES = com.android.internal.R.string.unknownName;
141
142     /**
143      * TelephonyProvider column name for source of the user displayed name.
144      * <P>Type: INT (int)</P> with one of the NAME_SOURCE_XXXX values below
145      *
146      * @hide
147      */
148     public static final String NAME_SOURCE = "name_source";
149
150     /**
151      * The name_source is undefined
152      * @hide
153      */
154     public static final int NAME_SOURCE_UNDEFINDED = -1;
155
156     /**
157      * The name_source is the default
158      * @hide
159      */
160     public static final int NAME_SOURCE_DEFAULT_SOURCE = 0;
161
162     /**
163      * The name_source is from the SIM
164      * @hide
165      */
166     public static final int NAME_SOURCE_SIM_SOURCE = 1;
167
168     /**
169      * The name_source is from the user
170      * @hide
171      */
172     public static final int NAME_SOURCE_USER_INPUT = 2;
173
174     /**
175      * TelephonyProvider column name for the color of a SIM.
176      * <P>Type: INTEGER (int)</P>
177      */
178     /** @hide */
179     public static final String COLOR = "color";
180
181     /** @hide */
182     public static final int COLOR_1 = 0;
183
184     /** @hide */
185     public static final int COLOR_2 = 1;
186
187     /** @hide */
188     public static final int COLOR_3 = 2;
189
190     /** @hide */
191     public static final int COLOR_4 = 3;
192
193     /** @hide */
194     public static final int COLOR_DEFAULT = COLOR_1;
195
196     /**
197      * TelephonyProvider column name for the phone number of a SIM.
198      * <P>Type: TEXT (String)</P>
199      */
200     /** @hide */
201     public static final String NUMBER = "number";
202
203     /**
204      * TelephonyProvider column name for the number display format of a SIM.
205      * <P>Type: INTEGER (int)</P>
206      */
207     /** @hide */
208     public static final String DISPLAY_NUMBER_FORMAT = "display_number_format";
209
210     /** @hide */
211     public static final int DISPLAY_NUMBER_NONE = 0;
212
213     /** @hide */
214     public static final int DISPLAY_NUMBER_FIRST = 1;
215
216     /** @hide */
217     public static final int DISPLAY_NUMBER_LAST = 2;
218
219     /** @hide */
220     public static final int DISPLAY_NUMBER_DEFAULT = DISPLAY_NUMBER_FIRST;
221
222     /**
223      * TelephonyProvider column name for permission for data roaming of a SIM.
224      * <P>Type: INTEGER (int)</P>
225      */
226     /** @hide */
227     public static final String DATA_ROAMING = "data_roaming";
228
229     /** Indicates that data roaming is enabled for a subscription */
230     public static final int DATA_ROAMING_ENABLE = 1;
231
232     /** Indicates that data roaming is disabled for a subscription */
233     public static final int DATA_ROAMING_DISABLE = 0;
234
235     /** Sim provisioning status: provisioned */
236     /** @hide */
237     public static final int SIM_PROVISIONED = 0;
238
239     /** Sim provisioning status: un-provisioned due to cold sim */
240     /** @hide */
241     public static final int SIM_UNPROVISIONED_COLD = 1;
242
243     /** Sim provisioning status: un-provisioned due to out of credit */
244     /** @hide */
245     public static final int SIM_UNPROVISIONED_OUT_OF_CREDIT = 2;
246
247     /** Maximum possible sim provisioning status */
248     /** @hide */
249     public static final int MAX_SIM_PROVISIONING_STATUS = SIM_UNPROVISIONED_OUT_OF_CREDIT;
250
251     /** @hide */
252     public static final int DATA_ROAMING_DEFAULT = DATA_ROAMING_DISABLE;
253
254     /**
255      * TelephonyProvider column name for the MCC associated with a SIM.
256      * <P>Type: INTEGER (int)</P>
257      * @hide
258      */
259     public static final String MCC = "mcc";
260
261     /**
262      * TelephonyProvider column name for the MNC associated with a SIM.
263      * <P>Type: INTEGER (int)</P>
264      * @hide
265      */
266     public static final String MNC = "mnc";
267
268     /**
269      * TelephonyProvider column name for the sim provisioning status associated with a SIM.
270      * <P>Type: INTEGER (int)</P>
271      * @hide
272      */
273     public static final String SIM_PROVISIONING_STATUS = "sim_provisioning_status";
274
275     /**
276      *  TelephonyProvider column name for extreme threat in CB settings
277      * @hide
278      */
279     public static final String CB_EXTREME_THREAT_ALERT = "enable_cmas_extreme_threat_alerts";
280
281     /**
282      * TelephonyProvider column name for severe threat in CB settings
283      *@hide
284      */
285     public static final String CB_SEVERE_THREAT_ALERT = "enable_cmas_severe_threat_alerts";
286
287     /**
288      * TelephonyProvider column name for amber alert in CB settings
289      *@hide
290      */
291     public static final String CB_AMBER_ALERT = "enable_cmas_amber_alerts";
292
293     /**
294      * TelephonyProvider column name for emergency alert in CB settings
295      *@hide
296      */
297     public static final String CB_EMERGENCY_ALERT = "enable_emergency_alerts";
298
299     /**
300      * TelephonyProvider column name for alert sound duration in CB settings
301      *@hide
302      */
303     public static final String CB_ALERT_SOUND_DURATION = "alert_sound_duration";
304
305     /**
306      * TelephonyProvider column name for alert reminder interval in CB settings
307      *@hide
308      */
309     public static final String CB_ALERT_REMINDER_INTERVAL = "alert_reminder_interval";
310
311     /**
312      * TelephonyProvider column name for enabling vibrate in CB settings
313      *@hide
314      */
315     public static final String CB_ALERT_VIBRATE = "enable_alert_vibrate";
316
317     /**
318      * TelephonyProvider column name for enabling alert speech in CB settings
319      *@hide
320      */
321     public static final String CB_ALERT_SPEECH = "enable_alert_speech";
322
323     /**
324      * TelephonyProvider column name for ETWS test alert in CB settings
325      *@hide
326      */
327     public static final String CB_ETWS_TEST_ALERT = "enable_etws_test_alerts";
328
329     /**
330      * TelephonyProvider column name for enable channel50 alert in CB settings
331      *@hide
332      */
333     public static final String CB_CHANNEL_50_ALERT = "enable_channel_50_alerts";
334
335     /**
336      * TelephonyProvider column name for CMAS test alert in CB settings
337      *@hide
338      */
339     public static final String CB_CMAS_TEST_ALERT= "enable_cmas_test_alerts";
340
341     /**
342      * TelephonyProvider column name for Opt out dialog in CB settings
343      *@hide
344      */
345     public static final String CB_OPT_OUT_DIALOG = "show_cmas_opt_out_dialog";
346
347     /**
348      * Broadcast Action: The user has changed one of the default subs related to
349      * data, phone calls, or sms</p>
350      *
351      * TODO: Change to a listener
352      * @hide
353      */
354     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
355     public static final String SUB_DEFAULT_CHANGED_ACTION =
356         "android.intent.action.SUB_DEFAULT_CHANGED";
357
358     private final Context mContext;
359
360     /**
361      * A listener class for monitoring changes to {@link SubscriptionInfo} records.
362      * <p>
363      * Override the onSubscriptionsChanged method in the object that extends this
364      * class and pass it to {@link #addOnSubscriptionsChangedListener(OnSubscriptionsChangedListener)}
365      * to register your listener and to unregister invoke
366      * {@link #removeOnSubscriptionsChangedListener(OnSubscriptionsChangedListener)}
367      * <p>
368      * Permissions android.Manifest.permission.READ_PHONE_STATE is required
369      * for #onSubscriptionsChanged to be invoked.
370      */
371     public static class OnSubscriptionsChangedListener {
372         private final Handler mHandler  = new Handler() {
373             @Override
374             public void handleMessage(Message msg) {
375                 if (DBG) {
376                     log("handleMessage: invoke the overriden onSubscriptionsChanged()");
377                 }
378                 OnSubscriptionsChangedListener.this.onSubscriptionsChanged();
379             }
380         };
381
382         /**
383          * Callback invoked when there is any change to any SubscriptionInfo. Typically
384          * this method would invoke {@link #getActiveSubscriptionInfoList}
385          */
386         public void onSubscriptionsChanged() {
387             if (DBG) log("onSubscriptionsChanged: NOT OVERRIDDEN");
388         }
389
390         /**
391          * The callback methods need to be called on the handler thread where
392          * this object was created.  If the binder did that for us it'd be nice.
393          */
394         IOnSubscriptionsChangedListener callback = new IOnSubscriptionsChangedListener.Stub() {
395             @Override
396             public void onSubscriptionsChanged() {
397                 if (DBG) log("callback: received, sendEmptyMessage(0) to handler");
398                 mHandler.sendEmptyMessage(0);
399             }
400         };
401
402         private void log(String s) {
403             Rlog.d(LOG_TAG, s);
404         }
405     }
406
407     /** @hide */
408     public SubscriptionManager(Context context) {
409         if (DBG) logd("SubscriptionManager created");
410         mContext = context;
411     }
412
413     /**
414      * Get an instance of the SubscriptionManager from the Context.
415      * This invokes {@link android.content.Context#getSystemService
416      * Context.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE)}.
417      *
418      * @param context to use.
419      * @return SubscriptionManager instance
420      */
421     public static SubscriptionManager from(Context context) {
422         return (SubscriptionManager) context.getSystemService(
423                 Context.TELEPHONY_SUBSCRIPTION_SERVICE);
424     }
425
426     /**
427      * Register for changes to the list of active {@link SubscriptionInfo} records or to the
428      * individual records themselves. When a change occurs the onSubscriptionsChanged method of
429      * the listener will be invoked immediately if there has been a notification.
430      *
431      * @param listener an instance of {@link OnSubscriptionsChangedListener} with
432      *                 onSubscriptionsChanged overridden.
433      */
434     public void addOnSubscriptionsChangedListener(OnSubscriptionsChangedListener listener) {
435         String pkgForDebug = mContext != null ? mContext.getOpPackageName() : "<unknown>";
436         if (DBG) {
437             logd("register OnSubscriptionsChangedListener pkgForDebug=" + pkgForDebug
438                     + " listener=" + listener);
439         }
440         try {
441             // We use the TelephonyRegistry as it runs in the system and thus is always
442             // available. Where as SubscriptionController could crash and not be available
443             ITelephonyRegistry tr = ITelephonyRegistry.Stub.asInterface(ServiceManager.getService(
444                     "telephony.registry"));
445             if (tr != null) {
446                 tr.addOnSubscriptionsChangedListener(pkgForDebug, listener.callback);
447             }
448         } catch (RemoteException ex) {
449             // Should not happen
450         }
451     }
452
453     /**
454      * Unregister the {@link OnSubscriptionsChangedListener}. This is not strictly necessary
455      * as the listener will automatically be unregistered if an attempt to invoke the listener
456      * fails.
457      *
458      * @param listener that is to be unregistered.
459      */
460     public void removeOnSubscriptionsChangedListener(OnSubscriptionsChangedListener listener) {
461         String pkgForDebug = mContext != null ? mContext.getOpPackageName() : "<unknown>";
462         if (DBG) {
463             logd("unregister OnSubscriptionsChangedListener pkgForDebug=" + pkgForDebug
464                     + " listener=" + listener);
465         }
466         try {
467             // We use the TelephonyRegistry as its runs in the system and thus is always
468             // available where as SubscriptionController could crash and not be available
469             ITelephonyRegistry tr = ITelephonyRegistry.Stub.asInterface(ServiceManager.getService(
470                     "telephony.registry"));
471             if (tr != null) {
472                 tr.removeOnSubscriptionsChangedListener(pkgForDebug, listener.callback);
473             }
474         } catch (RemoteException ex) {
475             // Should not happen
476         }
477     }
478
479     /**
480      * Get the active SubscriptionInfo with the input subId.
481      *
482      * @param subId The unique SubscriptionInfo key in database.
483      * @return SubscriptionInfo, maybe null if its not active.
484      */
485     public SubscriptionInfo getActiveSubscriptionInfo(int subId) {
486         if (VDBG) logd("[getActiveSubscriptionInfo]+ subId=" + subId);
487         if (!isValidSubscriptionId(subId)) {
488             if (DBG) {
489                 logd("[getActiveSubscriptionInfo]- invalid subId");
490             }
491             return null;
492         }
493
494         SubscriptionInfo subInfo = null;
495
496         try {
497             ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
498             if (iSub != null) {
499                 subInfo = iSub.getActiveSubscriptionInfo(subId, mContext.getOpPackageName());
500             }
501         } catch (RemoteException ex) {
502             // ignore it
503         }
504
505         return subInfo;
506
507     }
508
509     /**
510      * Get the active SubscriptionInfo associated with the iccId
511      * @param iccId the IccId of SIM card
512      * @return SubscriptionInfo, maybe null if its not active
513      * @hide
514      */
515     public SubscriptionInfo getActiveSubscriptionInfoForIccIndex(String iccId) {
516         if (VDBG) logd("[getActiveSubscriptionInfoForIccIndex]+ iccId=" + iccId);
517         if (iccId == null) {
518             logd("[getActiveSubscriptionInfoForIccIndex]- null iccid");
519             return null;
520         }
521
522         SubscriptionInfo result = null;
523
524         try {
525             ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
526             if (iSub != null) {
527                 result = iSub.getActiveSubscriptionInfoForIccId(iccId, mContext.getOpPackageName());
528             }
529         } catch (RemoteException ex) {
530             // ignore it
531         }
532
533         return result;
534     }
535
536     /**
537      * Get the active SubscriptionInfo associated with the slotIdx
538      * @param slotIdx the slot which the subscription is inserted
539      * @return SubscriptionInfo, maybe null if its not active
540      */
541     public SubscriptionInfo getActiveSubscriptionInfoForSimSlotIndex(int slotIdx) {
542         if (VDBG) logd("[getActiveSubscriptionInfoForSimSlotIndex]+ slotIdx=" + slotIdx);
543         if (!isValidSlotId(slotIdx)) {
544             logd("[getActiveSubscriptionInfoForSimSlotIndex]- invalid slotIdx");
545             return null;
546         }
547
548         SubscriptionInfo result = null;
549
550         try {
551             ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
552             if (iSub != null) {
553                 result = iSub.getActiveSubscriptionInfoForSimSlotIndex(slotIdx,
554                         mContext.getOpPackageName());
555             }
556         } catch (RemoteException ex) {
557             // ignore it
558         }
559
560         return result;
561     }
562
563     /**
564      * @return List of all SubscriptionInfo records in database,
565      * include those that were inserted before, maybe empty but not null.
566      * @hide
567      */
568     public List<SubscriptionInfo> getAllSubscriptionInfoList() {
569         if (VDBG) logd("[getAllSubscriptionInfoList]+");
570
571         List<SubscriptionInfo> result = null;
572
573         try {
574             ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
575             if (iSub != null) {
576                 result = iSub.getAllSubInfoList(mContext.getOpPackageName());
577             }
578         } catch (RemoteException ex) {
579             // ignore it
580         }
581
582         if (result == null) {
583             result = new ArrayList<SubscriptionInfo>();
584         }
585         return result;
586     }
587
588     /**
589      * Get the SubscriptionInfo(s) of the currently inserted SIM(s). The records will be sorted
590      * by {@link SubscriptionInfo#getSimSlotIndex} then by {@link SubscriptionInfo#getSubscriptionId}.
591      *
592      * @return Sorted list of the currently {@link SubscriptionInfo} records available on the device.
593      * <ul>
594      * <li>
595      * If null is returned the current state is unknown but if a {@link OnSubscriptionsChangedListener}
596      * has been registered {@link OnSubscriptionsChangedListener#onSubscriptionsChanged} will be
597      * invoked in the future.
598      * </li>
599      * <li>
600      * If the list is empty then there are no {@link SubscriptionInfo} records currently available.
601      * </li>
602      * <li>
603      * if the list is non-empty the list is sorted by {@link SubscriptionInfo#getSimSlotIndex}
604      * then by {@link SubscriptionInfo#getSubscriptionId}.
605      * </li>
606      * </ul>
607      */
608     public List<SubscriptionInfo> getActiveSubscriptionInfoList() {
609         List<SubscriptionInfo> result = null;
610
611         try {
612             ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
613             if (iSub != null) {
614                 result = iSub.getActiveSubscriptionInfoList(mContext.getOpPackageName());
615             }
616         } catch (RemoteException ex) {
617             // ignore it
618         }
619         return result;
620     }
621
622     /**
623      * @return the count of all subscriptions in the database, this includes
624      * all subscriptions that have been seen.
625      * @hide
626      */
627     public int getAllSubscriptionInfoCount() {
628         if (VDBG) logd("[getAllSubscriptionInfoCount]+");
629
630         int result = 0;
631
632         try {
633             ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
634             if (iSub != null) {
635                 result = iSub.getAllSubInfoCount(mContext.getOpPackageName());
636             }
637         } catch (RemoteException ex) {
638             // ignore it
639         }
640
641         return result;
642     }
643
644     /**
645      * @return the current number of active subscriptions. There is no guarantee the value
646      * returned by this method will be the same as the length of the list returned by
647      * {@link #getActiveSubscriptionInfoList}.
648      */
649     public int getActiveSubscriptionInfoCount() {
650         int result = 0;
651
652         try {
653             ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
654             if (iSub != null) {
655                 result = iSub.getActiveSubInfoCount(mContext.getOpPackageName());
656             }
657         } catch (RemoteException ex) {
658             // ignore it
659         }
660
661         return result;
662     }
663
664     /**
665      * @return the maximum number of active subscriptions that will be returned by
666      * {@link #getActiveSubscriptionInfoList} and the value returned by
667      * {@link #getActiveSubscriptionInfoCount}.
668      */
669     public int getActiveSubscriptionInfoCountMax() {
670         int result = 0;
671
672         try {
673             ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
674             if (iSub != null) {
675                 result = iSub.getActiveSubInfoCountMax();
676             }
677         } catch (RemoteException ex) {
678             // ignore it
679         }
680
681         return result;
682     }
683
684     /**
685      * Add a new SubscriptionInfo to SubscriptionInfo database if needed
686      * @param iccId the IccId of the SIM card
687      * @param slotId the slot which the SIM is inserted
688      * @return the URL of the newly created row or the updated row
689      * @hide
690      */
691     public Uri addSubscriptionInfoRecord(String iccId, int slotId) {
692         if (VDBG) logd("[addSubscriptionInfoRecord]+ iccId:" + iccId + " slotId:" + slotId);
693         if (iccId == null) {
694             logd("[addSubscriptionInfoRecord]- null iccId");
695         }
696         if (!isValidSlotId(slotId)) {
697             logd("[addSubscriptionInfoRecord]- invalid slotId");
698         }
699
700         try {
701             ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
702             if (iSub != null) {
703                 // FIXME: This returns 1 on success, 0 on error should should we return it?
704                 iSub.addSubInfoRecord(iccId, slotId);
705             }
706         } catch (RemoteException ex) {
707             // ignore it
708         }
709
710         // FIXME: Always returns null?
711         return null;
712
713     }
714
715     /**
716      * Set SIM icon tint color by simInfo index
717      * @param tint the RGB value of icon tint color of the SIM
718      * @param subId the unique SubInfoRecord index in database
719      * @return the number of records updated
720      * @hide
721      */
722     public int setIconTint(int tint, int subId) {
723         if (VDBG) logd("[setIconTint]+ tint:" + tint + " subId:" + subId);
724         if (!isValidSubscriptionId(subId)) {
725             logd("[setIconTint]- fail");
726             return -1;
727         }
728
729         int result = 0;
730
731         try {
732             ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
733             if (iSub != null) {
734                 result = iSub.setIconTint(tint, subId);
735             }
736         } catch (RemoteException ex) {
737             // ignore it
738         }
739
740         return result;
741
742     }
743
744     /**
745      * Set display name by simInfo index
746      * @param displayName the display name of SIM card
747      * @param subId the unique SubscriptionInfo index in database
748      * @return the number of records updated
749      * @hide
750      */
751     public int setDisplayName(String displayName, int subId) {
752         return setDisplayName(displayName, subId, NAME_SOURCE_UNDEFINDED);
753     }
754
755     /**
756      * Set display name by simInfo index with name source
757      * @param displayName the display name of SIM card
758      * @param subId the unique SubscriptionInfo index in database
759      * @param nameSource 0: NAME_SOURCE_DEFAULT_SOURCE, 1: NAME_SOURCE_SIM_SOURCE,
760      *                   2: NAME_SOURCE_USER_INPUT, -1 NAME_SOURCE_UNDEFINED
761      * @return the number of records updated or < 0 if invalid subId
762      * @hide
763      */
764     public int setDisplayName(String displayName, int subId, long nameSource) {
765         if (VDBG) {
766             logd("[setDisplayName]+  displayName:" + displayName + " subId:" + subId
767                     + " nameSource:" + nameSource);
768         }
769         if (!isValidSubscriptionId(subId)) {
770             logd("[setDisplayName]- fail");
771             return -1;
772         }
773
774         int result = 0;
775
776         try {
777             ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
778             if (iSub != null) {
779                 result = iSub.setDisplayNameUsingSrc(displayName, subId, nameSource);
780             }
781         } catch (RemoteException ex) {
782             // ignore it
783         }
784
785         return result;
786
787     }
788
789     /**
790      * Set phone number by subId
791      * @param number the phone number of the SIM
792      * @param subId the unique SubscriptionInfo index in database
793      * @return the number of records updated
794      * @hide
795      */
796     public int setDisplayNumber(String number, int subId) {
797         if (number == null || !isValidSubscriptionId(subId)) {
798             logd("[setDisplayNumber]- fail");
799             return -1;
800         }
801
802         int result = 0;
803
804         try {
805             ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
806             if (iSub != null) {
807                 result = iSub.setDisplayNumber(number, subId);
808             }
809         } catch (RemoteException ex) {
810             // ignore it
811         }
812
813         return result;
814
815     }
816
817     /**
818      * Set data roaming by simInfo index
819      * @param roaming 0:Don't allow data when roaming, 1:Allow data when roaming
820      * @param subId the unique SubscriptionInfo index in database
821      * @return the number of records updated
822      * @hide
823      */
824     public int setDataRoaming(int roaming, int subId) {
825         if (VDBG) logd("[setDataRoaming]+ roaming:" + roaming + " subId:" + subId);
826         if (roaming < 0 || !isValidSubscriptionId(subId)) {
827             logd("[setDataRoaming]- fail");
828             return -1;
829         }
830
831         int result = 0;
832
833         try {
834             ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
835             if (iSub != null) {
836                 result = iSub.setDataRoaming(roaming, subId);
837             }
838         } catch (RemoteException ex) {
839             // ignore it
840         }
841
842         return result;
843     }
844
845     /**
846      * Set Sim Provisioning Status by subscription ID
847      * @param simProvisioningStatus with the subscription
848      * {@See SubscriptionManager#SIM_PROVISIONED}
849      * {@See SubscriptionManager#SIM_UNPROVISIONED_COLD}
850      * {@See SubscriptionManager#SIM_UNPROVISIONED_OUT_OF_CREDIT}
851      * @param subId the unique SubInfoRecord index in database
852      * @return the number of records updated
853      * Permissions android.Manifest.permission.MODIFY_PHONE_STATE is required
854      * @hide
855      */
856     public int setSimProvisioningStatus(int simProvisioningStatus, int subId) {
857         if (VDBG) {
858             logd("[setSimProvisioningStatus]+ status:" + simProvisioningStatus + " subId:" + subId);
859         }
860         if (simProvisioningStatus < 0 || simProvisioningStatus > MAX_SIM_PROVISIONING_STATUS ||
861                 !isValidSubscriptionId(subId)) {
862             logd("[setSimProvisioningStatus]- fail");
863             return -1;
864         }
865
866         int result = 0;
867
868         try {
869             ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
870             if (iSub != null) {
871                 result = iSub.setSimProvisioningStatus(simProvisioningStatus, subId);
872             }
873         } catch (RemoteException ex) {
874             // ignore it
875         }
876         return result;
877     }
878
879     /**
880      * Get slotId associated with the subscription.
881      * @return slotId as a positive integer or a negative value if an error either
882      * SIM_NOT_INSERTED or < 0 if an invalid slot index
883      * @hide
884      */
885     public static int getSlotId(int subId) {
886         if (!isValidSubscriptionId(subId)) {
887             if (DBG) {
888                 logd("[getSlotId]- fail");
889             }
890         }
891
892         int result = INVALID_SIM_SLOT_INDEX;
893
894         try {
895             ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
896             if (iSub != null) {
897                 result = iSub.getSlotId(subId);
898             }
899         } catch (RemoteException ex) {
900             // ignore it
901         }
902
903         return result;
904
905     }
906
907     /** @hide */
908     public static int[] getSubId(int slotId) {
909         if (!isValidSlotId(slotId)) {
910             logd("[getSubId]- fail");
911             return null;
912         }
913
914         int[] subId = null;
915
916         try {
917             ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
918             if (iSub != null) {
919                 subId = iSub.getSubId(slotId);
920             }
921         } catch (RemoteException ex) {
922             // ignore it
923         }
924
925         return subId;
926     }
927
928     /** @hide */
929     public static int getPhoneId(int subId) {
930         if (!isValidSubscriptionId(subId)) {
931             if (DBG) {
932                 logd("[getPhoneId]- fail");
933             }
934             return INVALID_PHONE_INDEX;
935         }
936
937         int result = INVALID_PHONE_INDEX;
938
939         try {
940             ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
941             if (iSub != null) {
942                 result = iSub.getPhoneId(subId);
943             }
944         } catch (RemoteException ex) {
945             // ignore it
946         }
947
948         if (VDBG) logd("[getPhoneId]- phoneId=" + result);
949         return result;
950
951     }
952
953     private static void logd(String msg) {
954         Rlog.d(LOG_TAG, msg);
955     }
956
957     /**
958      * Returns the system's default subscription id.
959      *
960      * For a voice capable device, it will return getDefaultVoiceSubscriptionId.
961      * For a data only device, it will return the getDefaultDataSubscriptionId.
962      * May return an INVALID_SUBSCRIPTION_ID on error.
963      *
964      * @return the "system" default subscription id.
965      */
966     public static int getDefaultSubscriptionId() {
967         int subId = INVALID_SUBSCRIPTION_ID;
968
969         try {
970             ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
971             if (iSub != null) {
972                 subId = iSub.getDefaultSubId();
973             }
974         } catch (RemoteException ex) {
975             // ignore it
976         }
977
978         if (VDBG) logd("getDefaultSubId=" + subId);
979         return subId;
980     }
981
982     /**
983      * Returns the system's default voice subscription id.
984      *
985      * On a data only device or on error, will return INVALID_SUBSCRIPTION_ID.
986      *
987      * @return the default voice subscription Id.
988      */
989     public static int getDefaultVoiceSubscriptionId() {
990         int subId = INVALID_SUBSCRIPTION_ID;
991
992         try {
993             ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
994             if (iSub != null) {
995                 subId = iSub.getDefaultVoiceSubId();
996             }
997         } catch (RemoteException ex) {
998             // ignore it
999         }
1000
1001         if (VDBG) logd("getDefaultVoiceSubscriptionId, sub id = " + subId);
1002         return subId;
1003     }
1004
1005     /** @hide */
1006     public void setDefaultVoiceSubId(int subId) {
1007         if (VDBG) logd("setDefaultVoiceSubId sub id = " + subId);
1008         try {
1009             ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
1010             if (iSub != null) {
1011                 iSub.setDefaultVoiceSubId(subId);
1012             }
1013         } catch (RemoteException ex) {
1014             // ignore it
1015         }
1016     }
1017
1018     /**
1019      * Return the SubscriptionInfo for default voice subscription.
1020      *
1021      * Will return null on data only devices, or on error.
1022      *
1023      * @return the SubscriptionInfo for the default voice subscription.
1024      * @hide
1025      */
1026     public SubscriptionInfo getDefaultVoiceSubscriptionInfo() {
1027         return getActiveSubscriptionInfo(getDefaultVoiceSubscriptionId());
1028     }
1029
1030     /** @hide */
1031     public static int getDefaultVoicePhoneId() {
1032         return getPhoneId(getDefaultVoiceSubscriptionId());
1033     }
1034
1035     /**
1036      * Returns the system's default SMS subscription id.
1037      *
1038      * On a data only device or on error, will return INVALID_SUBSCRIPTION_ID.
1039      *
1040      * @return the default SMS subscription Id.
1041      */
1042     public static int getDefaultSmsSubscriptionId() {
1043         int subId = INVALID_SUBSCRIPTION_ID;
1044
1045         try {
1046             ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
1047             if (iSub != null) {
1048                 subId = iSub.getDefaultSmsSubId();
1049             }
1050         } catch (RemoteException ex) {
1051             // ignore it
1052         }
1053
1054         if (VDBG) logd("getDefaultSmsSubscriptionId, sub id = " + subId);
1055         return subId;
1056     }
1057
1058     /** @hide */
1059     public void setDefaultSmsSubId(int subId) {
1060         if (VDBG) logd("setDefaultSmsSubId sub id = " + subId);
1061         try {
1062             ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
1063             if (iSub != null) {
1064                 iSub.setDefaultSmsSubId(subId);
1065             }
1066         } catch (RemoteException ex) {
1067             // ignore it
1068         }
1069     }
1070
1071     /**
1072      * Return the SubscriptionInfo for default voice subscription.
1073      *
1074      * Will return null on data only devices, or on error.
1075      *
1076      * @return the SubscriptionInfo for the default SMS subscription.
1077      * @hide
1078      */
1079     public SubscriptionInfo getDefaultSmsSubscriptionInfo() {
1080         return getActiveSubscriptionInfo(getDefaultSmsSubscriptionId());
1081     }
1082
1083     /** @hide */
1084     public int getDefaultSmsPhoneId() {
1085         return getPhoneId(getDefaultSmsSubscriptionId());
1086     }
1087
1088     /**
1089      * Returns the system's default data subscription id.
1090      *
1091      * On a voice only device or on error, will return INVALID_SUBSCRIPTION_ID.
1092      *
1093      * @return the default data subscription Id.
1094      */
1095     public static int getDefaultDataSubscriptionId() {
1096         int subId = INVALID_SUBSCRIPTION_ID;
1097
1098         try {
1099             ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
1100             if (iSub != null) {
1101                 subId = iSub.getDefaultDataSubId();
1102             }
1103         } catch (RemoteException ex) {
1104             // ignore it
1105         }
1106
1107         if (VDBG) logd("getDefaultDataSubscriptionId, sub id = " + subId);
1108         return subId;
1109     }
1110
1111     /** @hide */
1112     public void setDefaultDataSubId(int subId) {
1113         if (VDBG) logd("setDataSubscription sub id = " + subId);
1114         try {
1115             ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
1116             if (iSub != null) {
1117                 iSub.setDefaultDataSubId(subId);
1118             }
1119         } catch (RemoteException ex) {
1120             // ignore it
1121         }
1122     }
1123
1124     /**
1125      * Return the SubscriptionInfo for default data subscription.
1126      *
1127      * Will return null on voice only devices, or on error.
1128      *
1129      * @return the SubscriptionInfo for the default data subscription.
1130      * @hide
1131      */
1132     public SubscriptionInfo getDefaultDataSubscriptionInfo() {
1133         return getActiveSubscriptionInfo(getDefaultDataSubscriptionId());
1134     }
1135
1136     /** @hide */
1137     public int getDefaultDataPhoneId() {
1138         return getPhoneId(getDefaultDataSubscriptionId());
1139     }
1140
1141     /** @hide */
1142     public void clearSubscriptionInfo() {
1143         try {
1144             ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
1145             if (iSub != null) {
1146                 iSub.clearSubInfo();
1147             }
1148         } catch (RemoteException ex) {
1149             // ignore it
1150         }
1151
1152         return;
1153     }
1154
1155     //FIXME this is vulnerable to race conditions
1156     /** @hide */
1157     public boolean allDefaultsSelected() {
1158         if (!isValidSubscriptionId(getDefaultDataSubscriptionId())) {
1159             return false;
1160         }
1161         if (!isValidSubscriptionId(getDefaultSmsSubscriptionId())) {
1162             return false;
1163         }
1164         if (!isValidSubscriptionId(getDefaultVoiceSubscriptionId())) {
1165             return false;
1166         }
1167         return true;
1168     }
1169
1170     /**
1171      * If a default is set to subscription which is not active, this will reset that default back to
1172      * an invalid subscription id, i.e. < 0.
1173      * @hide
1174      */
1175     public void clearDefaultsForInactiveSubIds() {
1176         if (VDBG) logd("clearDefaultsForInactiveSubIds");
1177         try {
1178             ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
1179             if (iSub != null) {
1180                 iSub.clearDefaultsForInactiveSubIds();
1181             }
1182         } catch (RemoteException ex) {
1183             // ignore it
1184         }
1185     }
1186
1187     /**
1188      * @return true if a valid subId else false
1189      * @hide
1190      */
1191     public static boolean isValidSubscriptionId(int subId) {
1192         return subId > INVALID_SUBSCRIPTION_ID ;
1193     }
1194
1195     /**
1196      * @return true if subId is an usable subId value else false. A
1197      * usable subId means its neither a INVALID_SUBSCRIPTION_ID nor a DEFAULT_SUB_ID.
1198      * @hide
1199      */
1200     public static boolean isUsableSubIdValue(int subId) {
1201         return subId >= MIN_SUBSCRIPTION_ID_VALUE && subId <= MAX_SUBSCRIPTION_ID_VALUE;
1202     }
1203
1204     /** @hide */
1205     public static boolean isValidSlotId(int slotId) {
1206         return slotId >= 0 && slotId < TelephonyManager.getDefault().getSimCount();
1207     }
1208
1209     /** @hide */
1210     public static boolean isValidPhoneId(int phoneId) {
1211         return phoneId >= 0 && phoneId < TelephonyManager.getDefault().getPhoneCount();
1212     }
1213
1214     /** @hide */
1215     public static void putPhoneIdAndSubIdExtra(Intent intent, int phoneId) {
1216         int[] subIds = SubscriptionManager.getSubId(phoneId);
1217         if (subIds != null && subIds.length > 0) {
1218             putPhoneIdAndSubIdExtra(intent, phoneId, subIds[0]);
1219         } else {
1220             logd("putPhoneIdAndSubIdExtra: no valid subs");
1221         }
1222     }
1223
1224     /** @hide */
1225     public static void putPhoneIdAndSubIdExtra(Intent intent, int phoneId, int subId) {
1226         if (VDBG) logd("putPhoneIdAndSubIdExtra: phoneId=" + phoneId + " subId=" + subId);
1227         intent.putExtra(PhoneConstants.SUBSCRIPTION_KEY, subId);
1228         intent.putExtra(PhoneConstants.PHONE_KEY, phoneId);
1229         //FIXME this is using phoneId and slotId interchangeably
1230         //Eventually, this should be removed as it is not the slot id
1231         intent.putExtra(PhoneConstants.SLOT_KEY, phoneId);
1232     }
1233
1234     /**
1235      * @return the list of subId's that are active,
1236      *         is never null but the length maybe 0.
1237      * @hide
1238      */
1239     public @NonNull int[] getActiveSubscriptionIdList() {
1240         int[] subId = null;
1241
1242         try {
1243             ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
1244             if (iSub != null) {
1245                 subId = iSub.getActiveSubIdList();
1246             }
1247         } catch (RemoteException ex) {
1248             // ignore it
1249         }
1250
1251         if (subId == null) {
1252             subId = new int[0];
1253         }
1254
1255         return subId;
1256
1257     }
1258
1259     /**
1260      * Returns true if the device is considered roaming on the current
1261      * network for a subscription.
1262      * <p>
1263      * Availability: Only when user registered to a network.
1264      *
1265      * @param subId The subscription ID
1266      * @return true if the network for the subscription is roaming, false otherwise
1267      */
1268     public boolean isNetworkRoaming(int subId) {
1269         final int phoneId = getPhoneId(subId);
1270         if (phoneId < 0) {
1271             // What else can we do?
1272             return false;
1273         }
1274         return TelephonyManager.getDefault().isNetworkRoaming(subId);
1275     }
1276
1277     /**
1278      * Returns a constant indicating the state of sim for the slot idx.
1279      *
1280      * @param slotIdx
1281      *
1282      * {@See TelephonyManager#SIM_STATE_UNKNOWN}
1283      * {@See TelephonyManager#SIM_STATE_ABSENT}
1284      * {@See TelephonyManager#SIM_STATE_PIN_REQUIRED}
1285      * {@See TelephonyManager#SIM_STATE_PUK_REQUIRED}
1286      * {@See TelephonyManager#SIM_STATE_NETWORK_LOCKED}
1287      * {@See TelephonyManager#SIM_STATE_READY}
1288      * {@See TelephonyManager#SIM_STATE_NOT_READY}
1289      * {@See TelephonyManager#SIM_STATE_PERM_DISABLED}
1290      * {@See TelephonyManager#SIM_STATE_CARD_IO_ERROR}
1291      *
1292      * {@hide}
1293      */
1294     public static int getSimStateForSlotIdx(int slotIdx) {
1295         int simState = TelephonyManager.SIM_STATE_UNKNOWN;
1296
1297         try {
1298             ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
1299             if (iSub != null) {
1300                 simState = iSub.getSimStateForSlotIdx(slotIdx);
1301             }
1302         } catch (RemoteException ex) {
1303         }
1304
1305         return simState;
1306     }
1307
1308     /**
1309      * Store properties associated with SubscriptionInfo in database
1310      * @param subId Subscription Id of Subscription
1311      * @param propKey Column name in database associated with SubscriptionInfo
1312      * @param propValue Value to store in DB for particular subId & column name
1313      * @hide
1314      */
1315     public static void setSubscriptionProperty(int subId, String propKey, String propValue) {
1316         try {
1317             ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
1318             if (iSub != null) {
1319                 iSub.setSubscriptionProperty(subId, propKey, propValue);
1320             }
1321         } catch (RemoteException ex) {
1322             // ignore it
1323         }
1324     }
1325
1326     /**
1327      * Store properties associated with SubscriptionInfo in database
1328      * @param subId Subscription Id of Subscription
1329      * @param propKey Column name in SubscriptionInfo database
1330      * @return Value associated with subId and propKey column in database
1331      * @hide
1332      */
1333     private static String getSubscriptionProperty(int subId, String propKey,
1334             Context context) {
1335         String resultValue = null;
1336         try {
1337             ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
1338             if (iSub != null) {
1339                 resultValue = iSub.getSubscriptionProperty(subId, propKey, 
1340                     context.getOpPackageName());
1341             }
1342         } catch (RemoteException ex) {
1343             // ignore it
1344         }
1345         return resultValue;
1346     }
1347
1348     /**
1349      * Returns boolean value corresponding to query result.
1350      * @param subId Subscription Id of Subscription
1351      * @param propKey Column name in SubscriptionInfo database
1352      * @param defValue Default boolean value to be returned
1353      * @return boolean result value to be returned
1354      * @hide
1355      */
1356     public static boolean getBooleanSubscriptionProperty(int subId, String propKey,
1357             boolean defValue, Context context) {
1358         String result = getSubscriptionProperty(subId, propKey, context);
1359         if (result != null) {
1360             try {
1361                 return Integer.parseInt(result) == 1;
1362             } catch (NumberFormatException err) {
1363                 logd("getBooleanSubscriptionProperty NumberFormat exception");
1364             }
1365         }
1366         return defValue;
1367     }
1368
1369     /**
1370      * Returns integer value corresponding to query result.
1371      * @param subId Subscription Id of Subscription
1372      * @param propKey Column name in SubscriptionInfo database
1373      * @param defValue Default integer value to be returned
1374      * @return integer result value to be returned
1375      * @hide
1376      */
1377     public static int getIntegerSubscriptionProperty(int subId, String propKey, int defValue,
1378             Context context) {
1379         String result = getSubscriptionProperty(subId, propKey, context);
1380         if (result != null) {
1381             try {
1382                 return Integer.parseInt(result);
1383             } catch (NumberFormatException err) {
1384                 logd("getBooleanSubscriptionProperty NumberFormat exception");
1385             }
1386         }
1387         return defValue;
1388     }
1389
1390     /**
1391      * Returns the resources associated with Subscription.
1392      * @param context Context object
1393      * @param subId Subscription Id of Subscription who's resources are required
1394      * @return Resources associated with Subscription.
1395      * @hide
1396      */
1397     public static Resources getResourcesForSubId(Context context, int subId) {
1398         final SubscriptionInfo subInfo =
1399                 SubscriptionManager.from(context).getActiveSubscriptionInfo(subId);
1400
1401         Configuration config = context.getResources().getConfiguration();
1402         Configuration newConfig = new Configuration();
1403         newConfig.setTo(config);
1404         if (subInfo != null) {
1405             newConfig.mcc = subInfo.getMcc();
1406             newConfig.mnc = subInfo.getMnc();
1407             if (newConfig.mnc == 0) newConfig.mnc = Configuration.MNC_ZERO;
1408         }
1409         DisplayMetrics metrics = context.getResources().getDisplayMetrics();
1410         DisplayMetrics newMetrics = new DisplayMetrics();
1411         newMetrics.setTo(metrics);
1412         return new Resources(context.getResources().getAssets(), newMetrics, newConfig);
1413     }
1414
1415     /**
1416      * @return true if the sub ID is active. i.e. The sub ID corresponds to a known subscription
1417      * and the SIM providing the subscription is present in a slot and in "LOADED" state.
1418      * @hide
1419      */
1420     public boolean isActiveSubId(int subId) {
1421         try {
1422             ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
1423             if (iSub != null) {
1424                 return iSub.isActiveSubId(subId);
1425             }
1426         } catch (RemoteException ex) {
1427         }
1428         return false;
1429     }
1430 }