1 /*
2  * Copyright (C) 2021 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.phone;
18 
19 import android.content.Context;
20 import android.content.SharedPreferences;
21 import android.os.PersistableBundle;
22 import android.preference.PreferenceManager;
23 import android.telephony.ims.ProvisioningManager;
24 import android.telephony.ims.feature.ImsFeature;
25 import android.telephony.ims.feature.MmTelFeature;
26 import android.telephony.ims.stub.ImsRegistrationImplBase;
27 import android.util.Log;
28 import android.util.SparseArray;
29 
30 import com.android.internal.annotations.VisibleForTesting;
31 
32 import java.io.File;
33 import java.io.FileInputStream;
34 import java.io.FileNotFoundException;
35 import java.io.FileOutputStream;
36 import java.io.IOException;
37 
38 /**
39  * Provides a function to set/get Ims feature provisioning status in storage.
40  */
41 public class ImsProvisioningLoader {
42     private static final String LOG_TAG = ImsProvisioningLoader.class.getSimpleName();
43 
44     public static final int STATUS_NOT_SET = -1;
45     public static final int STATUS_NOT_PROVISIONED =
46             ProvisioningManager.PROVISIONING_VALUE_DISABLED;
47     public static final int STATUS_PROVISIONED =
48             ProvisioningManager.PROVISIONING_VALUE_ENABLED;
49 
50     public static final int IMS_FEATURE_MMTEL = ImsFeature.FEATURE_MMTEL;
51     public static final int IMS_FEATURE_RCS = ImsFeature.FEATURE_RCS;
52 
53     private static final String PROVISIONING_FILE_NAME_PREF = "imsprovisioningstatus_";
54     private static final String PREF_PROVISION_IMS_MMTEL_PREFIX = "provision_ims_mmtel_";
55 
56     private Context mContext;
57     private SharedPreferences mTelephonySharedPreferences;
58     // key : sub Id, value : read from sub Id's xml and it's in-memory cache
59     private SparseArray<PersistableBundle> mSubIdBundleArray = new SparseArray<>();
60     private final Object mLock = new Object();
61 
ImsProvisioningLoader(Context context)62     public ImsProvisioningLoader(Context context) {
63         mContext = context;
64         mTelephonySharedPreferences =
65                 PreferenceManager.getDefaultSharedPreferences(context);
66     }
67 
68     /**
69      * Get Ims feature provisioned status in storage
70      */
getProvisioningStatus(int subId, @ImsFeature.FeatureType int imsFeature, int capability, @ImsRegistrationImplBase.ImsRegistrationTech int tech)71     public int getProvisioningStatus(int subId, @ImsFeature.FeatureType int imsFeature,
72             int capability, @ImsRegistrationImplBase.ImsRegistrationTech int tech) {
73         initCache(subId);
74         return getImsProvisioningStatus(subId, imsFeature, tech,
75                 capability);
76     }
77 
78     /**
79      * Set Ims feature provisioned status in storage
80      */
setProvisioningStatus(int subId, @ImsFeature.FeatureType int imsFeature, int capability, @ImsRegistrationImplBase.ImsRegistrationTech int tech, boolean isProvisioned)81     public boolean setProvisioningStatus(int subId, @ImsFeature.FeatureType int imsFeature,
82             int capability, @ImsRegistrationImplBase.ImsRegistrationTech int tech,
83             boolean isProvisioned) {
84         initCache(subId);
85         return setImsFeatureProvisioning(subId, imsFeature, tech, capability,
86                 isProvisioned);
87     }
88 
isFileExist(int subId)89     private boolean isFileExist(int subId) {
90         File file = new File(mContext.getFilesDir(), getFileName(subId));
91         return file.exists();
92     }
93 
initCache(int subId)94     private void initCache(int subId) {
95         synchronized (mLock) {
96             PersistableBundle subIdBundle = mSubIdBundleArray.get(subId, null);
97             if (subIdBundle != null) {
98                 // initCache() has already been called for the subId
99                 return;
100             }
101             if (isFileExist(subId)) {
102                 subIdBundle = readSubIdBundleFromXml(subId);
103             } else {
104                 // It should read the MMTEL capability cache as part of shared prefs and migrate
105                 // over any configs for UT.
106                 final int[] regTech = {ImsRegistrationImplBase.REGISTRATION_TECH_LTE,
107                         ImsRegistrationImplBase.REGISTRATION_TECH_IWLAN,
108                         ImsRegistrationImplBase.REGISTRATION_TECH_CROSS_SIM,
109                         ImsRegistrationImplBase.REGISTRATION_TECH_NR};
110                 subIdBundle = new PersistableBundle();
111                 for (int tech : regTech) {
112                     int UtProvisioningStatus = getUTProvisioningStatus(subId, tech);
113                     logd("check UT provisioning status " + UtProvisioningStatus);
114 
115                     if (STATUS_PROVISIONED == UtProvisioningStatus) {
116                         setProvisioningStatusToSubIdBundle(ImsFeature.FEATURE_MMTEL, tech,
117                                 MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_UT, subIdBundle,
118                                 UtProvisioningStatus);
119                     }
120                 }
121                 saveSubIdBundleToXml(subId, subIdBundle);
122             }
123             mSubIdBundleArray.put(subId, subIdBundle);
124         }
125     }
126 
getImsProvisioningStatus(int subId, int imsFeature, int tech, int capability)127     private int getImsProvisioningStatus(int subId, int imsFeature, int tech, int capability) {
128         PersistableBundle subIdBundle = null;
129         synchronized (mLock) {
130             subIdBundle = mSubIdBundleArray.get(subId, null);
131         }
132 
133         return getProvisioningStatusFromSubIdBundle(imsFeature, tech,
134                 capability, subIdBundle);
135     }
136 
setImsFeatureProvisioning(int subId, int imsFeature, int tech, int capability, boolean isProvisioned)137     private boolean setImsFeatureProvisioning(int subId, int imsFeature, int tech, int capability,
138             boolean isProvisioned) {
139         synchronized (mLock) {
140             int preValue = getImsProvisioningStatus(subId, imsFeature, tech, capability);
141             int newValue = isProvisioned ? STATUS_PROVISIONED : STATUS_NOT_PROVISIONED;
142             if (preValue == newValue) {
143                 logd("already stored provisioning status " + isProvisioned + " ImsFeature "
144                         + imsFeature + " tech " + tech + " capa " + capability);
145                 return false;
146             }
147 
148             PersistableBundle subIdBundle = mSubIdBundleArray.get(subId, null);
149             setProvisioningStatusToSubIdBundle(imsFeature, tech, capability, subIdBundle,
150                     newValue);
151             saveSubIdBundleToXml(subId, subIdBundle);
152         }
153         return true;
154     }
155 
getProvisioningStatusFromSubIdBundle(int imsFeature, int tech, int capability, PersistableBundle subIdBundle)156     private int getProvisioningStatusFromSubIdBundle(int imsFeature, int tech,
157             int capability, PersistableBundle subIdBundle) {
158         // If it doesn't exist in xml, return STATUS_NOT_SET
159         if (subIdBundle == null || subIdBundle.isEmpty()) {
160             logd("xml is empty");
161             return STATUS_NOT_SET;
162         }
163 
164         PersistableBundle regTechBundle = subIdBundle.getPersistableBundle(
165                 String.valueOf(imsFeature));
166         if (regTechBundle == null) {
167             logd("ImsFeature " + imsFeature + " is not exist in xml");
168             return STATUS_NOT_SET;
169         }
170 
171         PersistableBundle capabilityBundle = regTechBundle.getPersistableBundle(
172                 String.valueOf(tech));
173         if (capabilityBundle == null) {
174             logd("RegistrationTech " + tech + " is not exist in xml");
175             return STATUS_NOT_SET;
176         }
177 
178         return getIntValueFromBundle(String.valueOf(capability), capabilityBundle);
179     }
180 
setProvisioningStatusToSubIdBundle(int imsFeature, int tech, int capability, PersistableBundle subIdBundle, int newStatus)181     private void setProvisioningStatusToSubIdBundle(int imsFeature, int tech,
182             int capability, PersistableBundle subIdBundle, int newStatus) {
183         logd("set provisioning status " + newStatus + " ImsFeature "
184                 + imsFeature + " tech " + tech + " capa " + capability);
185 
186         PersistableBundle regTechBundle = subIdBundle.getPersistableBundle(
187                 String.valueOf(imsFeature));
188         if (regTechBundle == null) {
189             regTechBundle = new PersistableBundle();
190             subIdBundle.putPersistableBundle(String.valueOf(imsFeature), regTechBundle);
191         }
192 
193         PersistableBundle capabilityBundle = regTechBundle.getPersistableBundle(
194                 String.valueOf(tech));
195         if (capabilityBundle == null) {
196             capabilityBundle = new PersistableBundle();
197             regTechBundle.putPersistableBundle(String.valueOf(tech), capabilityBundle);
198         }
199 
200         capabilityBundle.putInt(String.valueOf(capability), newStatus);
201     }
202 
203     // Default value is STATUS_NOT_SET
getIntValueFromBundle(String key, PersistableBundle bundle)204     private int getIntValueFromBundle(String key, PersistableBundle bundle) {
205         int value = bundle.getInt(key, STATUS_NOT_SET);
206         logd("get value " + value);
207         return value;
208     }
209 
210     // Return subIdBundle from imsprovisioningstatus_{subId}.xml
readSubIdBundleFromXml(int subId)211     private PersistableBundle readSubIdBundleFromXml(int subId) {
212         String fileName = getFileName(subId);
213 
214         PersistableBundle subIdBundles = new PersistableBundle();
215         File file = null;
216         FileInputStream inFile = null;
217         synchronized (mLock) {
218             try {
219                 file = new File(mContext.getFilesDir(), fileName);
220                 inFile = new FileInputStream(file);
221                 subIdBundles = PersistableBundle.readFromStream(inFile);
222                 inFile.close();
223             } catch (FileNotFoundException e) {
224                 logd(e.toString());
225             } catch (IOException e) {
226                 loge(e.toString());
227             } catch (RuntimeException e) {
228                 loge(e.toString());
229             }
230         }
231 
232         return subIdBundles;
233     }
234 
saveSubIdBundleToXml(int subId, PersistableBundle subIdBundle)235     private void saveSubIdBundleToXml(int subId, PersistableBundle subIdBundle) {
236         String fileName = getFileName(subId);
237 
238         if (subIdBundle == null || subIdBundle.isEmpty()) {
239             logd("subIdBundle is empty");
240             return;
241         }
242 
243         FileOutputStream outFile = null;
244         synchronized (mLock) {
245             try {
246                 outFile = new FileOutputStream(new File(mContext.getFilesDir(), fileName));
247                 subIdBundle.writeToStream(outFile);
248                 outFile.flush();
249                 outFile.close();
250             } catch (IOException e) {
251                 loge(e.toString());
252             } catch (RuntimeException e) {
253                 loge(e.toString());
254             }
255         }
256     }
257 
getUTProvisioningStatus(int subId, int tech)258     private int getUTProvisioningStatus(int subId, int tech) {
259         return getMmTelCapabilityProvisioningBitfield(subId, tech) > 0 ? STATUS_PROVISIONED
260                 : STATUS_NOT_SET;
261     }
262 
263     /**
264      * @return the bitfield containing the MmTel provisioning for the provided subscription and
265      * technology. The bitfield should mirror the bitfield defined by
266      * {@link MmTelFeature.MmTelCapabilities.MmTelCapability}.
267      */
getMmTelCapabilityProvisioningBitfield(int subId, int tech)268     private int getMmTelCapabilityProvisioningBitfield(int subId, int tech) {
269         String key = getMmTelProvisioningKey(subId, tech);
270         // Default is no capabilities are provisioned.
271         return mTelephonySharedPreferences.getInt(key, 0 /*default*/);
272     }
273 
getMmTelProvisioningKey(int subId, int tech)274     private String getMmTelProvisioningKey(int subId, int tech) {
275         // Resulting key is provision_ims_mmtel_{subId}_{tech}
276         return PREF_PROVISION_IMS_MMTEL_PREFIX + subId + "_" + tech;
277     }
278 
getFileName(int subId)279     private String getFileName(int subId) {
280         // Resulting name is imsprovisioningstatus_{subId}.xml
281         return PROVISIONING_FILE_NAME_PREF + subId + ".xml";
282     }
283 
284     @VisibleForTesting
clear()285     void clear() {
286         synchronized (mLock) {
287             mSubIdBundleArray.clear();
288         }
289     }
290 
291     @VisibleForTesting
setProvisioningToXml(int subId, PersistableBundle subIdBundle, String[] infoArray)292     void setProvisioningToXml(int subId, PersistableBundle subIdBundle,
293             String[] infoArray) {
294         for (String info : infoArray) {
295             String[] paramArray = info.split(",");
296             setProvisioningStatusToSubIdBundle(Integer.valueOf(paramArray[0]),
297                     Integer.valueOf(paramArray[1]), Integer.valueOf(paramArray[2]),
298                     subIdBundle, Integer.valueOf(paramArray[3]));
299         }
300         saveSubIdBundleToXml(subId, subIdBundle);
301     }
302 
loge(String contents)303     private void loge(String contents) {
304         Log.e(LOG_TAG, contents);
305     }
306 
logd(String contents)307     private void logd(String contents) {
308         Log.d(LOG_TAG, contents);
309     }
310 
311 }
312