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