1 /*
2  * Copyright (C) 2015 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.internal.telephony;
18 
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.content.Context;
22 import android.content.res.XmlResourceParser;
23 import android.database.Cursor;
24 import android.os.Handler;
25 import android.os.IDeviceIdleController;
26 import android.os.Looper;
27 import android.os.ServiceManager;
28 import android.system.ErrnoException;
29 import android.system.Os;
30 import android.system.OsConstants;
31 import android.system.StructStatVfs;
32 import android.telephony.AccessNetworkConstants.TransportType;
33 import android.telephony.Rlog;
34 import android.text.TextUtils;
35 
36 import com.android.internal.telephony.cdma.CdmaSubscriptionSourceManager;
37 import com.android.internal.telephony.cdma.EriManager;
38 import com.android.internal.telephony.dataconnection.DataEnabledSettings;
39 import com.android.internal.telephony.dataconnection.DcTracker;
40 import com.android.internal.telephony.dataconnection.TransportManager;
41 import com.android.internal.telephony.emergency.EmergencyNumberTracker;
42 import com.android.internal.telephony.imsphone.ImsExternalCallTracker;
43 import com.android.internal.telephony.imsphone.ImsPhone;
44 import com.android.internal.telephony.imsphone.ImsPhoneCallTracker;
45 import com.android.internal.telephony.uicc.IccCardStatus;
46 import com.android.internal.telephony.uicc.UiccCard;
47 import com.android.internal.telephony.uicc.UiccProfile;
48 
49 import dalvik.system.PathClassLoader;
50 
51 import org.xmlpull.v1.XmlPullParser;
52 import org.xmlpull.v1.XmlPullParserException;
53 
54 import java.io.File;
55 import java.io.IOException;
56 import java.util.Arrays;
57 import java.util.HashSet;
58 import java.util.Set;
59 import java.util.function.Consumer;
60 import java.util.stream.Collectors;
61 
62 
63 
64 /**
65  * This class has one-line methods to instantiate objects only. The purpose is to make code
66  * unit-test friendly and use this class as a way to do dependency injection. Instantiating objects
67  * this way makes it easier to mock them in tests.
68  */
69 public class TelephonyComponentFactory {
70 
71     private static final String TAG = TelephonyComponentFactory.class.getSimpleName();
72 
73     private static TelephonyComponentFactory sInstance;
74 
75     private InjectedComponents mInjectedComponents;
76 
77     private static class InjectedComponents {
78         private static final String ATTRIBUTE_JAR = "jar";
79         private static final String ATTRIBUTE_PACKAGE = "package";
80         private static final String TAG_INJECTION = "injection";
81         private static final String TAG_COMPONENTS = "components";
82         private static final String TAG_COMPONENT = "component";
83         private static final String SYSTEM = "/system/";
84         private static final String PRODUCT = "/product/";
85 
86         private final Set<String> mComponentNames = new HashSet<>();
87         private TelephonyComponentFactory mInjectedInstance;
88         private String mPackageName;
89         private String mJarPath;
90 
91         /**
92          * @return paths correctly configured to inject.
93          * 1) PackageName and JarPath mustn't be empty.
94          * 2) JarPath is restricted under /system or /product only.
95          * 3) JarPath is on a READ-ONLY partition.
96          */
getValidatedPaths()97         private @Nullable String getValidatedPaths() {
98             if (TextUtils.isEmpty(mPackageName) || TextUtils.isEmpty(mJarPath)) {
99                 return null;
100             }
101             // filter out invalid paths
102             return Arrays.stream(mJarPath.split(File.pathSeparator))
103                     .filter(s -> (s.startsWith(SYSTEM) || s.startsWith(PRODUCT)))
104                     .filter(s -> {
105                         try {
106                             // This will also throw an error if the target doesn't exist.
107                             StructStatVfs vfs = Os.statvfs(s);
108                             return (vfs.f_flag & OsConstants.ST_RDONLY) != 0;
109                         } catch (ErrnoException e) {
110                             Rlog.w(TAG, "Injection jar is not protected , path: " + s
111                                     + e.getMessage());
112                             return false;
113                         }
114                     }).distinct()
115                     .collect(Collectors.joining(File.pathSeparator));
116         }
117 
makeInjectedInstance()118         private void makeInjectedInstance() {
119             String validatedPaths = getValidatedPaths();
120             Rlog.d(TAG, "validated paths: " + validatedPaths);
121             if (!TextUtils.isEmpty(validatedPaths)) {
122                 try {
123                     PathClassLoader classLoader = new PathClassLoader(validatedPaths,
124                             ClassLoader.getSystemClassLoader());
125                     Class<?> cls = classLoader.loadClass(mPackageName);
126                     mInjectedInstance = (TelephonyComponentFactory) cls.newInstance();
127                 } catch (ClassNotFoundException e) {
128                     Rlog.e(TAG, "failed: " + e.getMessage());
129                 } catch (IllegalAccessException | InstantiationException e) {
130                     Rlog.e(TAG, "injection failed: " + e.getMessage());
131                 }
132             }
133         }
134 
isComponentInjected(String componentName)135         private boolean isComponentInjected(String componentName) {
136             if (mInjectedInstance == null) {
137                 return false;
138             }
139             return mComponentNames.contains(componentName);
140         }
141 
142         /**
143          * Find the injection tag, set attributes, and then parse the injection.
144          */
parseXml(@onNull XmlPullParser parser)145         private void parseXml(@NonNull XmlPullParser parser) {
146             parseXmlByTag(parser, false, p -> {
147                 setAttributes(p);
148                 parseInjection(p);
149             }, TAG_INJECTION);
150         }
151 
152         /**
153          * Only parse the first injection tag. Find the components tag, then try parse it next.
154          */
parseInjection(@onNull XmlPullParser parser)155         private void parseInjection(@NonNull XmlPullParser parser) {
156             parseXmlByTag(parser, false, p -> parseComponents(p), TAG_COMPONENTS);
157         }
158 
159         /**
160          * Only parse the first components tag. Find the component tags, then try parse them next.
161          */
parseComponents(@onNull XmlPullParser parser)162         private void parseComponents(@NonNull XmlPullParser parser) {
163             parseXmlByTag(parser, true, p -> parseComponent(p), TAG_COMPONENT);
164         }
165 
166         /**
167          * Extract text values from component tags.
168          */
parseComponent(@onNull XmlPullParser parser)169         private void parseComponent(@NonNull XmlPullParser parser) {
170             try {
171                 int outerDepth = parser.getDepth();
172                 int type;
173                 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
174                         && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
175                     if (type == XmlPullParser.TEXT) {
176                         mComponentNames.add(parser.getText());
177                     }
178                 }
179             } catch (XmlPullParserException | IOException e) {
180                 Rlog.e(TAG, "Failed to parse the component." , e);
181             }
182         }
183 
184         /**
185          * Iterates the tags, finds the corresponding tag and then applies the consumer.
186          */
parseXmlByTag(@onNull XmlPullParser parser, boolean allowDuplicate, @NonNull Consumer<XmlPullParser> consumer, @NonNull final String tag)187         private void parseXmlByTag(@NonNull XmlPullParser parser, boolean allowDuplicate,
188                 @NonNull Consumer<XmlPullParser> consumer, @NonNull final String tag) {
189             try {
190                 int outerDepth = parser.getDepth();
191                 int type;
192                 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
193                         && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
194                     if (type == XmlPullParser.START_TAG && tag.equals(parser.getName())) {
195                         consumer.accept(parser);
196                         if (!allowDuplicate) {
197                             return;
198                         }
199                     }
200                 }
201             } catch (XmlPullParserException | IOException e) {
202                 Rlog.e(TAG, "Failed to parse or find tag: " + tag, e);
203             }
204         }
205 
206         /**
207          * Sets the mPackageName and mJarPath by <injection/> tag.
208          * @param parser
209          * @return
210          */
setAttributes(@onNull XmlPullParser parser)211         private void setAttributes(@NonNull XmlPullParser parser) {
212             for (int i = 0; i < parser.getAttributeCount(); i++) {
213                 String name = parser.getAttributeName(i);
214                 String value = parser.getAttributeValue(i);
215                 if (InjectedComponents.ATTRIBUTE_PACKAGE.equals(name)) {
216                     mPackageName = value;
217                 } else if (InjectedComponents.ATTRIBUTE_JAR.equals(name)) {
218                     mJarPath = value;
219                 }
220             }
221         }
222     }
223 
224     public static TelephonyComponentFactory getInstance() {
225         if (sInstance == null) {
226             sInstance = new TelephonyComponentFactory();
227         }
228         return sInstance;
229     }
230 
231     /**
232      * Inject TelephonyComponentFactory using a xml config file.
233      * @param parser a nullable {@link XmlResourceParser} created with the injection config file.
234      * The config xml should has below formats:
235      * <injection package="package.InjectedTelephonyComponentFactory" jar="path to jar file">
236      *     <components>
237      *         <component>example.package.ComponentAbc</component>
238      *         <component>example.package.ComponentXyz</component>
239      *         <!-- e.g. com.android.internal.telephony.GsmCdmaPhone -->
240      *     </components>
241      * </injection>
242      */
243     public void injectTheComponentFactory(XmlResourceParser parser) {
244         if (mInjectedComponents != null) {
245             Rlog.d(TAG, "Already injected.");
246             return;
247         }
248 
249         if (parser != null) {
250             mInjectedComponents = new InjectedComponents();
251             mInjectedComponents.parseXml(parser);
252             mInjectedComponents.makeInjectedInstance();
253             boolean injectSuccessful = !TextUtils.isEmpty(mInjectedComponents.getValidatedPaths());
254             Rlog.d(TAG, "Total components injected: " + (injectSuccessful
255                     ? mInjectedComponents.mComponentNames.size() : 0));
256         }
257     }
258 
259     /**
260      * Use the injected TelephonyComponentFactory if configured. Otherwise, use the default.
261      * @param componentName Name of the component class uses the injected component factory,
262      * e.g. GsmCdmaPhone.class.getName() for {@link GsmCdmaPhone}
263      * @return injected component factory. If not configured or injected, return the default one.
264      */
265     public TelephonyComponentFactory inject(String componentName) {
266         if (mInjectedComponents != null && mInjectedComponents.isComponentInjected(componentName)) {
267             return mInjectedComponents.mInjectedInstance;
268         }
269         return sInstance;
270     }
271 
272     public GsmCdmaCallTracker makeGsmCdmaCallTracker(GsmCdmaPhone phone) {
273         return new GsmCdmaCallTracker(phone);
274     }
275 
276     public SmsStorageMonitor makeSmsStorageMonitor(Phone phone) {
277         return new SmsStorageMonitor(phone);
278     }
279 
280     public SmsUsageMonitor makeSmsUsageMonitor(Context context) {
281         return new SmsUsageMonitor(context);
282     }
283 
284     public ServiceStateTracker makeServiceStateTracker(GsmCdmaPhone phone, CommandsInterface ci) {
285         return new ServiceStateTracker(phone, ci);
286     }
287 
288     /**
289      * Create a new EmergencyNumberTracker.
290      */
291     public EmergencyNumberTracker makeEmergencyNumberTracker(Phone phone, CommandsInterface ci) {
292         return new EmergencyNumberTracker(phone, ci);
293     }
294 
295     /**
296      * Sets the NitzStateMachine implementation to use during implementation. This boolean
297      * should be removed once the new implementation is stable.
298      */
299     static final boolean USE_NEW_NITZ_STATE_MACHINE = true;
300 
301     /**
302      * Returns a new {@link NitzStateMachine} instance.
303      */
304     public NitzStateMachine makeNitzStateMachine(GsmCdmaPhone phone) {
305         return USE_NEW_NITZ_STATE_MACHINE
306                 ? new NewNitzStateMachine(phone)
307                 : new OldNitzStateMachine(phone);
308     }
309 
310     public SimActivationTracker makeSimActivationTracker(Phone phone) {
311         return new SimActivationTracker(phone);
312     }
313 
314     public DcTracker makeDcTracker(Phone phone, @TransportType int transportType) {
315         return new DcTracker(phone, transportType);
316     }
317 
318     public CarrierSignalAgent makeCarrierSignalAgent(Phone phone) {
319         return new CarrierSignalAgent(phone);
320     }
321 
322     public CarrierActionAgent makeCarrierActionAgent(Phone phone) {
323         return new CarrierActionAgent(phone);
324     }
325 
326     public CarrierResolver makeCarrierResolver(Phone phone) {
327         return new CarrierResolver(phone);
328     }
329 
330     public IccPhoneBookInterfaceManager makeIccPhoneBookInterfaceManager(Phone phone) {
331         return new IccPhoneBookInterfaceManager(phone);
332     }
333 
334     public IccSmsInterfaceManager makeIccSmsInterfaceManager(Phone phone) {
335         return new IccSmsInterfaceManager(phone);
336     }
337 
338     /**
339      * Create a new UiccProfile object.
340      */
341     public UiccProfile makeUiccProfile(Context context, CommandsInterface ci, IccCardStatus ics,
342                                        int phoneId, UiccCard uiccCard, Object lock) {
343         return new UiccProfile(context, ci, ics, phoneId, uiccCard, lock);
344     }
345 
346     public EriManager makeEriManager(Phone phone, int eriFileSource) {
347         return new EriManager(phone, eriFileSource);
348     }
349 
350     public WspTypeDecoder makeWspTypeDecoder(byte[] pdu) {
351         return new WspTypeDecoder(pdu);
352     }
353 
354     /**
355      * Create a tracker for a single-part SMS.
356      */
357     public InboundSmsTracker makeInboundSmsTracker(byte[] pdu, long timestamp, int destPort,
358             boolean is3gpp2, boolean is3gpp2WapPdu, String address, String displayAddr,
359             String messageBody, boolean isClass0) {
360         return new InboundSmsTracker(pdu, timestamp, destPort, is3gpp2, is3gpp2WapPdu, address,
361                 displayAddr, messageBody, isClass0);
362     }
363 
364     /**
365      * Create a tracker for a multi-part SMS.
366      */
367     public InboundSmsTracker makeInboundSmsTracker(byte[] pdu, long timestamp, int destPort,
368             boolean is3gpp2, String address, String displayAddr, int referenceNumber,
369             int sequenceNumber, int messageCount, boolean is3gpp2WapPdu, String messageBody,
370             boolean isClass0) {
371         return new InboundSmsTracker(pdu, timestamp, destPort, is3gpp2, address, displayAddr,
372                 referenceNumber, sequenceNumber, messageCount, is3gpp2WapPdu, messageBody,
373                 isClass0);
374     }
375 
376     /**
377      * Create a tracker from a row of raw table
378      */
379     public InboundSmsTracker makeInboundSmsTracker(Cursor cursor, boolean isCurrentFormat3gpp2) {
380         return new InboundSmsTracker(cursor, isCurrentFormat3gpp2);
381     }
382 
383     public ImsPhoneCallTracker makeImsPhoneCallTracker(ImsPhone imsPhone) {
384         return new ImsPhoneCallTracker(imsPhone);
385     }
386 
387     public ImsExternalCallTracker makeImsExternalCallTracker(ImsPhone imsPhone) {
388 
389         return new ImsExternalCallTracker(imsPhone);
390     }
391 
392     /**
393      * Create an AppSmsManager for per-app SMS message.
394      */
395     public AppSmsManager makeAppSmsManager(Context context) {
396         return new AppSmsManager(context);
397     }
398 
399     public DeviceStateMonitor makeDeviceStateMonitor(Phone phone) {
400         return new DeviceStateMonitor(phone);
401     }
402 
403     public TransportManager makeTransportManager(Phone phone) {
404         return new TransportManager(phone);
405     }
406 
407     public CdmaSubscriptionSourceManager
408     getCdmaSubscriptionSourceManagerInstance(Context context, CommandsInterface ci, Handler h,
409                                              int what, Object obj) {
410         return CdmaSubscriptionSourceManager.getInstance(context, ci, h, what, obj);
411     }
412 
413     public IDeviceIdleController getIDeviceIdleController() {
414         return IDeviceIdleController.Stub.asInterface(
415                 ServiceManager.getService(Context.DEVICE_IDLE_CONTROLLER));
416     }
417 
418     public LocaleTracker makeLocaleTracker(Phone phone, NitzStateMachine nitzStateMachine,
419                                            Looper looper) {
420         return new LocaleTracker(phone, nitzStateMachine, looper);
421     }
422 
423     public DataEnabledSettings makeDataEnabledSettings(Phone phone) {
424         return new DataEnabledSettings(phone);
425     }
426 }
427