diff options
| author | android-build-team Robot <android-build-team-robot@google.com> | 2019-11-11 21:08:06 +0000 |
|---|---|---|
| committer | android-build-team Robot <android-build-team-robot@google.com> | 2019-11-11 21:08:06 +0000 |
| commit | 01ce1511ebf300efc9f94572eacccad75f4e6cab (patch) | |
| tree | 8b9a2cfac348e527d15163c62d82c3c9d60ccb88 | |
| parent | 95e6d303b3d13fb41ec0a0552f6cafb3c4b1973b (diff) | |
| parent | 624edfdec69eeac489e6d52e998575b965aab7c0 (diff) | |
| download | platform_packages_services_Telephony-android10-mainline-networking-release.tar.gz platform_packages_services_Telephony-android10-mainline-networking-release.tar.bz2 platform_packages_services_Telephony-android10-mainline-networking-release.zip | |
Snap for 6001391 from 624edfdec69eeac489e6d52e998575b965aab7c0 to qt-aml-networking-releaseandroid-mainline-10.0.0_r6android10-mainline-networking-release
Change-Id: I4420c3575e655d2ac1cb5a204ae19d8430d0907a
14 files changed, 776 insertions, 730 deletions
diff --git a/src/com/android/phone/CallFeaturesSetting.java b/src/com/android/phone/CallFeaturesSetting.java index 29df8b810..17a17340b 100644 --- a/src/com/android/phone/CallFeaturesSetting.java +++ b/src/com/android/phone/CallFeaturesSetting.java @@ -229,8 +229,8 @@ public class CallFeaturesSetting extends PreferenceActivity } private void listenPhoneState(boolean listen) { - TelephonyManager telephonyManager = - (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE); + TelephonyManager telephonyManager = getSystemService(TelephonyManager.class) + .createForSubscriptionId(mPhone.getSubId()); telephonyManager.listen(mPhoneStateListener, listen ? PhoneStateListener.LISTEN_CALL_STATE : PhoneStateListener.LISTEN_NONE); } @@ -239,10 +239,7 @@ public class CallFeaturesSetting extends PreferenceActivity @Override public void onCallStateChanged(int state, String incomingNumber) { if (DBG) log("PhoneStateListener onCallStateChanged: state is " + state); - // Use TelecomManager#getCallStete instead of 'state' parameter because it needs - // to check the current state of all phone calls. - boolean isCallStateIdle = - mTelecomManager.getCallState() == TelephonyManager.CALL_STATE_IDLE; + boolean isCallStateIdle = state == TelephonyManager.CALL_STATE_IDLE; if (mEnableVideoCalling != null) { mEnableVideoCalling.setEnabled(isCallStateIdle); } diff --git a/src/com/android/phone/CarrierConfigLoader.java b/src/com/android/phone/CarrierConfigLoader.java index 1d0138bcb..874c412ed 100644 --- a/src/com/android/phone/CarrierConfigLoader.java +++ b/src/com/android/phone/CarrierConfigLoader.java @@ -141,6 +141,8 @@ public class CarrierConfigLoader extends ICarrierConfigLoader.Stub { private static final int EVENT_FETCH_CARRIER_TIMEOUT = 15; // SubscriptionInfoUpdater has finished updating the sub for the carrier config. private static final int EVENT_SUBSCRIPTION_INFO_UPDATED = 16; + // Multi-SIM config changed. + private static final int EVENT_MULTI_SIM_CONFIG_CHANGED = 17; private static final int BIND_TIMEOUT_MILLIS = 30000; @@ -174,28 +176,21 @@ public class CarrierConfigLoader extends ICarrierConfigLoader.Stub { public void handleMessage(Message msg) { final int phoneId = msg.arg1; logWithLocalLog("mHandler: " + msg.what + " phoneId: " + phoneId); + if (!SubscriptionManager.isValidPhoneId(phoneId) + && msg.what != EVENT_MULTI_SIM_CONFIG_CHANGED) { + return; + } switch (msg.what) { case EVENT_CLEAR_CONFIG: { - /* Ignore clear configuration request if device is being shutdown. */ - Phone phone = PhoneFactory.getPhone(phoneId); - if (phone != null) { - if (phone.isShuttingDown()) { - break; - } - } - - mConfigFromDefaultApp[phoneId] = null; - mConfigFromCarrierApp[phoneId] = null; - mServiceConnection[phoneId] = null; - broadcastConfigChangedIntent(phoneId, false); + clearConfigForPhone(phoneId, true); break; } case EVENT_SYSTEM_UNLOCKED: { - for (int i = 0; i < TelephonyManager.from(mContext) - .getSupportedModemCount(); ++i) { + for (int i = 0; i < TelephonyManager.from(mContext).getActiveModemCount(); + ++i) { // When user unlock device, we should only try to send broadcast again if we // have sent it before unlock. This will avoid we try to load carrier config // when SIM is still loading when unlock happens. @@ -212,8 +207,7 @@ public class CarrierConfigLoader extends ICarrierConfigLoader.Stub { // Only update if there are cached config removed to avoid updating config for // unrelated packages. if (clearCachedConfigForPackage(carrierPackageName)) { - int numPhones = TelephonyManager.from(mContext) - .getSupportedModemCount(); + int numPhones = TelephonyManager.from(mContext).getActiveModemCount(); for (int i = 0; i < numPhones; ++i) { updateConfigForPhoneId(i); } @@ -496,6 +490,9 @@ public class CarrierConfigLoader extends ICarrierConfigLoader.Stub { case EVENT_SUBSCRIPTION_INFO_UPDATED: broadcastConfigChangedIntent(phoneId); break; + case EVENT_MULTI_SIM_CONFIG_CHANGED: + onMultiSimConfigChanged(); + break; } } } @@ -555,6 +552,23 @@ public class CarrierConfigLoader extends ICarrierConfigLoader.Stub { } } + private void clearConfigForPhone(int phoneId, boolean sendBroadcast) { + /* Ignore clear configuration request if device is being shutdown. */ + Phone phone = PhoneFactory.getPhone(phoneId); + if (phone != null) { + if (phone.isShuttingDown()) { + return; + } + } + + mConfigFromDefaultApp[phoneId] = null; + mConfigFromCarrierApp[phoneId] = null; + mServiceConnection[phoneId] = null; + mHasSentConfigChange[phoneId] = false; + + if (sendBroadcast) broadcastConfigChangedIntent(phoneId, false); + } + private void notifySubscriptionInfoUpdater(int phoneId) { String configPackagename; PersistableBundle configToSend; @@ -572,6 +586,7 @@ public class CarrierConfigLoader extends ICarrierConfigLoader.Stub { // mOverrideConfigs is for testing. And it will override current configs. PersistableBundle config = mOverrideConfigs[phoneId]; if (config != null) { + configToSend = new PersistableBundle(configToSend); configToSend.putAll(config); } @@ -919,6 +934,13 @@ public class CarrierConfigLoader extends ICarrierConfigLoader.Stub { mHandler.sendMessage(mHandler.obtainMessage(EVENT_DO_FETCH_DEFAULT, phoneId, -1)); } + private void onMultiSimConfigChanged() { + for (int i = TelephonyManager.from(mContext).getActiveModemCount(); + i < mConfigFromDefaultApp.length; i++) { + clearConfigForPhone(i, false); + } + } + @Override public @NonNull PersistableBundle getConfigForSubId(int subId, String callingPackage) { if (!TelephonyPermissions.checkCallingOrSelfReadPhoneState( diff --git a/src/com/android/phone/ImsRcsController.java b/src/com/android/phone/ImsRcsController.java new file mode 100644 index 000000000..d1ff56f25 --- /dev/null +++ b/src/com/android/phone/ImsRcsController.java @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.phone; + +import android.content.Context; +import android.net.Uri; +import android.os.ServiceManager; +import android.telephony.ims.aidl.IImsCapabilityCallback; +import android.telephony.ims.aidl.IImsRcsController; +import android.telephony.ims.aidl.IRcsUceControllerCallback; +import android.telephony.ims.feature.RcsFeature; +import android.util.Log; + +import java.util.List; + +/** + * Implementation of the IImsRcsController interface. + */ +public class ImsRcsController extends IImsRcsController.Stub { + private static final String TAG = "ImsRcsController"; + + /** The singleton instance. */ + private static ImsRcsController sInstance; + + private PhoneGlobals mApp; + + /** + * Initialize the singleton ImsRcsController instance. + * This is only done once, at startup, from PhoneApp.onCreate(). + */ + static ImsRcsController init(PhoneGlobals app) { + synchronized (ImsRcsController.class) { + if (sInstance == null) { + sInstance = new ImsRcsController(app); + } else { + Log.wtf(TAG, "init() called multiple times! sInstance = " + sInstance); + } + return sInstance; + } + } + + /** Private constructor; @see init() */ + private ImsRcsController(PhoneGlobals app) { + Log.i(TAG, "ImsRcsController"); + mApp = app; + ServiceManager.addService(Context.TELEPHONY_IMS_SERVICE, this); + } + + @Override + public void registerRcsAvailabilityCallback(IImsCapabilityCallback c) { + enforceReadPrivilegedPermission("registerRcsAvailabilityCallback"); + } + + @Override + public void unregisterRcsAvailabilityCallback(IImsCapabilityCallback c) { + enforceReadPrivilegedPermission("unregisterRcsAvailabilityCallback"); + } + + @Override + public boolean isCapable(int subId, + @RcsFeature.RcsImsCapabilities.RcsImsCapabilityFlag int capability) { + enforceReadPrivilegedPermission("isCapable"); + return false; + } + + @Override + public boolean isAvailable(int subId, + @RcsFeature.RcsImsCapabilities.RcsImsCapabilityFlag int capability) { + enforceReadPrivilegedPermission("isAvailable"); + return false; + } + + @Override + public void requestCapabilities(int subId, List<Uri> contactNumbers, + IRcsUceControllerCallback c) { + enforceReadPrivilegedPermission("requestCapabilities"); + } + + @Override + public int getUcePublishState(int subId) { + enforceReadPrivilegedPermission("getUcePublishState"); + return -1; + } + + @Override + public boolean isUceSettingEnabled(int subId) { + enforceReadPrivilegedPermission("isUceSettingEnabled"); + return false; + } + + @Override + public void setUceSettingEnabled(int subId, boolean isEnabled) { + enforceModifyPermission(); + } + + /** + * Make sure either called from same process as self (phone) or IPC caller has read privilege. + * + * @throws SecurityException if the caller does not have the required permission + */ + private void enforceReadPrivilegedPermission(String message) { + mApp.enforceCallingOrSelfPermission( + android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, message); + } + + /** + * Make sure the caller has the MODIFY_PHONE_STATE permission. + * + * @throws SecurityException if the caller does not have the required permission + */ + private void enforceModifyPermission() { + mApp.enforceCallingOrSelfPermission(android.Manifest.permission.MODIFY_PHONE_STATE, null); + } +} diff --git a/src/com/android/phone/PhoneDisplayMessage.java b/src/com/android/phone/PhoneDisplayMessage.java index 2b86a619d..199fbb827 100644 --- a/src/com/android/phone/PhoneDisplayMessage.java +++ b/src/com/android/phone/PhoneDisplayMessage.java @@ -78,10 +78,11 @@ public class PhoneDisplayMessage { sDisplayMessageDialog.getWindow().setType( WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG); sDisplayMessageDialog.getWindow().addFlags( - WindowManager.LayoutParams.FLAG_DIM_BEHIND); + WindowManager.LayoutParams.FLAG_DIM_BEHIND + | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON + | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); sDisplayMessageDialog.show(); - PhoneGlobals.getInstance().wakeUpScreen(); } /** @@ -91,6 +92,9 @@ public class PhoneDisplayMessage { if (DBG) log("Dissmissing Display Info Record..."); if (sDisplayMessageDialog != null) { + sDisplayMessageDialog.getWindow().clearFlags( + WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON + | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); sDisplayMessageDialog.dismiss(); sDisplayMessageDialog = null; } diff --git a/src/com/android/phone/PhoneGlobals.java b/src/com/android/phone/PhoneGlobals.java index c46d09b7e..b9c67287b 100644 --- a/src/com/android/phone/PhoneGlobals.java +++ b/src/com/android/phone/PhoneGlobals.java @@ -37,7 +37,6 @@ import android.os.Handler; import android.os.Message; import android.os.PersistableBundle; import android.os.PowerManager; -import android.os.SystemClock; import android.os.SystemProperties; import android.os.UserManager; import android.preference.PreferenceManager; @@ -148,6 +147,7 @@ public class PhoneGlobals extends ContextWrapper { CallerInfoCache callerInfoCache; NotificationMgr notificationMgr; public PhoneInterfaceManager phoneMgr; + public ImsRcsController imsRcsController; CarrierConfigLoader configLoader; private Phone phoneInEcm; @@ -354,6 +354,8 @@ public class PhoneGlobals extends ContextWrapper { phoneMgr = PhoneInterfaceManager.init(this); + imsRcsController = ImsRcsController.init(this); + configLoader = CarrierConfigLoader.init(this); // Create the CallNotifier singleton, which handles @@ -498,19 +500,6 @@ public class PhoneGlobals extends ContextWrapper { mPUKEntryProgressDialog = dialog; } - /** - * If we are not currently keeping the screen on, then poke the power - * manager to wake up the screen for the user activity timeout duration. - */ - /* package */ void wakeUpScreen() { - synchronized (this) { - if (mWakeState == WakeState.SLEEP) { - if (DBG) Log.d(LOG_TAG, "pulse screen lock"); - mPowerManager.wakeUp(SystemClock.uptimeMillis(), "android.phone:WAKE"); - } - } - } - KeyguardManager getKeyguardManager() { return mKeyguardManager; } diff --git a/src/com/android/phone/PhoneInterfaceManager.java b/src/com/android/phone/PhoneInterfaceManager.java index 4f91effe9..b5fca0340 100755 --- a/src/com/android/phone/PhoneInterfaceManager.java +++ b/src/com/android/phone/PhoneInterfaceManager.java @@ -1077,11 +1077,13 @@ public class PhoneInterfaceManager extends ITelephony.Stub { try { if (ar.exception != null) { Log.e(LOG_TAG, "Exception retrieving CellInfo=" + ar.exception); - cb.onError(TelephonyManager.CellInfoCallback.ERROR_MODEM_ERROR, - new android.os.ParcelableException(ar.exception)); + cb.onError( + TelephonyManager.CellInfoCallback.ERROR_MODEM_ERROR, + ar.exception.getClass().getName(), + ar.exception.toString()); } else if (ar.result == null) { Log.w(LOG_TAG, "Timeout Waiting for CellInfo!"); - cb.onError(TelephonyManager.CellInfoCallback.ERROR_TIMEOUT, null); + cb.onError(TelephonyManager.CellInfoCallback.ERROR_TIMEOUT, null, null); } else { // use the result as returned cb.onCellInfo((List<CellInfo>) ar.result); @@ -6475,7 +6477,8 @@ public class PhoneInterfaceManager extends ITelephony.Stub { public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err, String[] args, ShellCallback callback, ResultReceiver resultReceiver) throws RemoteException { - (new TelephonyShellCommand(this)).exec(this, in, out, err, args, callback, resultReceiver); + (new TelephonyShellCommand(this, getDefaultPhone().getContext())) + .exec(this, in, out, err, args, callback, resultReceiver); } /** diff --git a/src/com/android/phone/PhoneUtils.java b/src/com/android/phone/PhoneUtils.java index 6c3f0bfdd..362573365 100644 --- a/src/com/android/phone/PhoneUtils.java +++ b/src/com/android/phone/PhoneUtils.java @@ -43,10 +43,7 @@ import android.widget.EditText; import android.widget.Toast; import com.android.internal.telephony.Call; -import com.android.internal.telephony.CallManager; import com.android.internal.telephony.CallStateException; -import android.telephony.CallerInfo; -import android.telephony.CallerInfoAsyncQuery; import com.android.internal.telephony.Connection; import com.android.internal.telephony.IccCard; import com.android.internal.telephony.MmiCode; @@ -54,10 +51,8 @@ import com.android.internal.telephony.Phone; import com.android.internal.telephony.PhoneConstants; import com.android.internal.telephony.PhoneFactory; import com.android.internal.telephony.TelephonyCapabilities; -import com.android.phone.CallGatewayManager.RawGatewayInfo; import com.android.phone.settings.SuppServicesUiUtil; -import java.util.Arrays; import java.util.List; /** @@ -131,79 +126,35 @@ public class PhoneUtils { } /** - * @see placeCall below - */ - public static int placeCall(Context context, Phone phone, String number, Uri contactRef, - boolean isEmergencyCall) { - return placeCall(context, phone, number, contactRef, isEmergencyCall, - CallGatewayManager.EMPTY_INFO, null); - } - - /** * Dial the number using the phone passed in. * - * If the connection is establised, this method issues a sync call - * that may block to query the caller info. - * TODO: Change the logic to use the async query. - * * @param context To perform the CallerInfo query. * @param phone the Phone object. * @param number to be dialed as requested by the user. This is * NOT the phone number to connect to. It is used only to build the * call card and to update the call log. See above for restrictions. - * @param contactRef that triggered the call. Typically a 'tel:' - * uri but can also be a 'content://contacts' one. - * @param isEmergencyCall indicates that whether or not this is an - * emergency call - * @param gatewayUri Is the address used to setup the connection, null - * if not using a gateway - * @param callGateway Class for setting gateway data on a successful call. * * @return either CALL_STATUS_DIALED or CALL_STATUS_FAILED */ - public static int placeCall(Context context, Phone phone, String number, Uri contactRef, - boolean isEmergencyCall, RawGatewayInfo gatewayInfo, CallGatewayManager callGateway) { - final Uri gatewayUri = gatewayInfo.gatewayUri; + public static int placeOtaspCall(Context context, Phone phone, String number) { + final Uri gatewayUri = null; if (VDBG) { log("placeCall()... number: '" + number + "'" - + ", GW:'" + gatewayUri + "'" - + ", contactRef:" + contactRef - + ", isEmergencyCall: " + isEmergencyCall); + + ", GW:'" + gatewayUri + "'"); } else { log("placeCall()... number: " + toLogSafePhoneNumber(number) - + ", GW: " + (gatewayUri != null ? "non-null" : "null") - + ", emergency? " + isEmergencyCall); + + ", GW: " + (gatewayUri != null ? "non-null" : "null")); } final PhoneGlobals app = PhoneGlobals.getInstance(); boolean useGateway = false; - if (null != gatewayUri && - !isEmergencyCall && - PhoneUtils.isRoutableViaGateway(number)) { // Filter out MMI, OTA and other codes. - useGateway = true; - } + Uri contactRef = null; int status = CALL_STATUS_DIALED; Connection connection; String numberToDial; - if (useGateway) { - // TODO: 'tel' should be a constant defined in framework base - // somewhere (it is in webkit.) - if (null == gatewayUri || !PhoneAccount.SCHEME_TEL.equals(gatewayUri.getScheme())) { - Log.e(LOG_TAG, "Unsupported URL:" + gatewayUri); - return CALL_STATUS_FAILED; - } - - // We can use getSchemeSpecificPart because we don't allow # - // in the gateway numbers (treated a fragment delim.) However - // if we allow more complex gateway numbers sequence (with - // passwords or whatnot) that use #, this may break. - // TODO: Need to support MMI codes. - numberToDial = gatewayUri.getSchemeSpecificPart(); - } else { - numberToDial = number; - } + numberToDial = number; try { connection = app.mCM.dial(phone, numberToDial, VideoProfile.STATE_AUDIO_ONLY); @@ -228,33 +179,6 @@ public class PhoneUtils { if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) { updateCdmaCallStateOnNewOutgoingCall(app, connection); } - - if (gatewayUri == null) { - // phone.dial() succeeded: we're now in a normal phone call. - // attach the URI to the CallerInfo Object if it is there, - // otherwise just attach the Uri Reference. - // if the uri does not have a "content" scheme, then we treat - // it as if it does NOT have a unique reference. - String content = context.getContentResolver().SCHEME_CONTENT; - if ((contactRef != null) && (contactRef.getScheme().equals(content))) { - Object userDataObject = connection.getUserData(); - if (userDataObject == null) { - connection.setUserData(contactRef); - } else { - // TODO: This branch is dead code, we have - // just created the connection which has - // no user data (null) by default. - if (userDataObject instanceof CallerInfo) { - ((CallerInfo) userDataObject).contactRefUri = contactRef; - } else { - ((CallerInfoToken) userDataObject).currentInfo.contactRefUri = - contactRef; - } - } - } - } - - startGetCallerInfo(context, connection, null, null, gatewayInfo); } return status; @@ -662,533 +586,12 @@ public class PhoneUtils { return canceled; } - /** - * Returns the caller-id info corresponding to the specified Connection. - * (This is just a simple wrapper around CallerInfo.getCallerInfo(): we - * extract a phone number from the specified Connection, and feed that - * number into CallerInfo.getCallerInfo().) - * - * The returned CallerInfo may be null in certain error cases, like if the - * specified Connection was null, or if we weren't able to get a valid - * phone number from the Connection. - * - * Finally, if the getCallerInfo() call did succeed, we save the resulting - * CallerInfo object in the "userData" field of the Connection. - * - * NOTE: This API should be avoided, with preference given to the - * asynchronous startGetCallerInfo API. - */ - static CallerInfo getCallerInfo(Context context, Connection c) { - CallerInfo info = null; - - if (c != null) { - //See if there is a URI attached. If there is, this means - //that there is no CallerInfo queried yet, so we'll need to - //replace the URI with a full CallerInfo object. - Object userDataObject = c.getUserData(); - if (userDataObject instanceof Uri) { - info = CallerInfo.getCallerInfo(context, (Uri) userDataObject); - if (info != null) { - c.setUserData(info); - } - } else { - if (userDataObject instanceof CallerInfoToken) { - //temporary result, while query is running - info = ((CallerInfoToken) userDataObject).currentInfo; - } else { - //final query result - info = (CallerInfo) userDataObject; - } - if (info == null) { - // No URI, or Existing CallerInfo, so we'll have to make do with - // querying a new CallerInfo using the connection's phone number. - String number = c.getAddress(); - - if (DBG) log("getCallerInfo: number = " + toLogSafePhoneNumber(number)); - - if (!TextUtils.isEmpty(number)) { - info = CallerInfo.getCallerInfo(context, number); - if (info != null) { - c.setUserData(info); - } - } - } - } - } - return info; - } - - /** - * Class returned by the startGetCallerInfo call to package a temporary - * CallerInfo Object, to be superceded by the CallerInfo Object passed - * into the listener when the query with token mAsyncQueryToken is complete. - */ - public static class CallerInfoToken { - /**indicates that there will no longer be updates to this request.*/ - public boolean isFinal; - - public CallerInfo currentInfo; - public CallerInfoAsyncQuery asyncQuery; - } - - /** - * place a temporary callerinfo object in the hands of the caller and notify - * caller when the actual query is done. - */ - static CallerInfoToken startGetCallerInfo(Context context, Connection c, - CallerInfoAsyncQuery.OnQueryCompleteListener listener, Object cookie, - RawGatewayInfo info) { - CallerInfoToken cit; - - if (c == null) { - //TODO: perhaps throw an exception here. - cit = new CallerInfoToken(); - cit.asyncQuery = null; - return cit; - } - - Object userDataObject = c.getUserData(); - - // There are now 3 states for the Connection's userData object: - // - // (1) Uri - query has not been executed yet - // - // (2) CallerInfoToken - query is executing, but has not completed. - // - // (3) CallerInfo - query has executed. - // - // In each case we have slightly different behaviour: - // 1. If the query has not been executed yet (Uri or null), we start - // query execution asynchronously, and note it by attaching a - // CallerInfoToken as the userData. - // 2. If the query is executing (CallerInfoToken), we've essentially - // reached a state where we've received multiple requests for the - // same callerInfo. That means that once the query is complete, - // we'll need to execute the additional listener requested. - // 3. If the query has already been executed (CallerInfo), we just - // return the CallerInfo object as expected. - // 4. Regarding isFinal - there are cases where the CallerInfo object - // will not be attached, like when the number is empty (caller id - // blocking). This flag is used to indicate that the - // CallerInfoToken object is going to be permanent since no - // query results will be returned. In the case where a query - // has been completed, this flag is used to indicate to the caller - // that the data will not be updated since it is valid. - // - // Note: For the case where a number is NOT retrievable, we leave - // the CallerInfo as null in the CallerInfoToken. This is - // something of a departure from the original code, since the old - // code manufactured a CallerInfo object regardless of the query - // outcome. From now on, we will append an empty CallerInfo - // object, to mirror previous behaviour, and to avoid Null Pointer - // Exceptions. - - if (userDataObject instanceof Uri) { - // State (1): query has not been executed yet - - //create a dummy callerinfo, populate with what we know from URI. - cit = new CallerInfoToken(); - cit.currentInfo = new CallerInfo(); - cit.asyncQuery = CallerInfoAsyncQuery.startQuery(QUERY_TOKEN, context, - (Uri) userDataObject, sCallerInfoQueryListener, c); - cit.asyncQuery.addQueryListener(QUERY_TOKEN, listener, cookie); - cit.isFinal = false; - - c.setUserData(cit); - - if (DBG) log("startGetCallerInfo: query based on Uri: " + userDataObject); - - } else if (userDataObject == null) { - // No URI, or Existing CallerInfo, so we'll have to make do with - // querying a new CallerInfo using the connection's phone number. - String number = c.getAddress(); - - if (info != null && info != CallGatewayManager.EMPTY_INFO) { - // Gateway number, the connection number is actually the gateway number. - // need to lookup via dialed number. - number = info.trueNumber; - } - - if (DBG) { - log("PhoneUtils.startGetCallerInfo: new query for phone number..."); - log("- number (address): " + toLogSafePhoneNumber(number)); - log("- c: " + c); - log("- phone: " + c.getCall().getPhone()); - int phoneType = c.getCall().getPhone().getPhoneType(); - log("- phoneType: " + phoneType); - switch (phoneType) { - case PhoneConstants.PHONE_TYPE_NONE: log(" ==> PHONE_TYPE_NONE"); break; - case PhoneConstants.PHONE_TYPE_GSM: log(" ==> PHONE_TYPE_GSM"); break; - case PhoneConstants.PHONE_TYPE_IMS: log(" ==> PHONE_TYPE_IMS"); break; - case PhoneConstants.PHONE_TYPE_CDMA: log(" ==> PHONE_TYPE_CDMA"); break; - case PhoneConstants.PHONE_TYPE_SIP: log(" ==> PHONE_TYPE_SIP"); break; - case PhoneConstants.PHONE_TYPE_THIRD_PARTY: - log(" ==> PHONE_TYPE_THIRD_PARTY"); - break; - default: log(" ==> Unknown phone type"); break; - } - } - - cit = new CallerInfoToken(); - cit.currentInfo = new CallerInfo(); - - // Store CNAP information retrieved from the Connection (we want to do this - // here regardless of whether the number is empty or not). - cit.currentInfo.cnapName = c.getCnapName(); - cit.currentInfo.setName(cit.currentInfo.cnapName); // This can still get overwritten - // by ContactInfo later - cit.currentInfo.numberPresentation = c.getNumberPresentation(); - cit.currentInfo.namePresentation = c.getCnapNamePresentation(); - - if (VDBG) { - log("startGetCallerInfo: number = " + number); - log("startGetCallerInfo: CNAP Info from FW(1): name=" - + cit.currentInfo.cnapName - + ", Name/Number Pres=" + cit.currentInfo.numberPresentation); - } - - // handling case where number is null (caller id hidden) as well. - if (!TextUtils.isEmpty(number)) { - // Check for special CNAP cases and modify the CallerInfo accordingly - // to be sure we keep the right information to display/log later - number = modifyForSpecialCnapCases(context, cit.currentInfo, number, - cit.currentInfo.numberPresentation); - - cit.currentInfo.setPhoneNumber(number); - // For scenarios where we may receive a valid number from the network but a - // restricted/unavailable presentation, we do not want to perform a contact query - // (see note on isFinal above). So we set isFinal to true here as well. - if (cit.currentInfo.numberPresentation != PhoneConstants.PRESENTATION_ALLOWED) { - cit.isFinal = true; - } else { - if (DBG) log("==> Actually starting CallerInfoAsyncQuery.startQuery()..."); - cit.asyncQuery = CallerInfoAsyncQuery.startQuery(QUERY_TOKEN, context, - number, sCallerInfoQueryListener, c); - cit.asyncQuery.addQueryListener(QUERY_TOKEN, listener, cookie); - cit.isFinal = false; - } - } else { - // This is the case where we are querying on a number that - // is null or empty, like a caller whose caller id is - // blocked or empty (CLIR). The previous behaviour was to - // throw a null CallerInfo object back to the user, but - // this departure is somewhat cleaner. - if (DBG) log("startGetCallerInfo: No query to start, send trivial reply."); - cit.isFinal = true; // please see note on isFinal, above. - } - - c.setUserData(cit); - - if (DBG) { - log("startGetCallerInfo: query based on number: " + toLogSafePhoneNumber(number)); - } - - } else if (userDataObject instanceof CallerInfoToken) { - // State (2): query is executing, but has not completed. - - // just tack on this listener to the queue. - cit = (CallerInfoToken) userDataObject; - - // handling case where number is null (caller id hidden) as well. - if (cit.asyncQuery != null) { - cit.asyncQuery.addQueryListener(QUERY_TOKEN, listener, cookie); - - if (DBG) log("startGetCallerInfo: query already running, adding listener: " + - listener.getClass().toString()); - } else { - // handling case where number/name gets updated later on by the network - String updatedNumber = c.getAddress(); - - if (info != null) { - // Gateway number, the connection number is actually the gateway number. - // need to lookup via dialed number. - updatedNumber = info.trueNumber; - } - - if (DBG) { - log("startGetCallerInfo: updatedNumber initially = " - + toLogSafePhoneNumber(updatedNumber)); - } - if (!TextUtils.isEmpty(updatedNumber)) { - // Store CNAP information retrieved from the Connection - cit.currentInfo.cnapName = c.getCnapName(); - // This can still get overwritten by ContactInfo - cit.currentInfo.setName(cit.currentInfo.cnapName); - cit.currentInfo.numberPresentation = c.getNumberPresentation(); - cit.currentInfo.namePresentation = c.getCnapNamePresentation(); - - updatedNumber = modifyForSpecialCnapCases(context, cit.currentInfo, - updatedNumber, cit.currentInfo.numberPresentation); - - cit.currentInfo.setPhoneNumber(updatedNumber); - if (DBG) { - log("startGetCallerInfo: updatedNumber=" - + toLogSafePhoneNumber(updatedNumber)); - } - if (VDBG) { - log("startGetCallerInfo: CNAP Info from FW(2): name=" - + cit.currentInfo.cnapName - + ", Name/Number Pres=" + cit.currentInfo.numberPresentation); - } else if (DBG) { - log("startGetCallerInfo: CNAP Info from FW(2)"); - } - // For scenarios where we may receive a valid number from the network but a - // restricted/unavailable presentation, we do not want to perform a contact query - // (see note on isFinal above). So we set isFinal to true here as well. - if (cit.currentInfo.numberPresentation != PhoneConstants.PRESENTATION_ALLOWED) { - cit.isFinal = true; - } else { - cit.asyncQuery = CallerInfoAsyncQuery.startQuery(QUERY_TOKEN, context, - updatedNumber, sCallerInfoQueryListener, c); - cit.asyncQuery.addQueryListener(QUERY_TOKEN, listener, cookie); - cit.isFinal = false; - } - } else { - if (DBG) log("startGetCallerInfo: No query to attach to, send trivial reply."); - if (cit.currentInfo == null) { - cit.currentInfo = new CallerInfo(); - } - // Store CNAP information retrieved from the Connection - cit.currentInfo.cnapName = c.getCnapName(); // This can still get - // overwritten by ContactInfo - cit.currentInfo.setName(cit.currentInfo.cnapName); - cit.currentInfo.numberPresentation = c.getNumberPresentation(); - cit.currentInfo.namePresentation = c.getCnapNamePresentation(); - - if (VDBG) { - log("startGetCallerInfo: CNAP Info from FW(3): name=" - + cit.currentInfo.cnapName - + ", Name/Number Pres=" + cit.currentInfo.numberPresentation); - } else if (DBG) { - log("startGetCallerInfo: CNAP Info from FW(3)"); - } - cit.isFinal = true; // please see note on isFinal, above. - } - } - } else { - // State (3): query is complete. - - // The connection's userDataObject is a full-fledged - // CallerInfo instance. Wrap it in a CallerInfoToken and - // return it to the user. - - cit = new CallerInfoToken(); - cit.currentInfo = (CallerInfo) userDataObject; - cit.asyncQuery = null; - cit.isFinal = true; - // since the query is already done, call the listener. - if (DBG) log("startGetCallerInfo: query already done, returning CallerInfo"); - if (DBG) log("==> cit.currentInfo = " + cit.currentInfo); - } - return cit; - } - - /** - * Static CallerInfoAsyncQuery.OnQueryCompleteListener instance that - * we use with all our CallerInfoAsyncQuery.startQuery() requests. - */ - private static final int QUERY_TOKEN = -1; - static CallerInfoAsyncQuery.OnQueryCompleteListener sCallerInfoQueryListener = - new CallerInfoAsyncQuery.OnQueryCompleteListener () { - /** - * When the query completes, we stash the resulting CallerInfo - * object away in the Connection's "userData" (where it will - * later be retrieved by the in-call UI.) - */ - public void onQueryComplete(int token, Object cookie, CallerInfo ci) { - if (DBG) log("query complete, updating connection.userdata"); - Connection conn = (Connection) cookie; - - // Added a check if CallerInfo is coming from ContactInfo or from Connection. - // If no ContactInfo, then we want to use CNAP information coming from network - if (DBG) log("- onQueryComplete: CallerInfo:" + ci); - if (ci.contactExists || ci.isEmergencyNumber() || ci.isVoiceMailNumber()) { - // If the number presentation has not been set by - // the ContactInfo, use the one from the - // connection. - - // TODO: Need a new util method to merge the info - // from the Connection in a CallerInfo object. - // Here 'ci' is a new CallerInfo instance read - // from the DB. It has lost all the connection - // info preset before the query (see PhoneUtils - // line 1334). We should have a method to merge - // back into this new instance the info from the - // connection object not set by the DB. If the - // Connection already has a CallerInfo instance in - // userData, then we could use this instance to - // fill 'ci' in. The same routine could be used in - // PhoneUtils. - if (0 == ci.numberPresentation) { - ci.numberPresentation = conn.getNumberPresentation(); - } - } else { - // No matching contact was found for this number. - // Return a new CallerInfo based solely on the CNAP - // information from the network. - - CallerInfo newCi = getCallerInfo(null, conn); - - // ...but copy over the (few) things we care about - // from the original CallerInfo object: - if (newCi != null) { - newCi.setPhoneNumber(ci.getPhoneNumber()); // To get formatted phone number - newCi.geoDescription = ci.geoDescription; // To get geo description string - ci = newCi; - } - } - - if (DBG) log("==> Stashing CallerInfo " + ci + " into the connection..."); - conn.setUserData(ci); - } - }; - - - /** - * Returns a single "name" for the specified given a CallerInfo object. - * If the name is null, return defaultString as the default value, usually - * context.getString(R.string.unknown). - */ - static String getCompactNameFromCallerInfo(CallerInfo ci, Context context) { - if (DBG) log("getCompactNameFromCallerInfo: info = " + ci); - - String compactName = null; - if (ci != null) { - if (TextUtils.isEmpty(ci.getName())) { - // Perform any modifications for special CNAP cases to - // the phone number being displayed, if applicable. - compactName = modifyForSpecialCnapCases(context, ci, ci.getPhoneNumber(), - ci.numberPresentation); - } else { - // Don't call modifyForSpecialCnapCases on regular name. See b/2160795. - compactName = ci.getName(); - } - } - - if ((compactName == null) || (TextUtils.isEmpty(compactName))) { - // If we're still null/empty here, then check if we have a presentation - // string that takes precedence that we could return, otherwise display - // "unknown" string. - if (ci != null && ci.numberPresentation == PhoneConstants.PRESENTATION_RESTRICTED) { - compactName = context.getString(R.string.private_num); - } else if (ci != null && ci.numberPresentation == PhoneConstants.PRESENTATION_PAYPHONE) { - compactName = context.getString(R.string.payphone); - } else { - compactName = context.getString(R.string.unknown); - } - } - if (VDBG) log("getCompactNameFromCallerInfo: compactName=" + compactName); - return compactName; - } - - static boolean isInEmergencyCall(CallManager cm) { - Call fgCall = cm.getActiveFgCall(); - // isIdle includes checks for the DISCONNECTING/DISCONNECTED state. - if(!fgCall.isIdle()) { - for (Connection cn : fgCall.getConnections()) { - if (PhoneNumberUtils.isLocalEmergencyNumber(PhoneGlobals.getInstance(), - cn.getAddress())) { - return true; - } - } - } - return false; - } // // Misc UI policy helper functions // /** - * Based on the input CNAP number string, - * @return _RESTRICTED or _UNKNOWN for all the special CNAP strings. - * Otherwise, return CNAP_SPECIAL_CASE_NO. - */ - private static int checkCnapSpecialCases(String n) { - if (n.equals("PRIVATE") || - n.equals("P") || - n.equals("RES")) { - if (DBG) log("checkCnapSpecialCases, PRIVATE string: " + n); - return PhoneConstants.PRESENTATION_RESTRICTED; - } else if (n.equals("UNAVAILABLE") || - n.equals("UNKNOWN") || - n.equals("UNA") || - n.equals("U")) { - if (DBG) log("checkCnapSpecialCases, UNKNOWN string: " + n); - return PhoneConstants.PRESENTATION_UNKNOWN; - } else { - if (DBG) log("checkCnapSpecialCases, normal str. number: " + n); - return CNAP_SPECIAL_CASE_NO; - } - } - - /** - * Handles certain "corner cases" for CNAP. When we receive weird phone numbers - * from the network to indicate different number presentations, convert them to - * expected number and presentation values within the CallerInfo object. - * @param number number we use to verify if we are in a corner case - * @param presentation presentation value used to verify if we are in a corner case - * @return the new String that should be used for the phone number - */ - /* package */ static String modifyForSpecialCnapCases(Context context, CallerInfo ci, - String number, int presentation) { - // Obviously we return number if ci == null, but still return number if - // number == null, because in these cases the correct string will still be - // displayed/logged after this function returns based on the presentation value. - if (ci == null || number == null) return number; - - if (DBG) { - log("modifyForSpecialCnapCases: initially, number=" - + toLogSafePhoneNumber(number) - + ", presentation=" + presentation + " ci " + ci); - } - - // "ABSENT NUMBER" is a possible value we could get from the network as the - // phone number, so if this happens, change it to "Unknown" in the CallerInfo - // and fix the presentation to be the same. - final String[] absentNumberValues = - context.getResources().getStringArray(R.array.absent_num); - if (Arrays.asList(absentNumberValues).contains(number) - && presentation == PhoneConstants.PRESENTATION_ALLOWED) { - number = context.getString(R.string.unknown); - ci.numberPresentation = PhoneConstants.PRESENTATION_UNKNOWN; - } - - // Check for other special "corner cases" for CNAP and fix them similarly. Corner - // cases only apply if we received an allowed presentation from the network, so check - // if we think we have an allowed presentation, or if the CallerInfo presentation doesn't - // match the presentation passed in for verification (meaning we changed it previously - // because it's a corner case and we're being called from a different entry point). - if (ci.numberPresentation == PhoneConstants.PRESENTATION_ALLOWED - || (ci.numberPresentation != presentation - && presentation == PhoneConstants.PRESENTATION_ALLOWED)) { - int cnapSpecialCase = checkCnapSpecialCases(number); - if (cnapSpecialCase != CNAP_SPECIAL_CASE_NO) { - // For all special strings, change number & numberPresentation. - if (cnapSpecialCase == PhoneConstants.PRESENTATION_RESTRICTED) { - number = context.getString(R.string.private_num); - } else if (cnapSpecialCase == PhoneConstants.PRESENTATION_UNKNOWN) { - number = context.getString(R.string.unknown); - } - if (DBG) { - log("SpecialCnap: number=" + toLogSafePhoneNumber(number) - + "; presentation now=" + cnapSpecialCase); - } - ci.numberPresentation = cnapSpecialCase; - } - } - if (DBG) { - log("modifyForSpecialCnapCases: returning number string=" - + toLogSafePhoneNumber(number)); - } - return number; - } - - // - // Support for 3rd party phone service providers. - // - - /** * Check if a phone number can be route through a 3rd party * gateway. The number must be a global phone number in numerical * form (1-800-666-SEXY won't work). diff --git a/src/com/android/phone/TelephonyShellCommand.java b/src/com/android/phone/TelephonyShellCommand.java index d8d1717f8..428c00623 100644 --- a/src/com/android/phone/TelephonyShellCommand.java +++ b/src/com/android/phone/TelephonyShellCommand.java @@ -16,10 +16,15 @@ package com.android.phone; +import android.content.Context; import android.os.Binder; +import android.os.Build; +import android.os.PersistableBundle; import android.os.Process; import android.os.RemoteException; import android.os.ShellCommand; +import android.telephony.CarrierConfigManager; +import android.telephony.SubscriptionInfo; import android.telephony.SubscriptionManager; import android.telephony.emergency.EmergencyNumber; import android.util.Log; @@ -29,6 +34,9 @@ import com.android.internal.telephony.emergency.EmergencyNumberTracker; import java.io.PrintWriter; import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; +import java.util.TreeSet; /** * Takes actions based on the adb commands given by "adb shell cmd phone ...". Be careful, no @@ -46,6 +54,7 @@ public class TelephonyShellCommand extends ShellCommand { private static final String IMS_SUBCOMMAND = "ims"; private static final String NUMBER_VERIFICATION_SUBCOMMAND = "numverify"; private static final String EMERGENCY_NUMBER_TEST_MODE = "emergency-number-test-mode"; + private static final String CARRIER_CONFIG_SUBCOMMAND = "cc"; private static final String IMS_SET_CARRIER_SERVICE = "set-ims-service"; private static final String IMS_GET_CARRIER_SERVICE = "get-ims-service"; @@ -59,11 +68,60 @@ public class TelephonyShellCommand extends ShellCommand { private static final String NUMBER_VERIFICATION_OVERRIDE_PACKAGE = "override-package"; private static final String NUMBER_VERIFICATION_FAKE_CALL = "fake-call"; + private static final String CC_GET_VALUE = "get-value"; + private static final String CC_SET_VALUE = "set-value"; + private static final String CC_CLEAR_VALUES = "clear-values"; + // Take advantage of existing methods that already contain permissions checks when possible. private final ITelephony mInterface; - public TelephonyShellCommand(ITelephony binder) { + private SubscriptionManager mSubscriptionManager; + private CarrierConfigManager mCarrierConfigManager; + + private enum CcType { + BOOLEAN, DOUBLE, DOUBLE_ARRAY, INT, INT_ARRAY, LONG, LONG_ARRAY, STRING, + STRING_ARRAY, UNKNOWN + } + + // Maps carrier config keys to type. It is possible to infer the type for most carrier config + // keys by looking at the end of the string which usually tells the type. + // For instance: "xxxx_string", "xxxx_string_array", etc. + // The carrier config keys in this map does not follow this convention. It is therefore not + // possible to infer the type for these keys by looking at the string. + private static final Map<String, CcType> CC_TYPE_MAP = new HashMap<String, CcType>() {{ + put(CarrierConfigManager.Gps.KEY_A_GLONASS_POS_PROTOCOL_SELECT_STRING, CcType.STRING); + put(CarrierConfigManager.Gps.KEY_ES_EXTENSION_SEC_STRING, CcType.STRING); + put(CarrierConfigManager.Gps.KEY_GPS_LOCK_STRING, CcType.STRING); + put(CarrierConfigManager.Gps.KEY_LPP_PROFILE_STRING, CcType.STRING); + put(CarrierConfigManager.Gps.KEY_NFW_PROXY_APPS_STRING, CcType.STRING); + put(CarrierConfigManager.Gps.KEY_SUPL_ES_STRING, CcType.STRING); + put(CarrierConfigManager.Gps.KEY_SUPL_HOST_STRING, CcType.STRING); + put(CarrierConfigManager.Gps.KEY_SUPL_MODE_STRING, CcType.STRING); + put(CarrierConfigManager.Gps.KEY_SUPL_PORT_STRING, CcType.STRING); + put(CarrierConfigManager.Gps.KEY_SUPL_VER_STRING, CcType.STRING); + put(CarrierConfigManager.Gps.KEY_USE_EMERGENCY_PDN_FOR_EMERGENCY_SUPL_STRING, + CcType.STRING); + put(CarrierConfigManager.KEY_CARRIER_APP_NO_WAKE_SIGNAL_CONFIG_STRING_ARRAY, + CcType.STRING_ARRAY); + put(CarrierConfigManager.KEY_CARRIER_APP_WAKE_SIGNAL_CONFIG_STRING_ARRAY, + CcType.STRING_ARRAY); + put(CarrierConfigManager.KEY_CARRIER_CALL_SCREENING_APP_STRING, CcType.STRING); + put(CarrierConfigManager.KEY_MMS_EMAIL_GATEWAY_NUMBER_STRING, CcType.STRING); + put(CarrierConfigManager.KEY_MMS_HTTP_PARAMS_STRING, CcType.STRING); + put(CarrierConfigManager.KEY_MMS_NAI_SUFFIX_STRING, CcType.STRING); + put(CarrierConfigManager.KEY_MMS_UA_PROF_TAG_NAME_STRING, CcType.STRING); + put(CarrierConfigManager.KEY_MMS_UA_PROF_URL_STRING, CcType.STRING); + put(CarrierConfigManager.KEY_MMS_USER_AGENT_STRING, CcType.STRING); + put(CarrierConfigManager.KEY_RATCHET_RAT_FAMILIES, CcType.STRING_ARRAY); + } + }; + + public TelephonyShellCommand(ITelephony binder, Context context) { mInterface = binder; + mCarrierConfigManager = + (CarrierConfigManager) context.getSystemService(Context.CARRIER_CONFIG_SERVICE); + mSubscriptionManager = (SubscriptionManager) + context.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE); } @Override @@ -80,6 +138,9 @@ public class TelephonyShellCommand extends ShellCommand { return handleNumberVerificationCommand(); case EMERGENCY_NUMBER_TEST_MODE: return handleEmergencyNumberTestModeCommand(); + case CARRIER_CONFIG_SUBCOMMAND: { + return handleCcCommand(); + } default: { return handleDefaultCommands(cmd); } @@ -96,8 +157,11 @@ public class TelephonyShellCommand extends ShellCommand { pw.println(" IMS Commands."); pw.println(" emergency-number-test-mode"); pw.println(" Emergency Number Test Mode Commands."); + pw.println(" cc"); + pw.println(" Carrier Config Commands."); onHelpIms(); onHelpEmergencyNumber(); + onHelpCc(); } private void onHelpIms() { @@ -152,6 +216,33 @@ public class TelephonyShellCommand extends ShellCommand { pw.println(" -p: get the full emergency number list in the test mode."); } + private void onHelpCc() { + PrintWriter pw = getOutPrintWriter(); + pw.println("Carrier Config Commands:"); + pw.println(" cc get-value [-s SLOT_ID] [KEY]"); + pw.println(" Print carrier config values."); + pw.println(" Options are:"); + pw.println(" -s: The SIM slot ID to read carrier config value for. If no option"); + pw.println(" is specified, it will choose the default voice SIM slot."); + pw.println(" KEY: The key to the carrier config value to print. All values are printed"); + pw.println(" if KEY is not specified."); + pw.println(" cc set-value [-s SLOT_ID] KEY [NEW_VALUE]"); + pw.println(" Set carrier config KEY to NEW_VALUE."); + pw.println(" Options are:"); + pw.println(" -s: The SIM slot ID to set carrier config value for. If no option"); + pw.println(" is specified, it will choose the default voice SIM slot."); + pw.println(" NEW_VALUE specifies the new value for carrier config KEY. Null will be"); + pw.println(" used if NEW_VALUE is not set. Strings should be encapsulated with"); + pw.println(" quotation marks. Spaces needs to be escaped. Example: \"Hello\\ World\""); + pw.println(" Separate items in arrays with space . Example: \"item1\" \"item2\""); + pw.println(" cc clear-values [-s SLOT_ID]"); + pw.println(" Clear all carrier override values that has previously been set"); + pw.println(" with set-value"); + pw.println(" Options are:"); + pw.println(" -s: The SIM slot ID to clear carrier config values for. If no option"); + pw.println(" is specified, it will choose the default voice SIM slot."); + } + private int handleImsCommand() { String arg = getNextArg(); if (arg == null) { @@ -477,9 +568,486 @@ public class TelephonyShellCommand extends ShellCommand { return slotId; } + // Get the subId from argument SLOT_ID if it was provided. Otherwise use the default + // subscription. + private int getSubIdFromArgumentSlotId(String tag) { + PrintWriter errPw = getErrPrintWriter(); + int subId = SubscriptionManager.getDefaultSubscriptionId(); + String opt; + + while ((opt = getNextOption()) != null) { + switch (opt) { + case "-s": { + try { + subId = slotStringToSubId(tag, getNextArgRequired()); + } catch (IllegalArgumentException e) { + // Missing slot id + errPw.println(tag + "SLOT_ID expected after -s."); + return SubscriptionManager.INVALID_SUBSCRIPTION_ID; + } + break; + } + default: { + errPw.println(tag + "Unknown option " + opt); + return SubscriptionManager.INVALID_SUBSCRIPTION_ID; + } + } + } + return subId; + } + + private int slotStringToSubId(String tag, String slotString) { + int slotId = -1; + try { + slotId = Integer.parseInt(slotString); + } catch (NumberFormatException e) { + getErrPrintWriter().println(tag + slotString + " is not a valid SLOT_ID."); + return SubscriptionManager.INVALID_SUBSCRIPTION_ID; + } + + SubscriptionInfo subInfo = + mSubscriptionManager.getActiveSubscriptionInfoForSimSlotIndex(slotId); + if (subInfo == null) { + getErrPrintWriter().println(tag + "No subscription found in slot " + slotId + "."); + return SubscriptionManager.INVALID_SUBSCRIPTION_ID; + } + return subInfo.getSubscriptionId(); + } + private boolean checkShellUid() { // adb can run as root or as shell, depending on whether the device is rooted. return Binder.getCallingUid() == Process.SHELL_UID || Binder.getCallingUid() == Process.ROOT_UID; } + + private int handleCcCommand() { + // Verify that the user is allowed to run the command. Only allowed in rooted device in a + // non user build. + if (Binder.getCallingUid() != Process.ROOT_UID || Build.IS_USER) { + getErrPrintWriter().println("cc: Permission denied."); + return -1; + } + + String arg = getNextArg(); + if (arg == null) { + onHelpCc(); + return 0; + } + + switch (arg) { + case CC_GET_VALUE: { + return handleCcGetValue(); + } + case CC_SET_VALUE: { + return handleCcSetValue(); + } + case CC_CLEAR_VALUES: { + return handleCcClearValues(); + } + default: { + getErrPrintWriter().println("cc: Unknown argument: " + arg); + } + } + return -1; + } + + // cc get-value + private int handleCcGetValue() { + PrintWriter errPw = getErrPrintWriter(); + String tag = CARRIER_CONFIG_SUBCOMMAND + " " + CC_GET_VALUE + ": "; + String key = null; + + // Get the subId from the SLOT_ID-argument. + int subId = getSubIdFromArgumentSlotId(tag); + if (!SubscriptionManager.isValidSubscriptionId(subId)) { + errPw.println(tag + "No valid subscription found."); + return -1; + } + + // Get bundle containing all carrier configuration values. + PersistableBundle bundle = mCarrierConfigManager.getConfigForSubId(subId); + if (bundle == null) { + errPw.println(tag + "No carrier config values found for subId " + subId + "."); + return -1; + } + + // Get the key. + key = getNextArg(); + if (key != null) { + // A key was provided. Verify if it is a valid key + if (!bundle.containsKey(key)) { + errPw.println(tag + key + " is not a valid key."); + return -1; + } + + // Print the carrier config value for key. + getOutPrintWriter().println(ccValueToString(key, getType(tag, key, bundle), bundle)); + } else { + // No key provided. Show all values. + // Iterate over a sorted list of all carrier config keys and print them. + TreeSet<String> sortedSet = new TreeSet<String>(bundle.keySet()); + for (String k : sortedSet) { + getOutPrintWriter().println(ccValueToString(k, getType(tag, k, bundle), bundle)); + } + } + return 0; + } + + // cc set-value + private int handleCcSetValue() { + PrintWriter errPw = getErrPrintWriter(); + String tag = CARRIER_CONFIG_SUBCOMMAND + " " + CC_SET_VALUE + ": "; + + // Get the subId from the SLOT_ID-argument. + int subId = getSubIdFromArgumentSlotId(tag); + if (!SubscriptionManager.isValidSubscriptionId(subId)) { + errPw.println(tag + "No valid subscription found."); + return -1; + } + + // Get bundle containing all current carrier configuration values. + PersistableBundle originalValues = mCarrierConfigManager.getConfigForSubId(subId); + if (originalValues == null) { + errPw.println(tag + "No carrier config values found for subId " + subId + "."); + return -1; + } + + // Get the key. + String key = getNextArg(); + if (key == null || key.equals("")) { + errPw.println(tag + "KEY is missing"); + return -1; + } + + // Verify if the key is valid + if (!originalValues.containsKey(key)) { + errPw.println(tag + key + " is not a valid key."); + return -1; + } + + // Remaining arguments is a list of new values. Add them all into an ArrayList. + ArrayList<String> valueList = new ArrayList<String>(); + while (peekNextArg() != null) { + valueList.add(getNextArg()); + } + + // Find the type of the carrier config value + CcType type = getType(tag, key, originalValues); + if (type == CcType.UNKNOWN) { + errPw.println(tag + "ERROR: Not possible to override key with unknown type."); + return -1; + } + + // Create an override bundle containing the key and value that should be overriden. + PersistableBundle overrideBundle = getOverrideBundle(tag, type, key, valueList); + if (overrideBundle == null) { + return -1; + } + + // Override the value + mCarrierConfigManager.overrideConfig(subId, overrideBundle); + + // Find bundle containing all new carrier configuration values after the override. + PersistableBundle newValues = mCarrierConfigManager.getConfigForSubId(subId); + if (newValues == null) { + errPw.println(tag + "No carrier config values found for subId " + subId + "."); + return -1; + } + + // Print the original and new value. + String originalValueString = ccValueToString(key, type, originalValues); + String newValueString = ccValueToString(key, type, newValues); + getOutPrintWriter().println("Previous value: \n" + originalValueString); + getOutPrintWriter().println("New value: \n" + newValueString); + + return 0; + } + + // cc clear-values + private int handleCcClearValues() { + PrintWriter errPw = getErrPrintWriter(); + String tag = CARRIER_CONFIG_SUBCOMMAND + " " + CC_CLEAR_VALUES + ": "; + + // Get the subId from the SLOT_ID-argument. + int subId = getSubIdFromArgumentSlotId(tag); + if (!SubscriptionManager.isValidSubscriptionId(subId)) { + errPw.println(tag + "No valid subscription found."); + return -1; + } + + // Clear all values that has previously been set. + mCarrierConfigManager.overrideConfig(subId, null); + getOutPrintWriter() + .println("All previously set carrier config override values has been cleared"); + return 0; + } + + private CcType getType(String tag, String key, PersistableBundle bundle) { + // Find the type by checking the type of the current value stored in the bundle. + Object value = bundle.get(key); + + if (CC_TYPE_MAP.containsKey(key)) { + return CC_TYPE_MAP.get(key); + } else if (value != null) { + if (value instanceof Boolean) { + return CcType.BOOLEAN; + } else if (value instanceof Double) { + return CcType.DOUBLE; + } else if (value instanceof double[]) { + return CcType.DOUBLE_ARRAY; + } else if (value instanceof Integer) { + return CcType.INT; + } else if (value instanceof int[]) { + return CcType.INT_ARRAY; + } else if (value instanceof Long) { + return CcType.LONG; + } else if (value instanceof long[]) { + return CcType.LONG_ARRAY; + } else if (value instanceof String) { + return CcType.STRING; + } else if (value instanceof String[]) { + return CcType.STRING_ARRAY; + } + } else { + // Current value was null and can therefore not be used in order to find the type. + // Check the name of the key to infer the type. This check is not needed for primitive + // data types (boolean, double, int and long), since they can not be null. + if (key.endsWith("double_array")) { + return CcType.DOUBLE_ARRAY; + } + if (key.endsWith("int_array")) { + return CcType.INT_ARRAY; + } + if (key.endsWith("long_array")) { + return CcType.LONG_ARRAY; + } + if (key.endsWith("string")) { + return CcType.STRING; + } + if (key.endsWith("string_array") || key.endsWith("strings")) { + return CcType.STRING_ARRAY; + } + } + + // Not possible to infer the type by looking at the current value or the key. + PrintWriter errPw = getErrPrintWriter(); + errPw.println(tag + "ERROR: " + key + " has unknown type."); + return CcType.UNKNOWN; + } + + private String ccValueToString(String key, CcType type, PersistableBundle bundle) { + String result; + StringBuilder valueString = new StringBuilder(); + String typeString = type.toString(); + Object value = bundle.get(key); + + if (value == null) { + valueString.append("null"); + } else { + switch (type) { + case DOUBLE_ARRAY: { + // Format the string representation of the int array as value1 value2...... + double[] valueArray = (double[]) value; + for (int i = 0; i < valueArray.length; i++) { + if (i != 0) { + valueString.append(" "); + } + valueString.append(valueArray[i]); + } + break; + } + case INT_ARRAY: { + // Format the string representation of the int array as value1 value2...... + int[] valueArray = (int[]) value; + for (int i = 0; i < valueArray.length; i++) { + if (i != 0) { + valueString.append(" "); + } + valueString.append(valueArray[i]); + } + break; + } + case LONG_ARRAY: { + // Format the string representation of the int array as value1 value2...... + long[] valueArray = (long[]) value; + for (int i = 0; i < valueArray.length; i++) { + if (i != 0) { + valueString.append(" "); + } + valueString.append(valueArray[i]); + } + break; + } + case STRING: { + valueString.append("\"" + value.toString() + "\""); + break; + } + case STRING_ARRAY: { + // Format the string representation of the string array as "value1" "value2".... + String[] valueArray = (String[]) value; + for (int i = 0; i < valueArray.length; i++) { + if (i != 0) { + valueString.append(" "); + } + if (valueArray[i] != null) { + valueString.append("\"" + valueArray[i] + "\""); + } else { + valueString.append("null"); + } + } + break; + } + default: { + valueString.append(value.toString()); + } + } + } + return String.format("%-70s %-15s %s", key, typeString, valueString); + } + + private PersistableBundle getOverrideBundle(String tag, CcType type, String key, + ArrayList<String> valueList) { + PrintWriter errPw = getErrPrintWriter(); + PersistableBundle bundle = new PersistableBundle(); + + // First verify that a valid number of values has been provided for the type. + switch (type) { + case BOOLEAN: + case DOUBLE: + case INT: + case LONG: { + if (valueList.size() != 1) { + errPw.println(tag + "Expected 1 value for type " + type + + ". Found: " + valueList.size()); + return null; + } + break; + } + case STRING: { + if (valueList.size() > 1) { + errPw.println(tag + "Expected 0 or 1 values for type " + type + + ". Found: " + valueList.size()); + return null; + } + break; + } + } + + // Parse the value according to type and add it to the Bundle. + switch (type) { + case BOOLEAN: { + if ("true".equalsIgnoreCase(valueList.get(0))) { + bundle.putBoolean(key, true); + } else if ("false".equalsIgnoreCase(valueList.get(0))) { + bundle.putBoolean(key, false); + } else { + errPw.println(tag + "Unable to parse " + valueList.get(0) + " as a " + type); + return null; + } + break; + } + case DOUBLE: { + try { + bundle.putDouble(key, Double.parseDouble(valueList.get(0))); + } catch (NumberFormatException nfe) { + // Not a valid double + errPw.println(tag + "Unable to parse " + valueList.get(0) + " as a " + type); + return null; + } + break; + } + case DOUBLE_ARRAY: { + double[] valueDoubleArray = null; + if (valueList.size() > 0) { + valueDoubleArray = new double[valueList.size()]; + for (int i = 0; i < valueList.size(); i++) { + try { + valueDoubleArray[i] = Double.parseDouble(valueList.get(i)); + } catch (NumberFormatException nfe) { + // Not a valid double + errPw.println( + tag + "Unable to parse " + valueList.get(i) + " as a double."); + return null; + } + } + } + bundle.putDoubleArray(key, valueDoubleArray); + break; + } + case INT: { + try { + bundle.putInt(key, Integer.parseInt(valueList.get(0))); + } catch (NumberFormatException nfe) { + // Not a valid integer + errPw.println(tag + "Unable to parse " + valueList.get(0) + " as an " + type); + return null; + } + break; + } + case INT_ARRAY: { + int[] valueIntArray = null; + if (valueList.size() > 0) { + valueIntArray = new int[valueList.size()]; + for (int i = 0; i < valueList.size(); i++) { + try { + valueIntArray[i] = Integer.parseInt(valueList.get(i)); + } catch (NumberFormatException nfe) { + // Not a valid integer + errPw.println(tag + + "Unable to parse " + valueList.get(i) + " as an integer."); + return null; + } + } + } + bundle.putIntArray(key, valueIntArray); + break; + } + case LONG: { + try { + bundle.putLong(key, Long.parseLong(valueList.get(0))); + } catch (NumberFormatException nfe) { + // Not a valid long + errPw.println(tag + "Unable to parse " + valueList.get(0) + " as a " + type); + return null; + } + break; + } + case LONG_ARRAY: { + long[] valueLongArray = null; + if (valueList.size() > 0) { + valueLongArray = new long[valueList.size()]; + for (int i = 0; i < valueList.size(); i++) { + try { + valueLongArray[i] = Long.parseLong(valueList.get(i)); + } catch (NumberFormatException nfe) { + // Not a valid long + errPw.println( + tag + "Unable to parse " + valueList.get(i) + " as a long"); + return null; + } + } + } + bundle.putLongArray(key, valueLongArray); + break; + } + case STRING: { + String value = null; + if (valueList.size() > 0) { + value = valueList.get(0); + } + bundle.putString(key, value); + break; + } + case STRING_ARRAY: { + String[] valueStringArray = null; + if (valueList.size() > 0) { + valueStringArray = new String[valueList.size()]; + valueList.toArray(valueStringArray); + } + bundle.putStringArray(key, valueStringArray); + break; + } + } + return bundle; + } } diff --git a/src/com/android/phone/otasp/OtaspActivationService.java b/src/com/android/phone/otasp/OtaspActivationService.java index 749088011..6ed2ea8fe 100644 --- a/src/com/android/phone/otasp/OtaspActivationService.java +++ b/src/com/android/phone/otasp/OtaspActivationService.java @@ -150,11 +150,9 @@ public class OtaspActivationService extends Service { mPhone.registerForCdmaOtaStatusChange(mHandler, EVENT_CDMA_PROVISION_STATUS_UPDATE, null); mPhone.registerForPreciseCallStateChanged(mHandler, EVENT_CALL_STATE_CHANGED, null); logd("startNonInteractiveOtasp: placing call to '" + OTASP_NUMBER + "'..."); - int callStatus = PhoneUtils.placeCall(this, + int callStatus = PhoneUtils.placeOtaspCall(this, getPhone(), - OTASP_NUMBER, - null, // contactRef - false); // isEmergencyCall + OTASP_NUMBER); if (callStatus == PhoneUtils.CALL_STATUS_DIALED) { if (DBG) logd(" ==> success return from placeCall(): callStatus = " + callStatus); } else { diff --git a/src/com/android/services/telephony/ImsConferenceController.java b/src/com/android/services/telephony/ImsConferenceController.java index 315aaadcb..b7f7b92f2 100644 --- a/src/com/android/services/telephony/ImsConferenceController.java +++ b/src/com/android/services/telephony/ImsConferenceController.java @@ -147,6 +147,7 @@ public class ImsConferenceController { mTelephonyConnections.add(connection); connection.addTelephonyConnectionListener(mTelephonyConnectionListener); recalculateConference(); + recalculateConferenceable(); } /** diff --git a/src/com/android/services/telephony/RadioOnHelper.java b/src/com/android/services/telephony/RadioOnHelper.java index 741a6c5b9..981dc9655 100644 --- a/src/com/android/services/telephony/RadioOnHelper.java +++ b/src/com/android/services/telephony/RadioOnHelper.java @@ -49,13 +49,19 @@ public class RadioOnHelper implements RadioOnStateListener.Callback { } private void setupListeners() { - if (mListeners != null) { - return; + if (mListeners == null) { + mListeners = new ArrayList<>(2); } - mListeners = new ArrayList<>(2); - for (int i = 0; i < TelephonyManager.getDefault().getSupportedModemCount(); i++) { + int activeModems = TelephonyManager.from(mContext).getActiveModemCount(); + // Add new listeners if active modem count increased. + while (mListeners.size() < activeModems) { mListeners.add(new RadioOnStateListener()); } + // Clean up listeners if active modem count decreased. + while (mListeners.size() > activeModems) { + mListeners.get(mListeners.size() - 1).cleanup(); + mListeners.remove(mListeners.size() - 1); + } } /** * Starts the "turn on radio" sequence. This is the (single) external API of the @@ -76,7 +82,7 @@ public class RadioOnHelper implements RadioOnStateListener.Callback { mCallback = callback; mInProgressListeners.clear(); mIsRadioOnCallingEnabled = false; - for (int i = 0; i < TelephonyManager.getDefault().getSupportedModemCount(); i++) { + for (int i = 0; i < TelephonyManager.from(mContext).getActiveModemCount(); i++) { Phone phone = PhoneFactory.getPhone(i); if (phone == null) { continue; diff --git a/src/com/android/services/telephony/RadioOnStateListener.java b/src/com/android/services/telephony/RadioOnStateListener.java index 729f6a9c1..52bd9cf42 100644 --- a/src/com/android/services/telephony/RadioOnStateListener.java +++ b/src/com/android/services/telephony/RadioOnStateListener.java @@ -231,7 +231,7 @@ public class RadioOnStateListener { * Note we don't call this method simply after a successful call to placeCall(), since it's * still possible the call will disconnect very quickly with an OUT_OF_SERVICE error. */ - private void cleanup() { + public void cleanup() { Log.d(this, "cleanup()"); // This will send a failure call back if callback has yet to be invoked. If the callback diff --git a/testapps/ImsTestService/src/com/android/phone/testapps/imstestapp/ImsRegistrationActivity.java b/testapps/ImsTestService/src/com/android/phone/testapps/imstestapp/ImsRegistrationActivity.java index 139f8ac94..5c0416b5b 100644 --- a/testapps/ImsTestService/src/com/android/phone/testapps/imstestapp/ImsRegistrationActivity.java +++ b/testapps/ImsTestService/src/com/android/phone/testapps/imstestapp/ImsRegistrationActivity.java @@ -182,7 +182,7 @@ public class ImsRegistrationActivity extends Activity { try { mImsManager = ImsMmTelManager.createForSubscriptionId( SubscriptionManager.getDefaultVoiceSubscriptionId()); - mImsManager.registerImsRegistrationCallback(mRegistrationCallback, getMainExecutor()); + mImsManager.registerImsRegistrationCallback(getMainExecutor(), mRegistrationCallback); } catch (IllegalArgumentException | ImsException e) { Log.w("ImsCallingActivity", "illegal subscription ID."); } diff --git a/tests/src/com/android/phone/CnapTest.java b/tests/src/com/android/phone/CnapTest.java deleted file mode 100644 index 534d02a43..000000000 --- a/tests/src/com/android/phone/CnapTest.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright (C) 2009 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -// Need to be in this package to access package methods. -package com.android.phone; -import android.content.Context; -import android.test.AndroidTestCase; -import android.test.suitebuilder.annotation.SmallTest; -import android.telephony.CallerInfo; - -import static com.android.internal.telephony.PhoneConstants.PRESENTATION_ALLOWED; -import static com.android.internal.telephony.PhoneConstants.PRESENTATION_UNKNOWN; - -// Test suite for the Caller Name Presentation (CNAP) handling. -// See AndroidManifest.xml how to run these tests. -public class CnapTest extends AndroidTestCase { - private static final String TAG = "CnapTest"; - private Context mContext; - private CallerInfo mCallerInfo; - // TODO: This string should be loaded from the phone package and - // not hardcoded. - private String mUnknown = "Unknown"; - - @Override - protected void setUp() throws Exception { - super.setUp(); - mContext = getContext(); - mCallerInfo = new CallerInfo(); - } - - // Checks the cnap 'ABSENT NUMBER' is mapped to the unknown presentation. - @SmallTest - public void testAbsentNumberIsMappedToUnknown() throws Exception { - String num = modifyForSpecialCnapCases("ABSENT NUMBER", PRESENTATION_ALLOWED); - assertIsUnknown(num); - } - - // HELPERS - - /** - * Checks the number and CallerInfo structure indicate the number - * is unknown. - */ - private void assertIsUnknown(String number) { - assertEquals(mUnknown, number); - assertEquals(PRESENTATION_UNKNOWN, mCallerInfo.numberPresentation); - // TODO: cnapName and name presentation should be set to - // unknown. At least I cannot see why it shouldn't be the case - // assertEquals(mUnknown, mCallerInfo.cnapName); - // assertEquals(PRESENTATION_UNKNOWN, mCallerInfo.namePresentation); - } - - /** - * Shorthand for PhoneUtils.modifyForSpecialCnapCases(mContext, mCallerInfo, ...) - */ - private String modifyForSpecialCnapCases(String number, int presentation) { - return PhoneUtils.modifyForSpecialCnapCases( - mContext, mCallerInfo, number, presentation); - } -} |
