Snap for 6001391 from 3386e7bd94c533837bd434c23757aa8aa1176b3e to qt-aml-networking-release
Change-Id: I65033beeed631598d5a26bd38b3ecbbb0697ad46
diff --git a/src/java/com/android/internal/telephony/BaseCommands.java b/src/java/com/android/internal/telephony/BaseCommands.java
index 89d5c27..f0c6262 100644
--- a/src/java/com/android/internal/telephony/BaseCommands.java
+++ b/src/java/com/android/internal/telephony/BaseCommands.java
@@ -167,11 +167,9 @@
@Override
public void registerForRadioStateChanged(Handler h, int what, Object obj) {
- Registrant r = new Registrant (h, what, obj);
-
synchronized (mStateMonitor) {
- mRadioStateChangedRegistrants.add(r);
- r.notifyRegistrant();
+ mRadioStateChangedRegistrants.addUnique(h, what, obj);
+ Message.obtain(h, what, new AsyncResult(obj, null, null)).sendToTarget();
}
}
@@ -183,8 +181,7 @@
}
public void registerForImsNetworkStateChanged(Handler h, int what, Object obj) {
- Registrant r = new Registrant (h, what, obj);
- mImsNetworkStateChangedRegistrants.add(r);
+ mImsNetworkStateChangedRegistrants.addUnique(h, what, obj);
}
public void unregisterForImsNetworkStateChanged(Handler h) {
@@ -193,13 +190,11 @@
@Override
public void registerForOn(Handler h, int what, Object obj) {
- Registrant r = new Registrant (h, what, obj);
-
synchronized (mStateMonitor) {
- mOnRegistrants.add(r);
+ mOnRegistrants.addUnique(h, what, obj);
if (mState == TelephonyManager.RADIO_POWER_ON) {
- r.notifyRegistrant(new AsyncResult(null, null, null));
+ Message.obtain(h, what, new AsyncResult(obj, null, null)).sendToTarget();
}
}
}
@@ -213,13 +208,11 @@
@Override
public void registerForAvailable(Handler h, int what, Object obj) {
- Registrant r = new Registrant (h, what, obj);
-
synchronized (mStateMonitor) {
- mAvailRegistrants.add(r);
+ mAvailRegistrants.addUnique(h, what, obj);
if (mState != TelephonyManager.RADIO_POWER_UNAVAILABLE) {
- r.notifyRegistrant(new AsyncResult(null, null, null));
+ Message.obtain(h, what, new AsyncResult(obj, null, null)).sendToTarget();
}
}
}
@@ -233,13 +226,11 @@
@Override
public void registerForNotAvailable(Handler h, int what, Object obj) {
- Registrant r = new Registrant (h, what, obj);
-
synchronized (mStateMonitor) {
- mNotAvailRegistrants.add(r);
+ mNotAvailRegistrants.addUnique(h, what, obj);
if (mState == TelephonyManager.RADIO_POWER_UNAVAILABLE) {
- r.notifyRegistrant(new AsyncResult(null, null, null));
+ Message.obtain(h, what, new AsyncResult(obj, null, null)).sendToTarget();
}
}
}
@@ -253,14 +244,12 @@
@Override
public void registerForOffOrNotAvailable(Handler h, int what, Object obj) {
- Registrant r = new Registrant (h, what, obj);
-
synchronized (mStateMonitor) {
- mOffOrNotAvailRegistrants.add(r);
+ mOffOrNotAvailRegistrants.addUnique(h, what, obj);
if (mState == TelephonyManager.RADIO_POWER_OFF
|| mState == TelephonyManager.RADIO_POWER_UNAVAILABLE) {
- r.notifyRegistrant(new AsyncResult(null, null, null));
+ Message.obtain(h, what, new AsyncResult(obj, null, null)).sendToTarget();
}
}
}
@@ -273,9 +262,7 @@
@Override
public void registerForCallStateChanged(Handler h, int what, Object obj) {
- Registrant r = new Registrant (h, what, obj);
-
- mCallStateRegistrants.add(r);
+ mCallStateRegistrants.addUnique(h, what, obj);
}
@Override
@@ -285,9 +272,7 @@
@Override
public void registerForNetworkStateChanged(Handler h, int what, Object obj) {
- Registrant r = new Registrant (h, what, obj);
-
- mNetworkStateRegistrants.add(r);
+ mNetworkStateRegistrants.addUnique(h, what, obj);
}
@Override
@@ -297,9 +282,7 @@
@Override
public void registerForDataCallListChanged(Handler h, int what, Object obj) {
- Registrant r = new Registrant (h, what, obj);
-
- mDataCallListChangedRegistrants.add(r);
+ mDataCallListChangedRegistrants.addUnique(h, what, obj);
}
@Override
@@ -309,8 +292,7 @@
@Override
public void registerForVoiceRadioTechChanged(Handler h, int what, Object obj) {
- Registrant r = new Registrant (h, what, obj);
- mVoiceRadioTechChangedRegistrants.add(r);
+ mVoiceRadioTechChangedRegistrants.addUnique(h, what, obj);
}
@Override
@@ -320,8 +302,7 @@
@Override
public void registerForIccStatusChanged(Handler h, int what, Object obj) {
- Registrant r = new Registrant (h, what, obj);
- mIccStatusChangedRegistrants.add(r);
+ mIccStatusChangedRegistrants.addUnique(h, what, obj);
}
@Override
@@ -331,8 +312,7 @@
@Override
public void registerForIccSlotStatusChanged(Handler h, int what, Object obj) {
- Registrant r = new Registrant(h, what, obj);
- mIccSlotStatusChangedRegistrants.add(r);
+ mIccSlotStatusChangedRegistrants.addUnique(h, what, obj);
}
@Override
@@ -524,8 +504,7 @@
@Override
public void registerForIccRefresh(Handler h, int what, Object obj) {
- Registrant r = new Registrant (h, what, obj);
- mIccRefreshRegistrants.add(r);
+ mIccRefreshRegistrants.addUnique(h, what, obj);
}
@Override
public void setOnIccRefresh(Handler h, int what, Object obj) {
@@ -581,8 +560,7 @@
@Override
public void registerForInCallVoicePrivacyOn(Handler h, int what, Object obj) {
- Registrant r = new Registrant (h, what, obj);
- mVoicePrivacyOnRegistrants.add(r);
+ mVoicePrivacyOnRegistrants.addUnique(h, what, obj);
}
@Override
@@ -592,8 +570,7 @@
@Override
public void registerForInCallVoicePrivacyOff(Handler h, int what, Object obj) {
- Registrant r = new Registrant (h, what, obj);
- mVoicePrivacyOffRegistrants.add(r);
+ mVoicePrivacyOffRegistrants.addUnique(h, what, obj);
}
@Override
@@ -616,8 +593,7 @@
@Override
public void registerForDisplayInfo(Handler h, int what, Object obj) {
- Registrant r = new Registrant (h, what, obj);
- mDisplayInfoRegistrants.add(r);
+ mDisplayInfoRegistrants.addUnique(h, what, obj);
}
@Override
@@ -627,8 +603,7 @@
@Override
public void registerForCallWaitingInfo(Handler h, int what, Object obj) {
- Registrant r = new Registrant (h, what, obj);
- mCallWaitingInfoRegistrants.add(r);
+ mCallWaitingInfoRegistrants.addUnique(h, what, obj);
}
@Override
@@ -638,8 +613,7 @@
@Override
public void registerForSignalInfo(Handler h, int what, Object obj) {
- Registrant r = new Registrant (h, what, obj);
- mSignalInfoRegistrants.add(r);
+ mSignalInfoRegistrants.addUnique(h, what, obj);
}
public void setOnUnsolOemHookRaw(Handler h, int what, Object obj) {
@@ -660,8 +634,7 @@
@Override
public void registerForCdmaOtaProvision(Handler h,int what, Object obj){
- Registrant r = new Registrant (h, what, obj);
- mOtaProvisionRegistrants.add(r);
+ mOtaProvisionRegistrants.addUnique(h, what, obj);
}
@Override
@@ -671,8 +644,7 @@
@Override
public void registerForNumberInfo(Handler h,int what, Object obj) {
- Registrant r = new Registrant (h, what, obj);
- mNumberInfoRegistrants.add(r);
+ mNumberInfoRegistrants.addUnique(h, what, obj);
}
@Override
@@ -682,8 +654,7 @@
@Override
public void registerForRedirectedNumberInfo(Handler h,int what, Object obj) {
- Registrant r = new Registrant (h, what, obj);
- mRedirNumInfoRegistrants.add(r);
+ mRedirNumInfoRegistrants.addUnique(h, what, obj);
}
@Override
@@ -693,8 +664,7 @@
@Override
public void registerForLineControlInfo(Handler h, int what, Object obj) {
- Registrant r = new Registrant (h, what, obj);
- mLineControlInfoRegistrants.add(r);
+ mLineControlInfoRegistrants.addUnique(h, what, obj);
}
@Override
@@ -704,8 +674,7 @@
@Override
public void registerFoT53ClirlInfo(Handler h,int what, Object obj) {
- Registrant r = new Registrant (h, what, obj);
- mT53ClirInfoRegistrants.add(r);
+ mT53ClirInfoRegistrants.addUnique(h, what, obj);
}
@Override
@@ -715,8 +684,7 @@
@Override
public void registerForT53AudioControlInfo(Handler h,int what, Object obj) {
- Registrant r = new Registrant (h, what, obj);
- mT53AudCntrlInfoRegistrants.add(r);
+ mT53AudCntrlInfoRegistrants.addUnique(h, what, obj);
}
@Override
@@ -726,8 +694,7 @@
@Override
public void registerForRingbackTone(Handler h, int what, Object obj) {
- Registrant r = new Registrant (h, what, obj);
- mRingbackToneRegistrants.add(r);
+ mRingbackToneRegistrants.addUnique(h, what, obj);
}
@Override
@@ -737,8 +704,7 @@
@Override
public void registerForResendIncallMute(Handler h, int what, Object obj) {
- Registrant r = new Registrant (h, what, obj);
- mResendIncallMuteRegistrants.add(r);
+ mResendIncallMuteRegistrants.addUnique(h, what, obj);
}
@Override
@@ -748,8 +714,7 @@
@Override
public void registerForCdmaSubscriptionChanged(Handler h, int what, Object obj) {
- Registrant r = new Registrant (h, what, obj);
- mCdmaSubscriptionChangedRegistrants.add(r);
+ mCdmaSubscriptionChangedRegistrants.addUnique(h, what, obj);
}
@Override
@@ -759,8 +724,7 @@
@Override
public void registerForCdmaPrlChanged(Handler h, int what, Object obj) {
- Registrant r = new Registrant (h, what, obj);
- mCdmaPrlChangedRegistrants.add(r);
+ mCdmaPrlChangedRegistrants.addUnique(h, what, obj);
}
@Override
@@ -770,8 +734,7 @@
@Override
public void registerForExitEmergencyCallbackMode(Handler h, int what, Object obj) {
- Registrant r = new Registrant (h, what, obj);
- mExitEmergencyCallbackModeRegistrants.add(r);
+ mExitEmergencyCallbackModeRegistrants.addUnique(h, what, obj);
}
@Override
@@ -781,8 +744,7 @@
@Override
public void registerForHardwareConfigChanged(Handler h, int what, Object obj) {
- Registrant r = new Registrant (h, what, obj);
- mHardwareConfigChangeRegistrants.add(r);
+ mHardwareConfigChangeRegistrants.addUnique(h, what, obj);
}
@Override
@@ -793,7 +755,7 @@
@Override
public void registerForNetworkScanResult(Handler h, int what, Object obj) {
Registrant r = new Registrant(h, what, obj);
- mRilNetworkScanResultRegistrants.add(r);
+ mRilNetworkScanResultRegistrants.addUnique(h, what, obj);
}
@Override
@@ -806,10 +768,10 @@
*/
@Override
public void registerForRilConnected(Handler h, int what, Object obj) {
- Registrant r = new Registrant (h, what, obj);
- mRilConnectedRegistrants.add(r);
+ mRilConnectedRegistrants.addUnique(h, what, obj);
if (mRilVersion != -1) {
- r.notifyRegistrant(new AsyncResult(null, new Integer(mRilVersion), null));
+ Message.obtain(h, what, new AsyncResult(obj, new Integer(mRilVersion), null))
+ .sendToTarget();
}
}
@@ -819,8 +781,7 @@
}
public void registerForSubscriptionStatusChanged(Handler h, int what, Object obj) {
- Registrant r = new Registrant (h, what, obj);
- mSubscriptionStatusRegistrants.add(r);
+ mSubscriptionStatusRegistrants.addUnique(h, what, obj);
}
public void unregisterForSubscriptionStatusChanged(Handler h) {
@@ -829,8 +790,7 @@
@Override
public void registerForEmergencyNumberList(Handler h, int what, Object obj) {
- Registrant r = new Registrant(h, what, obj);
- mEmergencyNumberListRegistrants.add(r);
+ mEmergencyNumberListRegistrants.addUnique(h, what, obj);
}
@Override
@@ -901,8 +861,7 @@
*/
@Override
public void registerForCellInfoList(Handler h, int what, Object obj) {
- Registrant r = new Registrant (h, what, obj);
- mRilCellInfoListRegistrants.add(r);
+ mRilCellInfoListRegistrants.addUnique(h, what, obj);
}
@Override
public void unregisterForCellInfoList(Handler h) {
@@ -911,8 +870,7 @@
@Override
public void registerForPhysicalChannelConfiguration(Handler h, int what, Object obj) {
- Registrant r = new Registrant(h, what, obj);
- mPhysicalChannelConfigurationRegistrants.add(r);
+ mPhysicalChannelConfigurationRegistrants.addUnique(h, what, obj);
}
@Override
@@ -922,9 +880,7 @@
@Override
public void registerForSrvccStateChanged(Handler h, int what, Object obj) {
- Registrant r = new Registrant (h, what, obj);
-
- mSrvccStateRegistrants.add(r);
+ mSrvccStateRegistrants.addUnique(h, what, obj);
}
@Override
@@ -961,8 +917,7 @@
@Override
public void registerForRadioCapabilityChanged(Handler h, int what, Object obj) {
- Registrant r = new Registrant(h, what, obj);
- mPhoneRadioCapabilityChangedRegistrants.add(r);
+ mPhoneRadioCapabilityChangedRegistrants.addUnique(h, what, obj);
}
@Override
@@ -984,10 +939,8 @@
@Override
public void registerForLceInfo(Handler h, int what, Object obj) {
- Registrant r = new Registrant(h, what, obj);
-
synchronized (mStateMonitor) {
- mLceInfoRegistrants.add(r);
+ mLceInfoRegistrants.addUnique(h, what, obj);
}
}
@@ -1033,7 +986,7 @@
Registrant r = new Registrant(h, what, obj);
synchronized (mStateMonitor) {
- mNattKeepaliveStatusRegistrants.add(r);
+ mNattKeepaliveStatusRegistrants.addUnique(h, what, obj);
}
}
diff --git a/src/java/com/android/internal/telephony/BtSmsInterfaceManager.java b/src/java/com/android/internal/telephony/BtSmsInterfaceManager.java
index 408d7ab..67e07bc 100644
--- a/src/java/com/android/internal/telephony/BtSmsInterfaceManager.java
+++ b/src/java/com/android/internal/telephony/BtSmsInterfaceManager.java
@@ -45,13 +45,13 @@
BluetoothAdapter btAdapter = BluetoothAdapter.getDefaultAdapter();
if (btAdapter == null) {
// No bluetooth service on this platform?
- sendErrorInPendingIntent(sentIntent, SmsManager.RESULT_ERROR_NO_SERVICE);
+ sendErrorInPendingIntent(sentIntent, SmsManager.RESULT_NO_BLUETOOTH_SERVICE);
return;
}
BluetoothDevice device = btAdapter.getRemoteDevice(info.getIccId());
if (device == null) {
Log.d(LOG_TAG, "Bluetooth device addr invalid: " + info.getIccId());
- sendErrorInPendingIntent(sentIntent, SmsManager.RESULT_ERROR_NO_SERVICE);
+ sendErrorInPendingIntent(sentIntent, SmsManager.RESULT_INVALID_BLUETOOTH_ADDRESS);
return;
}
btAdapter.getProfileProxy(ActivityThread.currentApplication().getApplicationContext(),
@@ -113,7 +113,7 @@
public void onServiceDisconnected(int profile) {
if (mMessage != null) {
Log.d(LOG_TAG, "Bluetooth disconnected before sending the message");
- sendErrorInPendingIntent(mSentIntent, SmsManager.RESULT_ERROR_NO_SERVICE);
+ sendErrorInPendingIntent(mSentIntent, SmsManager.RESULT_BLUETOOTH_DISCONNECTED);
mMessage = null;
}
}
diff --git a/src/java/com/android/internal/telephony/Call.java b/src/java/com/android/internal/telephony/Call.java
index 37fefd9..cce5ee7 100644
--- a/src/java/com/android/internal/telephony/Call.java
+++ b/src/java/com/android/internal/telephony/Call.java
@@ -16,25 +16,43 @@
package com.android.internal.telephony;
+import android.annotation.UnsupportedAppUsage;
import android.telecom.ConferenceParticipant;
+import android.telephony.Rlog;
import java.util.ArrayList;
import java.util.List;
-import android.annotation.UnsupportedAppUsage;
-import android.telephony.Rlog;
-
/**
* {@hide}
*/
public abstract class Call {
protected final String LOG_TAG = "Call";
- /* Enums */
+ @UnsupportedAppUsage
+ public Call() {
+ }
+ /* Enums */
+ @UnsupportedAppUsage(implicitMember = "values()[Lcom/android/internal/telephony/Call$State;")
public enum State {
@UnsupportedAppUsage
- IDLE, ACTIVE, HOLDING, DIALING, ALERTING, INCOMING, WAITING, DISCONNECTED, DISCONNECTING;
+ IDLE,
+ ACTIVE,
+ @UnsupportedAppUsage
+ HOLDING,
+ @UnsupportedAppUsage
+ DIALING,
+ @UnsupportedAppUsage
+ ALERTING,
+ @UnsupportedAppUsage
+ INCOMING,
+ @UnsupportedAppUsage
+ WAITING,
+ @UnsupportedAppUsage
+ DISCONNECTED,
+ @UnsupportedAppUsage
+ DISCONNECTING;
@UnsupportedAppUsage
public boolean isAlive() {
diff --git a/src/java/com/android/internal/telephony/CallForwardInfo.java b/src/java/com/android/internal/telephony/CallForwardInfo.java
index ea3ba27..d9967ad 100644
--- a/src/java/com/android/internal/telephony/CallForwardInfo.java
+++ b/src/java/com/android/internal/telephony/CallForwardInfo.java
@@ -28,6 +28,10 @@
private static final String TAG = "CallForwardInfo";
@UnsupportedAppUsage
+ public CallForwardInfo() {
+ }
+
+ @UnsupportedAppUsage
public int status; /*1 = active, 0 = not active */
@UnsupportedAppUsage
public int reason; /* from TS 27.007 7.11 "reason" */
diff --git a/src/java/com/android/internal/telephony/CallTracker.java b/src/java/com/android/internal/telephony/CallTracker.java
index 002e082..f24b3c8 100644
--- a/src/java/com/android/internal/telephony/CallTracker.java
+++ b/src/java/com/android/internal/telephony/CallTracker.java
@@ -75,6 +75,10 @@
protected static final int EVENT_THREE_WAY_DIAL_L2_RESULT_CDMA = 16;
protected static final int EVENT_THREE_WAY_DIAL_BLANK_FLASH = 20;
+ @UnsupportedAppUsage
+ public CallTracker() {
+ }
+
protected void pollCallsWhenSafe() {
mNeedsPoll = true;
diff --git a/src/java/com/android/internal/telephony/CarrierServiceBindHelper.java b/src/java/com/android/internal/telephony/CarrierServiceBindHelper.java
index 59bd9b4..0393ffa 100644
--- a/src/java/com/android/internal/telephony/CarrierServiceBindHelper.java
+++ b/src/java/com/android/internal/telephony/CarrierServiceBindHelper.java
@@ -23,6 +23,7 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
+import android.content.pm.ComponentInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.os.Bundle;
@@ -42,6 +43,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.content.PackageMonitor;
+import com.android.internal.telephony.util.TelephonyUtils;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.List;
@@ -228,8 +230,9 @@
String candidateServiceClass = null;
if (carrierResolveInfo != null) {
metadata = carrierResolveInfo.serviceInfo.metaData;
- candidateServiceClass =
- carrierResolveInfo.getComponentInfo().getComponentName().getClassName();
+ ComponentInfo componentInfo = TelephonyUtils.getComponentInfo(carrierResolveInfo);
+ candidateServiceClass = new ComponentName(componentInfo.packageName,
+ componentInfo.name).getClassName();
}
// Only bind if the service wants it
diff --git a/src/java/com/android/internal/telephony/CellBroadcastServiceManager.java b/src/java/com/android/internal/telephony/CellBroadcastServiceManager.java
index 2e82747..132ea3b 100644
--- a/src/java/com/android/internal/telephony/CellBroadcastServiceManager.java
+++ b/src/java/com/android/internal/telephony/CellBroadcastServiceManager.java
@@ -25,11 +25,13 @@
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
+import android.os.RemoteCallback;
import android.os.RemoteException;
import android.telephony.CellBroadcastService;
import android.telephony.ICellBroadcastService;
import android.util.LocalLog;
import android.util.Log;
+import android.util.Pair;
import com.android.internal.telephony.cdma.SmsMessage;
@@ -57,6 +59,7 @@
/** New SMS cell broadcast received as an AsyncResult. */
private static final int EVENT_NEW_GSM_SMS_CB = 0;
private static final int EVENT_NEW_CDMA_SMS_CB = 1;
+ private static final int EVENT_NEW_CDMA_SCP_MESSAGE = 2;
private boolean mEnabled;
public CellBroadcastServiceManager(Context context, Phone phone) {
@@ -66,7 +69,7 @@
}
/**
- * Send a GSM CB message to the CellBroadcastServieManager's handler.
+ * Send a GSM CB message to the CellBroadcastServiceManager's handler.
* @param m the message
*/
public void sendGsmMessageToHandler(Message m) {
@@ -75,7 +78,7 @@
}
/**
- * Send a CDMA CB message to the CellBroadcastServieManager's handler.
+ * Send a CDMA CB message to the CellBroadcastServiceManager's handler.
* @param sms the SmsMessage to forward
*/
public void sendCdmaMessageToHandler(SmsMessage sms) {
@@ -86,6 +89,17 @@
}
/**
+ * Send a CDMA Service Category Program message to the CellBroadcastServiceManager's handler.
+ * @param sms the SCP message
+ */
+ public void sendCdmaScpMessageToHandler(SmsMessage sms, RemoteCallback callback) {
+ Message m = Message.obtain();
+ m.what = EVENT_NEW_CDMA_SCP_MESSAGE;
+ m.obj = Pair.create(sms, callback);
+ mModuleCellBroadcastHandler.sendMessage(m);
+ }
+
+ /**
* Enable the CB module. The CellBroadcastService will be bound to and CB messages from the
* RIL will be forwarded to the module.
*/
@@ -99,7 +113,9 @@
public void disable() {
mEnabled = false;
mPhone.mCi.unSetOnNewGsmBroadcastSms(mModuleCellBroadcastHandler);
- mContext.unbindService(sServiceConnection);
+ if (sServiceConnection.mService != null) {
+ mContext.unbindService(sServiceConnection);
+ }
}
/**
@@ -138,7 +154,16 @@
SmsMessage sms = (SmsMessage) msg.obj;
cellBroadcastService.handleCdmaCellBroadcastSms(mPhone.getPhoneId(),
sms.getEnvelopeBearerData(), sms.getEnvelopeServiceCategory());
-
+ } else if (msg.what == EVENT_NEW_CDMA_SCP_MESSAGE) {
+ mLocalLog.log("CDMA SCP message for phone " + mPhone.getPhoneId());
+ Pair<SmsMessage, RemoteCallback> smsAndCallback =
+ (Pair<SmsMessage, RemoteCallback>) msg.obj;
+ SmsMessage sms = smsAndCallback.first;
+ RemoteCallback callback = smsAndCallback.second;
+ cellBroadcastService.handleCdmaScpMessage(mPhone.getPhoneId(),
+ sms.getSmsCbProgramData(),
+ sms.getOriginatingAddress(),
+ callback);
}
} catch (RemoteException e) {
Log.e(TAG, "Failed to connect to default app: "
@@ -154,7 +179,16 @@
Intent intent = new Intent(CellBroadcastService.CELL_BROADCAST_SERVICE_INTERFACE);
intent.setPackage(mCellBroadcastServicePackage);
if (sServiceConnection.mService == null) {
- mContext.bindService(intent, sServiceConnection, Context.BIND_AUTO_CREATE);
+ boolean serviceWasBound = mContext.bindService(intent, sServiceConnection,
+ Context.BIND_AUTO_CREATE);
+ Log.d(TAG, "serviceWasBound=" + serviceWasBound);
+ if (!serviceWasBound) {
+ Log.e(TAG, "Unable to bind to service");
+ mLocalLog.log("Unable to bind to service");
+ return;
+ }
+ } else {
+ Log.d(TAG, "skipping bindService because connection already exists");
}
mPhone.mCi.setOnNewGsmBroadcastSms(mModuleCellBroadcastHandler, EVENT_NEW_GSM_SMS_CB,
null);
diff --git a/src/java/com/android/internal/telephony/CommandException.java b/src/java/com/android/internal/telephony/CommandException.java
index 4642d90..31ffcd2 100644
--- a/src/java/com/android/internal/telephony/CommandException.java
+++ b/src/java/com/android/internal/telephony/CommandException.java
@@ -123,6 +123,7 @@
OEM_ERROR_23,
OEM_ERROR_24,
OEM_ERROR_25,
+ REQUEST_CANCELLED,
}
@UnsupportedAppUsage
@@ -321,6 +322,8 @@
return new CommandException(Error.OEM_ERROR_24);
case RILConstants.OEM_ERROR_25:
return new CommandException(Error.OEM_ERROR_25);
+ case RILConstants.REQUEST_CANCELLED:
+ return new CommandException(Error.REQUEST_CANCELLED);
default:
Rlog.e("GSM", "Unrecognized RIL errno " + ril_errno);
diff --git a/src/java/com/android/internal/telephony/DriverCall.java b/src/java/com/android/internal/telephony/DriverCall.java
index 8c2c3db..3a7947d 100644
--- a/src/java/com/android/internal/telephony/DriverCall.java
+++ b/src/java/com/android/internal/telephony/DriverCall.java
@@ -17,9 +17,8 @@
package com.android.internal.telephony;
import android.annotation.UnsupportedAppUsage;
-import android.telephony.Rlog;
-import java.lang.Comparable;
import android.telephony.PhoneNumberUtils;
+import android.telephony.Rlog;
/**
* {@hide}
@@ -27,6 +26,8 @@
public class DriverCall implements Comparable<DriverCall> {
static final String LOG_TAG = "DriverCall";
+ @UnsupportedAppUsage(implicitMember =
+ "values()[Lcom/android/internal/telephony/DriverCall$State;")
public enum State {
@UnsupportedAppUsage
ACTIVE,
diff --git a/src/java/com/android/internal/telephony/IIccPhoneBook.aidl b/src/java/com/android/internal/telephony/IIccPhoneBook.aidl
index fbbdee2..dc990de 100644
--- a/src/java/com/android/internal/telephony/IIccPhoneBook.aidl
+++ b/src/java/com/android/internal/telephony/IIccPhoneBook.aidl
@@ -30,6 +30,7 @@
* @param efid the EF id of a ADN-like SIM
* @return List of AdnRecord
*/
+ @UnsupportedAppUsage
List<AdnRecord> getAdnRecordsInEf(int efid);
/**
@@ -40,6 +41,7 @@
* @param subId user preferred subId
* @return List of AdnRecord
*/
+ @UnsupportedAppUsage
List<AdnRecord> getAdnRecordsInEfForSubscriber(int subId, int efid);
/**
@@ -60,6 +62,7 @@
* @param pin2 required to update EF_FDN, otherwise must be null
* @return true for success
*/
+ @UnsupportedAppUsage
boolean updateAdnRecordsInEfBySearch(int efid,
String oldTag, String oldPhoneNumber,
String newTag, String newPhoneNumber,
@@ -138,6 +141,7 @@
* recordSizes[1] is the total length of the EF file
* recordSizes[2] is the number of records in the EF file
*/
+ @UnsupportedAppUsage
int[] getAdnRecordsSize(int efid);
/**
@@ -150,6 +154,7 @@
* recordSizes[1] is the total length of the EF file
* recordSizes[2] is the number of records in the EF file
*/
+ @UnsupportedAppUsage
int[] getAdnRecordsSizeForSubscriber(int subId, int efid);
}
diff --git a/src/java/com/android/internal/telephony/IccProvider.java b/src/java/com/android/internal/telephony/IccProvider.java
index 3ac4027..ae5cd7b 100644
--- a/src/java/com/android/internal/telephony/IccProvider.java
+++ b/src/java/com/android/internal/telephony/IccProvider.java
@@ -81,6 +81,10 @@
private SubscriptionManager mSubscriptionManager;
+ @UnsupportedAppUsage
+ public IccProvider() {
+ }
+
@Override
public boolean onCreate() {
mSubscriptionManager = SubscriptionManager.from(getContext());
diff --git a/src/java/com/android/internal/telephony/MultiSimSettingController.java b/src/java/com/android/internal/telephony/MultiSimSettingController.java
index 5ae88f8..1ce360f 100644
--- a/src/java/com/android/internal/telephony/MultiSimSettingController.java
+++ b/src/java/com/android/internal/telephony/MultiSimSettingController.java
@@ -36,6 +36,7 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.os.AsyncResult;
import android.os.Handler;
import android.os.Message;
import android.os.ParcelUuid;
@@ -79,6 +80,7 @@
private static final int EVENT_SUBSCRIPTION_GROUP_CHANGED = 5;
private static final int EVENT_DEFAULT_DATA_SUBSCRIPTION_CHANGED = 6;
private static final int EVENT_CARRIER_CONFIG_CHANGED = 7;
+ private static final int EVENT_MULTI_SIM_CONFIG_CHANGED = 8;
@Retention(RetentionPolicy.SOURCE)
@IntDef(prefix = {"PRIMARY_SUB_"},
@@ -188,6 +190,9 @@
mCarrierConfigLoadedSubIds = new int[phoneCount];
Arrays.fill(mCarrierConfigLoadedSubIds, SubscriptionManager.INVALID_SUBSCRIPTION_ID);
+ PhoneConfigurationManager.registerForMultiSimConfigChange(
+ this, EVENT_MULTI_SIM_CONFIG_CHANGED, null);
+
context.registerReceiver(mIntentReceiver, new IntentFilter(
CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED));
}
@@ -268,6 +273,9 @@
int subId = msg.arg2;
onCarrierConfigChanged(phoneId, subId);
break;
+ case EVENT_MULTI_SIM_CONFIG_CHANGED:
+ int activeModems = (int) ((AsyncResult) msg.obj).result;
+ onMultiSimConfigChanged(activeModems);
}
}
@@ -358,6 +366,14 @@
return true;
}
+ private void onMultiSimConfigChanged(int activeModems) {
+ // Clear mCarrierConfigLoadedSubIds. Other actions will responds to active
+ // subscription change.
+ for (int phoneId = activeModems; phoneId < mCarrierConfigLoadedSubIds.length; phoneId++) {
+ mCarrierConfigLoadedSubIds[phoneId] = INVALID_SUBSCRIPTION_ID;
+ }
+ }
+
/**
* Wait for subInfo initialization (after boot up) and carrier config load for all active
* subscriptions before re-evaluate multi SIM settings.
diff --git a/src/java/com/android/internal/telephony/NetworkScanRequestTracker.java b/src/java/com/android/internal/telephony/NetworkScanRequestTracker.java
index ba1081b..c848358 100644
--- a/src/java/com/android/internal/telephony/NetworkScanRequestTracker.java
+++ b/src/java/com/android/internal/telephony/NetworkScanRequestTracker.java
@@ -525,12 +525,12 @@
// stopped, a new scan will automatically start with nsri.
// The new scan can interrupt the live scan only when all the below requirements are met:
// 1. There is 1 live scan and no other pending scan
- // 2. The new scan is requested by mobile network setting menu (owned by PHONE process)
+ // 2. The new scan is requested by mobile network setting menu (owned by SYSTEM process)
// 3. The live scan is not requested by mobile network setting menu
private synchronized boolean interruptLiveScan(NetworkScanRequestInfo nsri) {
if (mLiveRequestInfo != null && mPendingRequestInfo == null
- && nsri.mUid == Process.PHONE_UID
- && mLiveRequestInfo.mUid != Process.PHONE_UID) {
+ && nsri.mUid == Process.SYSTEM_UID
+ && mLiveRequestInfo.mUid != Process.SYSTEM_UID) {
doInterruptScan(mLiveRequestInfo.mScanId);
mPendingRequestInfo = nsri;
notifyMessenger(mLiveRequestInfo, TelephonyScanManager.CALLBACK_SCAN_ERROR,
diff --git a/src/java/com/android/internal/telephony/ProxyController.java b/src/java/com/android/internal/telephony/ProxyController.java
index a3b5c04..54fcac9 100644
--- a/src/java/com/android/internal/telephony/ProxyController.java
+++ b/src/java/com/android/internal/telephony/ProxyController.java
@@ -26,6 +26,7 @@
import android.os.PowerManager.WakeLock;
import android.telephony.RadioAccessFamily;
import android.telephony.Rlog;
+import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.util.Log;
@@ -151,7 +152,7 @@
public void registerForAllDataDisconnected(int subId, Handler h, int what) {
int phoneId = SubscriptionController.getInstance().getPhoneId(subId);
- if (phoneId >= 0 && phoneId < TelephonyManager.getDefault().getSupportedModemCount()) {
+ if (SubscriptionManager.isValidPhoneId(phoneId)) {
mPhones[phoneId].registerForAllDataDisconnected(h, what);
}
}
@@ -159,7 +160,7 @@
public void unregisterForAllDataDisconnected(int subId, Handler h) {
int phoneId = SubscriptionController.getInstance().getPhoneId(subId);
- if (phoneId >= 0 && phoneId < TelephonyManager.getDefault().getSupportedModemCount()) {
+ if (SubscriptionManager.isValidPhoneId(phoneId)) {
mPhones[phoneId].unregisterForAllDataDisconnected(h);
}
}
@@ -168,7 +169,7 @@
public boolean areAllDataDisconnected(int subId) {
int phoneId = SubscriptionController.getInstance().getPhoneId(subId);
- if (phoneId >= 0 && phoneId < TelephonyManager.getDefault().getSupportedModemCount()) {
+ if (SubscriptionManager.isValidPhoneId(phoneId)) {
return mPhones[phoneId].areAllDataDisconnected();
} else {
// if we can't find a phone for the given subId, it is disconnected.
diff --git a/src/java/com/android/internal/telephony/RIL.java b/src/java/com/android/internal/telephony/RIL.java
index dd4bdba..4d7a687 100644
--- a/src/java/com/android/internal/telephony/RIL.java
+++ b/src/java/com/android/internal/telephony/RIL.java
@@ -393,7 +393,7 @@
}
}
- private void resetProxyAndRequestList() {
+ private synchronized void resetProxyAndRequestList() {
mRadioProxy = null;
mOemHookProxy = null;
diff --git a/src/java/com/android/internal/telephony/SMSDispatcher.java b/src/java/com/android/internal/telephony/SMSDispatcher.java
index 8c1e0e8..0f3d8aa 100644
--- a/src/java/com/android/internal/telephony/SMSDispatcher.java
+++ b/src/java/com/android/internal/telephony/SMSDispatcher.java
@@ -17,7 +17,6 @@
package com.android.internal.telephony;
import static android.Manifest.permission.SEND_SMS_NO_CONFIRMATION;
-import static android.telephony.SmsManager.RESULT_ERROR_FDN_CHECK_FAILURE;
import static android.telephony.SmsManager.RESULT_ERROR_GENERIC_FAILURE;
import static android.telephony.SmsManager.RESULT_ERROR_LIMIT_EXCEEDED;
import static android.telephony.SmsManager.RESULT_ERROR_NONE;
@@ -340,8 +339,8 @@
Rlog.d(TAG, "SMSDispatcher: EVENT_STOP_SENDING - "
+ "sending LIMIT_EXCEEDED error code.");
} else {
- error = RESULT_ERROR_GENERIC_FAILURE;
- Rlog.e(TAG, "SMSDispatcher: EVENT_STOP_SENDING - unexpected cases.");
+ error = SmsManager.RESULT_UNEXPECTED_EVENT_STOP_SENDING;
+ Rlog.e(TAG, "SMSDispatcher: EVENT_STOP_SENDING - unexpected cases.");
}
handleSmsTrackersFailure(trackers, error, NO_ERROR_CODE);
@@ -649,7 +648,8 @@
private void sendSubmitPdu(SmsTracker[] trackers) {
if (shouldBlockSmsForEcbm()) {
Rlog.d(TAG, "Block SMS in Emergency Callback mode");
- handleSmsTrackersFailure(trackers, RESULT_ERROR_NO_SERVICE, NO_ERROR_CODE);
+ handleSmsTrackersFailure(trackers, SmsManager.RESULT_SMS_BLOCKED_DURING_EMERGENCY,
+ NO_ERROR_CODE);
} else {
sendRawPdu(trackers);
}
@@ -729,16 +729,62 @@
if (ar.result != null) {
errorCode = ((SmsResponse)ar.result).mErrorCode;
}
- int error = RESULT_ERROR_GENERIC_FAILURE;
- if (((CommandException)(ar.exception)).getCommandError()
- == CommandException.Error.FDN_CHECK_FAILURE) {
- error = RESULT_ERROR_FDN_CHECK_FAILURE;
- }
+ int error = rilErrorToSmsManagerResult(((CommandException) (ar.exception))
+ .getCommandError());
tracker.onFailed(mContext, error, errorCode);
}
}
}
+ private static int rilErrorToSmsManagerResult(CommandException.Error rilError) {
+ switch (rilError) {
+ case RADIO_NOT_AVAILABLE:
+ return SmsManager.RESULT_RIL_RADIO_NOT_AVAILABLE;
+ case SMS_FAIL_RETRY:
+ return SmsManager.RESULT_RIL_SMS_SEND_FAIL_RETRY;
+ case NETWORK_REJECT:
+ return SmsManager.RESULT_RIL_NETWORK_REJECT;
+ case INVALID_STATE:
+ return SmsManager.RESULT_RIL_INVALID_STATE;
+ case INVALID_ARGUMENTS:
+ return SmsManager.RESULT_RIL_INVALID_ARGUMENTS;
+ case NO_MEMORY:
+ return SmsManager.RESULT_RIL_NO_MEMORY;
+ case REQUEST_RATE_LIMITED:
+ return SmsManager.RESULT_RIL_REQUEST_RATE_LIMITED;
+ case INVALID_SMS_FORMAT:
+ return SmsManager.RESULT_RIL_INVALID_SMS_FORMAT;
+ case SYSTEM_ERR:
+ return SmsManager.RESULT_RIL_SYSTEM_ERR;
+ case ENCODING_ERR:
+ return SmsManager.RESULT_RIL_ENCODING_ERR;
+ case MODEM_ERR:
+ return SmsManager.RESULT_RIL_MODEM_ERR;
+ case NETWORK_ERR:
+ return SmsManager.RESULT_RIL_NETWORK_ERR;
+ case INTERNAL_ERR:
+ return SmsManager.RESULT_RIL_INTERNAL_ERR;
+ case REQUEST_NOT_SUPPORTED:
+ return SmsManager.RESULT_RIL_REQUEST_NOT_SUPPORTED;
+ case INVALID_MODEM_STATE:
+ return SmsManager.RESULT_RIL_INVALID_MODEM_STATE;
+ case NETWORK_NOT_READY:
+ return SmsManager.RESULT_RIL_NETWORK_NOT_READY;
+ case OPERATION_NOT_ALLOWED:
+ return SmsManager.RESULT_RIL_OPERATION_NOT_ALLOWED;
+ case NO_RESOURCES:
+ return SmsManager.RESULT_RIL_NO_RESOURCES;
+ case REQUEST_CANCELLED:
+ return SmsManager.RESULT_RIL_CANCELLED;
+ case SIM_ABSENT:
+ return SmsManager.RESULT_RIL_SIM_ABSENT;
+ case FDN_CHECK_FAILURE:
+ return SmsManager.RESULT_ERROR_FDN_CHECK_FAILURE;
+ default:
+ return RESULT_ERROR_GENERIC_FAILURE;
+ }
+ }
+
/**
* Handles outbound message when the phone is not in service.
*
diff --git a/src/java/com/android/internal/telephony/ServiceStateTracker.java b/src/java/com/android/internal/telephony/ServiceStateTracker.java
index 32b0cff..561a051 100755
--- a/src/java/com/android/internal/telephony/ServiceStateTracker.java
+++ b/src/java/com/android/internal/telephony/ServiceStateTracker.java
@@ -1570,10 +1570,11 @@
mNrStateChangedRegistrants.notifyRegistrants();
hasChanged = true;
}
+ hasChanged |= RatRatcheter
+ .updateBandwidths(getBandwidthsFromConfigs(list), mSS);
// Notify NR frequency, NR connection status or bandwidths changed.
- if (hasChanged
- || RatRatcheter.updateBandwidths(getBandwidthsFromConfigs(list), mSS)) {
+ if (hasChanged) {
mPhone.notifyServiceStateChanged(mSS);
}
}
diff --git a/src/java/com/android/internal/telephony/SmsDispatchersController.java b/src/java/com/android/internal/telephony/SmsDispatchersController.java
index c75b478..6b7c8a3 100644
--- a/src/java/com/android/internal/telephony/SmsDispatchersController.java
+++ b/src/java/com/android/internal/telephony/SmsDispatchersController.java
@@ -462,7 +462,7 @@
|| (map.containsKey("data") && map.containsKey("destPort"))))) {
// should never come here...
Rlog.e(TAG, "sendRetrySms failed to re-encode per missing fields!");
- tracker.onFailed(mContext, SmsManager.RESULT_ERROR_GENERIC_FAILURE, NO_ERROR_CODE);
+ tracker.onFailed(mContext, SmsManager.RESULT_SMS_SEND_RETRY_FAILED, NO_ERROR_CODE);
return;
}
String scAddr = (String) map.get("scAddr");
diff --git a/src/java/com/android/internal/telephony/SubscriptionController.java b/src/java/com/android/internal/telephony/SubscriptionController.java
index 34ea10d..24bb42d 100644
--- a/src/java/com/android/internal/telephony/SubscriptionController.java
+++ b/src/java/com/android/internal/telephony/SubscriptionController.java
@@ -61,6 +61,7 @@
import com.android.internal.telephony.uicc.IccUtils;
import com.android.internal.telephony.uicc.UiccCard;
import com.android.internal.telephony.uicc.UiccController;
+import com.android.internal.telephony.util.TelephonyUtils;
import com.android.internal.util.ArrayUtils;
import java.io.FileDescriptor;
@@ -1332,17 +1333,12 @@
if (DBG) logdl("[clearSubInfoRecord]+ iccId:" + " slotIndex:" + slotIndex);
// update simInfo db with invalid slot index
- List<SubscriptionInfo> oldSubInfo = getSubInfoUsingSlotIndexPrivileged(slotIndex);
ContentResolver resolver = mContext.getContentResolver();
ContentValues value = new ContentValues(1);
- value.put(SubscriptionManager.SIM_SLOT_INDEX,
- SubscriptionManager.INVALID_SIM_SLOT_INDEX);
- if (oldSubInfo != null) {
- for (int i = 0; i < oldSubInfo.size(); i++) {
- resolver.update(SubscriptionManager.getUriForSubscriptionId(
- oldSubInfo.get(i).getSubscriptionId()), value, null, null);
- }
- }
+ value.put(SubscriptionManager.SIM_SLOT_INDEX, SubscriptionManager.INVALID_SIM_SLOT_INDEX);
+ String where = "(" + SubscriptionManager.SIM_SLOT_INDEX + "=" + slotIndex + ")";
+ resolver.update(SubscriptionManager.CONTENT_URI, value, where, null);
+
// Refresh the Cache of Active Subscription Info List
refreshCachedActiveSubscriptionInfoList();
@@ -3743,7 +3739,7 @@
*/
@NonNull
public String getDataEnabledOverrideRules(int subId) {
- return TextUtils.emptyIfNull(getSubscriptionProperty(subId,
+ return TelephonyUtils.emptyIfNull(getSubscriptionProperty(subId,
SubscriptionManager.DATA_ENABLED_OVERRIDE_RULES));
}
diff --git a/src/java/com/android/internal/telephony/SubscriptionInfoUpdater.java b/src/java/com/android/internal/telephony/SubscriptionInfoUpdater.java
index 749e800..f20870a 100644
--- a/src/java/com/android/internal/telephony/SubscriptionInfoUpdater.java
+++ b/src/java/com/android/internal/telephony/SubscriptionInfoUpdater.java
@@ -75,7 +75,7 @@
public class SubscriptionInfoUpdater extends Handler {
private static final String LOG_TAG = "SubscriptionInfoUpdater";
@UnsupportedAppUsage
- private static final int PROJECT_SIM_NUM = TelephonyManager.getDefault()
+ private static final int SUPPORTED_MODEM_COUNT = TelephonyManager.getDefault()
.getSupportedModemCount();
private static final boolean DBG = true;
@@ -92,6 +92,7 @@
private static final int EVENT_SIM_READY = 10;
private static final int EVENT_SIM_IMSI = 11;
private static final int EVENT_REFRESH_EMBEDDED_SUBSCRIPTIONS = 12;
+ private static final int EVENT_MULTI_SIM_CONFIG_CHANGED = 13;
private static final String ICCID_STRING_FOR_NO_SIM = "";
@@ -105,9 +106,9 @@
private static Context sContext = null;
@UnsupportedAppUsage
- private static String[] sIccId = new String[PROJECT_SIM_NUM];
- private static int[] sSimCardState = new int[PROJECT_SIM_NUM];
- private static int[] sSimApplicationState = new int[PROJECT_SIM_NUM];
+ private static String[] sIccId = new String[SUPPORTED_MODEM_COUNT];
+ private static int[] sSimCardState = new int[SUPPORTED_MODEM_COUNT];
+ private static int[] sSimApplicationState = new int[SUPPORTED_MODEM_COUNT];
private static boolean sIsSubInfoInitialized = false;
private SubscriptionManager mSubscriptionManager = null;
private EuiccManager mEuiccManager;
@@ -153,6 +154,9 @@
mCarrierServiceBindHelper = new CarrierServiceBindHelper(sContext);
initializeCarrierApps();
+
+ PhoneConfigurationManager.registerForMultiSimConfigChange(
+ this, EVENT_MULTI_SIM_CONFIG_CHANGED, null);
}
private void initializeCarrierApps() {
@@ -222,7 +226,7 @@
@UnsupportedAppUsage
private boolean isAllIccIdQueryDone() {
- for (int i = 0; i < TelephonyManager.getDefault().getPhoneCount(); i++) {
+ for (int i = 0; i < TelephonyManager.getDefault().getActiveModemCount(); i++) {
UiccSlot slot = UiccController.getInstance().getUiccSlotForPhone(i);
int slotId = UiccController.getInstance().getSlotIdFromPhoneId(i);
if (sIccId[i] == null || slot == null || !slot.isActive()) {
@@ -334,11 +338,25 @@
});
break;
+ case EVENT_MULTI_SIM_CONFIG_CHANGED:
+ onMultiSimConfigChanged();
default:
logd("Unknown msg:" + msg.what);
}
}
+ private void onMultiSimConfigChanged() {
+ int activeModemCount = ((TelephonyManager) sContext.getSystemService(
+ Context.TELEPHONY_SERVICE)).getActiveModemCount();
+ // For inactive modems, reset its states.
+ for (int phoneId = activeModemCount; phoneId < SUPPORTED_MODEM_COUNT; phoneId++) {
+ SubscriptionController.getInstance().clearSubInfoRecord(phoneId);
+ sIccId[phoneId] = null;
+ sSimCardState[phoneId] = TelephonyManager.SIM_STATE_UNKNOWN;
+ sSimApplicationState[phoneId] = TelephonyManager.SIM_STATE_UNKNOWN;
+ }
+ }
+
private int getCardIdFromPhoneId(int phoneId) {
UiccController uiccController = UiccController.getInstance();
UiccCard card = uiccController.getUiccCardForPhone(phoneId);
diff --git a/src/java/com/android/internal/telephony/TelephonyComponentFactory.java b/src/java/com/android/internal/telephony/TelephonyComponentFactory.java
index a461324..d89ee94 100644
--- a/src/java/com/android/internal/telephony/TelephonyComponentFactory.java
+++ b/src/java/com/android/internal/telephony/TelephonyComponentFactory.java
@@ -42,6 +42,7 @@
import com.android.internal.telephony.imsphone.ImsExternalCallTracker;
import com.android.internal.telephony.imsphone.ImsPhone;
import com.android.internal.telephony.imsphone.ImsPhoneCallTracker;
+import com.android.internal.telephony.nitz.NewNitzStateMachineImpl;
import com.android.internal.telephony.uicc.IccCardStatus;
import com.android.internal.telephony.uicc.UiccCard;
import com.android.internal.telephony.uicc.UiccProfile;
@@ -292,11 +293,17 @@
return new EmergencyNumberTracker(phone, ci);
}
+ private static final boolean USE_NEW_NITZ_STATE_MACHINE = false;
+
/**
* Returns a new {@link NitzStateMachine} instance.
*/
public NitzStateMachine makeNitzStateMachine(GsmCdmaPhone phone) {
- return new NitzStateMachineImpl(phone);
+ if (USE_NEW_NITZ_STATE_MACHINE) {
+ return NewNitzStateMachineImpl.createInstance(phone);
+ } else {
+ return new NitzStateMachineImpl(phone);
+ }
}
public SimActivationTracker makeSimActivationTracker(Phone phone) {
diff --git a/src/java/com/android/internal/telephony/cat/AppInterface.java b/src/java/com/android/internal/telephony/cat/AppInterface.java
index ed7c822..4f6ca86 100755
--- a/src/java/com/android/internal/telephony/cat/AppInterface.java
+++ b/src/java/com/android/internal/telephony/cat/AppInterface.java
@@ -62,13 +62,20 @@
*/
void onCmdResponse(CatResponseMessage resMsg);
+ /**
+ * Dispose when the service is not longer needed.
+ */
+ void dispose();
+
/*
* Enumeration for representing "Type of Command" of proactive commands.
* Those are the only commands which are supported by the Telephony. Any app
* implementation should support those.
* Refer to ETSI TS 102.223 section 9.4
*/
- public static enum CommandType {
+ @UnsupportedAppUsage(implicitMember =
+ "values()[Lcom/android/internal/telephony/cat/AppInterface$CommandType;")
+ enum CommandType {
@UnsupportedAppUsage
DISPLAY_TEXT(0x21),
@UnsupportedAppUsage
diff --git a/src/java/com/android/internal/telephony/cat/CatService.java b/src/java/com/android/internal/telephony/cat/CatService.java
index 1ec2549..f149c6f 100644
--- a/src/java/com/android/internal/telephony/cat/CatService.java
+++ b/src/java/com/android/internal/telephony/cat/CatService.java
@@ -16,12 +16,9 @@
package com.android.internal.telephony.cat;
-import static com.android.internal.telephony.cat.CatCmdMessage.SetupEventListConstants
- .IDLE_SCREEN_AVAILABLE_EVENT;
-import static com.android.internal.telephony.cat.CatCmdMessage.SetupEventListConstants
- .LANGUAGE_SELECTION_EVENT;
-import static com.android.internal.telephony.cat.CatCmdMessage.SetupEventListConstants
- .USER_ACTIVITY_EVENT;
+import static com.android.internal.telephony.cat.CatCmdMessage.SetupEventListConstants.IDLE_SCREEN_AVAILABLE_EVENT;
+import static com.android.internal.telephony.cat.CatCmdMessage.SetupEventListConstants.LANGUAGE_SELECTION_EVENT;
+import static com.android.internal.telephony.cat.CatCmdMessage.SetupEventListConstants.USER_ACTIVITY_EVENT;
import android.annotation.UnsupportedAppUsage;
import android.app.ActivityManagerNative;
@@ -38,7 +35,6 @@
import android.os.LocaleList;
import android.os.Message;
import android.os.RemoteException;
-import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import com.android.internal.telephony.CommandsInterface;
@@ -252,6 +248,7 @@
}
@UnsupportedAppUsage
+ @Override
public void dispose() {
synchronized (sInstanceLock) {
CatLog.d(this, "Disposing CatService object");
@@ -275,7 +272,7 @@
mMsgDecoder = null;
removeCallbacksAndMessages(null);
if (sInstance != null) {
- if (SubscriptionManager.isValidSlotIndex(mSlotId)) {
+ if (mSlotId >= 0 && mSlotId < sInstance.length) {
sInstance[mSlotId] = null;
} else {
CatLog.d(this, "error: invaild slot id: " + mSlotId);
diff --git a/src/java/com/android/internal/telephony/cat/ResponseData.java b/src/java/com/android/internal/telephony/cat/ResponseData.java
index 600514f..4d0a2d2 100644
--- a/src/java/com/android/internal/telephony/cat/ResponseData.java
+++ b/src/java/com/android/internal/telephony/cat/ResponseData.java
@@ -16,20 +16,25 @@
package com.android.internal.telephony.cat;
-import com.android.internal.telephony.EncodeException;
-import com.android.internal.telephony.GsmAlphabet;
-import java.util.Calendar;
-import java.util.TimeZone;
+import android.annotation.UnsupportedAppUsage;
import android.os.SystemProperties;
import android.text.TextUtils;
+import com.android.internal.telephony.EncodeException;
+import com.android.internal.telephony.GsmAlphabet;
import com.android.internal.telephony.cat.AppInterface.CommandType;
-import android.annotation.UnsupportedAppUsage;
import java.io.ByteArrayOutputStream;
import java.io.UnsupportedEncodingException;
+import java.util.Calendar;
+import java.util.TimeZone;
abstract class ResponseData {
+
+ @UnsupportedAppUsage
+ ResponseData() {
+ }
+
/**
* Format the data appropriate for TERMINAL RESPONSE and write it into
* the ByteArrayOutputStream object.
diff --git a/src/java/com/android/internal/telephony/cat/ResultCode.java b/src/java/com/android/internal/telephony/cat/ResultCode.java
index 346d74a..adcf53e 100644
--- a/src/java/com/android/internal/telephony/cat/ResultCode.java
+++ b/src/java/com/android/internal/telephony/cat/ResultCode.java
@@ -26,6 +26,7 @@
*
* {@hide}
*/
+@UnsupportedAppUsage(implicitMember = "values()[Lcom/android/internal/telephony/cat/ResultCode;")
public enum ResultCode {
/*
diff --git a/src/java/com/android/internal/telephony/cat/RilMessageDecoder.java b/src/java/com/android/internal/telephony/cat/RilMessageDecoder.java
index 508908e..eeb9dd4 100755
--- a/src/java/com/android/internal/telephony/cat/RilMessageDecoder.java
+++ b/src/java/com/android/internal/telephony/cat/RilMessageDecoder.java
@@ -22,7 +22,6 @@
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
-import com.android.internal.telephony.PhoneConstants;
import com.android.internal.telephony.uicc.IccFileHandler;
import com.android.internal.telephony.uicc.IccUtils;
import com.android.internal.util.State;
@@ -41,6 +40,7 @@
// members
@UnsupportedAppUsage
private CommandParamsFactory mCmdParamsFactory = null;
+ @UnsupportedAppUsage
private RilMessage mCurrentRilMessage = null;
private Handler mCaller = null;
private static int mSimCount = 0;
@@ -88,6 +88,7 @@
*
* @param rilMsg
*/
+ @UnsupportedAppUsage
public void sendStartDecodingMessageParams(RilMessage rilMsg) {
Message msg = obtainMessage(CMD_START);
msg.obj = rilMsg;
@@ -107,6 +108,7 @@
sendMessage(msg);
}
+ @UnsupportedAppUsage
private void sendCmdForExecution(RilMessage rilMsg) {
Message msg = mCaller.obtainMessage(CatService.MSG_ID_RIL_MSG_DECODED,
new RilMessage(rilMsg));
diff --git a/src/java/com/android/internal/telephony/cat/ValueParser.java b/src/java/com/android/internal/telephony/cat/ValueParser.java
index 4e528b6..03d7f67 100644
--- a/src/java/com/android/internal/telephony/cat/ValueParser.java
+++ b/src/java/com/android/internal/telephony/cat/ValueParser.java
@@ -16,13 +16,14 @@
package com.android.internal.telephony.cat;
+import android.annotation.UnsupportedAppUsage;
+import android.content.res.Resources;
+import android.content.res.Resources.NotFoundException;
+
import com.android.internal.telephony.GsmAlphabet;
import com.android.internal.telephony.cat.Duration.TimeUnit;
import com.android.internal.telephony.uicc.IccUtils;
-import android.annotation.UnsupportedAppUsage;
-import android.content.res.Resources;
-import android.content.res.Resources.NotFoundException;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.List;
@@ -62,6 +63,7 @@
* Command Details object is found, ResultException is thrown.
* @throws ResultException
*/
+ @UnsupportedAppUsage
static DeviceIdentities retrieveDeviceIdentities(ComprehensionTlv ctlv)
throws ResultException {
diff --git a/src/java/com/android/internal/telephony/cdma/CdmaInboundSmsHandler.java b/src/java/com/android/internal/telephony/cdma/CdmaInboundSmsHandler.java
index 4f8530a..0608f88 100644
--- a/src/java/com/android/internal/telephony/cdma/CdmaInboundSmsHandler.java
+++ b/src/java/com/android/internal/telephony/cdma/CdmaInboundSmsHandler.java
@@ -22,9 +22,13 @@
import android.content.IntentFilter;
import android.content.res.Resources;
import android.os.Message;
+import android.os.RemoteCallback;
+import android.os.SystemProperties;
import android.provider.Telephony.Sms.Intents;
+import android.telephony.PhoneNumberUtils;
import android.telephony.SmsCbMessage;
import android.telephony.TelephonyManager;
+import android.telephony.cdma.CdmaSmsCbProgramResults;
import com.android.internal.telephony.CellBroadcastHandler;
import com.android.internal.telephony.CommandsInterface;
@@ -36,10 +40,15 @@
import com.android.internal.telephony.SmsStorageMonitor;
import com.android.internal.telephony.TelephonyComponentFactory;
import com.android.internal.telephony.WspTypeDecoder;
+import com.android.internal.telephony.cdma.sms.BearerData;
import com.android.internal.telephony.cdma.sms.CdmaSmsAddress;
import com.android.internal.telephony.cdma.sms.SmsEnvelope;
import com.android.internal.util.HexDump;
+import java.io.ByteArrayOutputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
import java.util.Arrays;
/**
@@ -50,22 +59,26 @@
private final CdmaSMSDispatcher mSmsDispatcher;
private final CdmaServiceCategoryProgramHandler mServiceCategoryProgramHandler;
private static CdmaCbTestBroadcastReceiver sTestBroadcastReceiver;
+ private static CdmaScpTestBroadcastReceiver sTestScpBroadcastReceiver;
private byte[] mLastDispatchedSmsFingerprint;
private byte[] mLastAcknowledgedSmsFingerprint;
+ // Callback used to process the result of an SCP message
+ private RemoteCallback mScpCallback;
+
private final boolean mCheckForDuplicatePortsInOmadmWapPush = Resources.getSystem().getBoolean(
com.android.internal.R.bool.config_duplicate_port_omadm_wappush);
// When TEST_MODE is on we allow the test intent to trigger an SMS CB alert
- private static boolean sEnableCbModule = false;
- private static final boolean TEST_MODE = true; //STOPSHIP if true
+ private static final boolean TEST_MODE = SystemProperties.getInt("ro.debuggable", 0) == 1;
private static final String TEST_ACTION = "com.android.internal.telephony.cdma"
+ ".TEST_TRIGGER_CELL_BROADCAST";
+ private static final String SCP_TEST_ACTION = "com.android.internal.telephony.cdma"
+ + ".TEST_TRIGGER_SCP_MESSAGE";
private static final String TOGGLE_CB_MODULE = "com.android.internal.telephony.cdma"
+ ".TOGGLE_CB_MODULE";
-
/**
* Create a new inbound SMS handler for CDMA.
*/
@@ -75,10 +88,63 @@
CellBroadcastHandler.makeCellBroadcastHandler(context, phone));
mSmsDispatcher = smsDispatcher;
mServiceCategoryProgramHandler = CdmaServiceCategoryProgramHandler.makeScpHandler(context,
- phone.mCi);
+ phone.mCi, phone);
phone.mCi.setOnNewCdmaSms(getHandler(), EVENT_NEW_SMS, null);
mCellBroadcastServiceManager.enable();
+ mScpCallback = new RemoteCallback(result -> {
+ if (result == null) {
+ loge("SCP results error: missing extras");
+ return;
+ }
+ String sender = result.getString("sender");
+ if (sender == null) {
+ loge("SCP results error: missing sender extra.");
+ return;
+ }
+ ArrayList<CdmaSmsCbProgramResults> results = result.getParcelableArrayList("results");
+ if (results == null) {
+ loge("SCP results error: missing results extra.");
+ return;
+ }
+
+ BearerData bData = new BearerData();
+ bData.messageType = BearerData.MESSAGE_TYPE_SUBMIT;
+ bData.messageId = SmsMessage.getNextMessageId();
+ bData.serviceCategoryProgramResults = results;
+ byte[] encodedBearerData = BearerData.encode(bData);
+
+ ByteArrayOutputStream baos = new ByteArrayOutputStream(100);
+ DataOutputStream dos = new DataOutputStream(baos);
+ try {
+ dos.writeInt(SmsEnvelope.TELESERVICE_SCPT);
+ dos.writeInt(0); //servicePresent
+ dos.writeInt(0); //serviceCategory
+ CdmaSmsAddress destAddr = CdmaSmsAddress.parse(
+ PhoneNumberUtils.cdmaCheckAndProcessPlusCodeForSms(sender));
+ dos.write(destAddr.digitMode);
+ dos.write(destAddr.numberMode);
+ dos.write(destAddr.ton); // number_type
+ dos.write(destAddr.numberPlan);
+ dos.write(destAddr.numberOfDigits);
+ dos.write(destAddr.origBytes, 0, destAddr.origBytes.length); // digits
+ // Subaddress is not supported.
+ dos.write(0); //subaddressType
+ dos.write(0); //subaddr_odd
+ dos.write(0); //subaddr_nbr_of_digits
+ dos.write(encodedBearerData.length);
+ dos.write(encodedBearerData, 0, encodedBearerData.length);
+ // Ignore the RIL response. TODO: implement retry if SMS send fails.
+ mPhone.mCi.sendCdmaSms(baos.toByteArray(), null);
+ } catch (IOException e) {
+ loge("exception creating SCP results PDU", e);
+ } finally {
+ try {
+ dos.close();
+ } catch (IOException ignored) {
+ }
+ }
+ });
if (TEST_MODE) {
if (sTestBroadcastReceiver == null) {
sTestBroadcastReceiver = new CdmaCbTestBroadcastReceiver();
@@ -87,6 +153,12 @@
filter.addAction(TOGGLE_CB_MODULE);
context.registerReceiver(sTestBroadcastReceiver, filter);
}
+ if (sTestScpBroadcastReceiver == null) {
+ sTestScpBroadcastReceiver = new CdmaScpTestBroadcastReceiver();
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(SCP_TEST_ACTION);
+ context.registerReceiver(sTestScpBroadcastReceiver, filter);
+ }
}
}
@@ -115,6 +187,7 @@
/**
* Return true if this handler is for 3GPP2 messages; false for 3GPP format.
+ *
* @return true (3GPP2)
*/
@Override
@@ -124,6 +197,7 @@
/**
* Process Cell Broadcast, Voicemail Notification, and other 3GPP/3GPP2-specific messages.
+ *
* @param smsb the SmsMessageBase object from the RIL
* @return true if the message was handled here; false to continue processing
*/
@@ -170,7 +244,11 @@
break;
case SmsEnvelope.TELESERVICE_SCPT:
- mServiceCategoryProgramHandler.dispatchSmsMessage(sms);
+ if (sEnableCbModule) {
+ mCellBroadcastServiceManager.sendCdmaScpMessageToHandler(sms, mScpCallback);
+ } else {
+ mServiceCategoryProgramHandler.dispatchSmsMessage(sms);
+ }
return Intents.RESULT_SMS_HANDLED;
case SmsEnvelope.TELESERVICE_FDEA_WAP:
@@ -220,8 +298,9 @@
/**
* Send an acknowledge message.
- * @param success indicates that last message was successfully received.
- * @param result result code indicating any error
+ *
+ * @param success indicates that last message was successfully received.
+ * @param result result code indicating any error
* @param response callback message sent when operation completes.
*/
@Override
@@ -237,27 +316,29 @@
/**
* Convert Android result code to CDMA SMS failure cause.
+ *
* @param rc the Android SMS intent result value
* @return 0 for success, or a CDMA SMS failure cause value
*/
private static int resultToCause(int rc) {
switch (rc) {
- case Activity.RESULT_OK:
- case Intents.RESULT_SMS_HANDLED:
- // Cause code is ignored on success.
- return 0;
- case Intents.RESULT_SMS_OUT_OF_MEMORY:
- return CommandsInterface.CDMA_SMS_FAIL_CAUSE_RESOURCE_SHORTAGE;
- case Intents.RESULT_SMS_UNSUPPORTED:
- return CommandsInterface.CDMA_SMS_FAIL_CAUSE_INVALID_TELESERVICE_ID;
- case Intents.RESULT_SMS_GENERIC_ERROR:
- default:
- return CommandsInterface.CDMA_SMS_FAIL_CAUSE_OTHER_TERMINAL_PROBLEM;
+ case Activity.RESULT_OK:
+ case Intents.RESULT_SMS_HANDLED:
+ // Cause code is ignored on success.
+ return 0;
+ case Intents.RESULT_SMS_OUT_OF_MEMORY:
+ return CommandsInterface.CDMA_SMS_FAIL_CAUSE_RESOURCE_SHORTAGE;
+ case Intents.RESULT_SMS_UNSUPPORTED:
+ return CommandsInterface.CDMA_SMS_FAIL_CAUSE_INVALID_TELESERVICE_ID;
+ case Intents.RESULT_SMS_GENERIC_ERROR:
+ default:
+ return CommandsInterface.CDMA_SMS_FAIL_CAUSE_OTHER_TERMINAL_PROBLEM;
}
}
/**
* Handle {@link SmsEnvelope#TELESERVICE_VMN} and {@link SmsEnvelope#TELESERVICE_MWI}.
+ *
* @param sms the message to process
*/
private void handleVoicemailTeleservice(SmsMessage sms) {
@@ -285,8 +366,8 @@
*
* @param pdu The WAP-WDP PDU segment
* @return a result code from {@link android.provider.Telephony.Sms.Intents}, or
- * {@link Activity#RESULT_OK} if the message has been broadcast
- * to applications
+ * {@link Activity#RESULT_OK} if the message has been broadcast
+ * to applications
*/
private int processCdmaWapPdu(byte[] pdu, int referenceNumber, String address, String dispAddr,
long timestamp) {
@@ -333,8 +414,10 @@
System.arraycopy(pdu, index, userData, 0, pdu.length - index);
InboundSmsTracker tracker = TelephonyComponentFactory.getInstance()
.inject(InboundSmsTracker.class.getName()).makeInboundSmsTracker(
- userData, timestamp, destinationPort, true, address, dispAddr, referenceNumber,
- segment, totalSegments, true, HexDump.toHexString(userData), false /* isClass0 */,
+ userData, timestamp, destinationPort, true, address, dispAddr,
+ referenceNumber,
+ segment, totalSegments, true, HexDump.toHexString(userData),
+ false /* isClass0 */,
mPhone.getSubId());
// de-duping is done only for text messages
@@ -346,11 +429,12 @@
* extra port fields.
* - Some carriers make this mistake.
* ex: MSGTYPE-TotalSegments-CurrentSegment
- * -SourcePortDestPort-SourcePortDestPort-OMADM PDU
+ * -SourcePortDestPort-SourcePortDestPort-OMADM PDU
+ *
* @param origPdu The WAP-WDP PDU segment
- * @param index Current Index while parsing the PDU.
+ * @param index Current Index while parsing the PDU.
* @return True if OrigPdu is OmaDM Push Message which has duplicate ports.
- * False if OrigPdu is NOT OmaDM Push Message which has duplicate ports.
+ * False if OrigPdu is NOT OmaDM Push Message which has duplicate ports.
*/
private static boolean checkDuplicatePortOmadmWapPush(byte[] origPdu, int index) {
index += 4;
@@ -418,7 +502,7 @@
// the CdmaSmsAddress is not used for a test cell broadcast message, but needs to be
// supplied to avoid a null pointer exception in the platform
CdmaSmsAddress nonNullAddress = new CdmaSmsAddress();
- nonNullAddress.origBytes = new byte[] { (byte) 0xFF };
+ nonNullAddress.origBytes = new byte[]{(byte) 0xFF};
envelope.origAddress = nonNullAddress;
// parse service category from intent
@@ -447,11 +531,74 @@
@Override
protected void handleToggleEnable() {
// sEnableCbModule is already toggled in super class
+ mCellBroadcastServiceManager.enable();
}
@Override
protected void handleToggleDisable(Context context) {
// sEnableCbModule is already toggled in super class
+ mCellBroadcastServiceManager.disable();
+ }
+ }
+
+ /**
+ * A broadcast receiver used for testing CDMA SCP messages. To trigger test CDMA SCP messages
+ * with adb run e.g:
+ *
+ * adb shell am broadcast -a com.android.internal.telephony.cdma.TEST_TRIGGER_SCP_MESSAGE \
+ * --es originating_address_string 1234567890 \
+ * --es bearer_data_string 00031007B0122610880080B2091C5F1D3965DB95054D1CB2E1E883A6F41334E \
+ * 6CA830EEC882872DFC32F2E9E40
+ *
+ * To toggle use the CDMA CB test broadcast receiver.
+ */
+ private class CdmaScpTestBroadcastReceiver extends CbTestBroadcastReceiver {
+
+ CdmaScpTestBroadcastReceiver() {
+ super(SCP_TEST_ACTION, null);
+ }
+
+ @Override
+ protected void handleTestAction(Intent intent) {
+ SmsEnvelope envelope = new SmsEnvelope();
+ // the CdmaSmsAddress is not used for a test SCP message, but needs to be supplied to
+ // avoid a null pointer exception in the platform
+ CdmaSmsAddress nonNullAddress = new CdmaSmsAddress();
+ nonNullAddress.origBytes = new byte[]{(byte) 0xFF};
+ envelope.origAddress = nonNullAddress;
+
+ // parse bearer data from intent
+ String bearerDataString = intent.getStringExtra("bearer_data_string");
+ envelope.bearerData = decodeHexString(bearerDataString);
+ if (envelope.bearerData == null) {
+ log("No bearer data, ignoring SCP test intent");
+ return;
+ }
+
+ CdmaSmsAddress origAddr = new CdmaSmsAddress();
+ String addressString = intent.getStringExtra("originating_address_string");
+ origAddr.origBytes = decodeHexString(addressString);
+ if (origAddr.origBytes == null) {
+ log("No address data, ignoring SCP test intent");
+ return;
+ }
+ SmsMessage sms = new SmsMessage(origAddr, envelope);
+ sms.parseSms();
+ if (sEnableCbModule) {
+ mCellBroadcastServiceManager.sendCdmaScpMessageToHandler(sms, mScpCallback);
+ } else {
+ mServiceCategoryProgramHandler.dispatchSmsMessage(sms);
+ }
+ }
+
+ @Override
+ protected void handleToggleEnable() {
+ // noop
+ }
+
+ @Override
+ protected void handleToggleDisable(Context context) {
+ // noop
}
}
}
diff --git a/src/java/com/android/internal/telephony/cdma/CdmaServiceCategoryProgramHandler.java b/src/java/com/android/internal/telephony/cdma/CdmaServiceCategoryProgramHandler.java
index 5412911..3dc728c 100644
--- a/src/java/com/android/internal/telephony/cdma/CdmaServiceCategoryProgramHandler.java
+++ b/src/java/com/android/internal/telephony/cdma/CdmaServiceCategoryProgramHandler.java
@@ -31,6 +31,7 @@
import android.telephony.cdma.CdmaSmsCbProgramResults;
import com.android.internal.telephony.CommandsInterface;
+import com.android.internal.telephony.Phone;
import com.android.internal.telephony.WakeLockStateMachine;
import com.android.internal.telephony.cdma.sms.BearerData;
import com.android.internal.telephony.cdma.sms.CdmaSmsAddress;
@@ -51,8 +52,9 @@
/**
* Create a new CDMA inbound SMS handler.
*/
- CdmaServiceCategoryProgramHandler(Context context, CommandsInterface commandsInterface) {
- super("CdmaServiceCategoryProgramHandler", context, null);
+ CdmaServiceCategoryProgramHandler(Context context, CommandsInterface commandsInterface,
+ Phone phone) {
+ super("CdmaServiceCategoryProgramHandler", context, phone);
mContext = context;
mCi = commandsInterface;
}
@@ -64,9 +66,9 @@
* @return the new SCPD handler
*/
static CdmaServiceCategoryProgramHandler makeScpHandler(Context context,
- CommandsInterface commandsInterface) {
+ CommandsInterface commandsInterface, Phone phone) {
CdmaServiceCategoryProgramHandler handler = new CdmaServiceCategoryProgramHandler(
- context, commandsInterface);
+ context, commandsInterface, phone);
handler.start();
return handler;
}
diff --git a/src/java/com/android/internal/telephony/dataconnection/DcTracker.java b/src/java/com/android/internal/telephony/dataconnection/DcTracker.java
index 90d0097..d2354e1 100644
--- a/src/java/com/android/internal/telephony/dataconnection/DcTracker.java
+++ b/src/java/com/android/internal/telephony/dataconnection/DcTracker.java
@@ -964,7 +964,9 @@
private void enableMobileProvisioning() {
final Message msg = obtainMessage(DctConstants.CMD_ENABLE_MOBILE_PROVISIONING);
- msg.setData(Bundle.forPair(DctConstants.PROVISIONING_URL_KEY, mProvisionUrl));
+ Bundle bundle = new Bundle(1);
+ bundle.putString(DctConstants.PROVISIONING_URL_KEY, mProvisionUrl);
+ msg.setData(bundle);
sendMessage(msg);
}
@@ -2176,13 +2178,6 @@
* desired power state has changed in the interim, we don't want to
* override it with an unconditional power on.
*/
-
- int reset = Integer.parseInt(SystemProperties.get("net.ppp.reset-by-timeout", "0"));
- try {
- SystemProperties.set("net.ppp.reset-by-timeout", String.valueOf(reset + 1));
- } catch (RuntimeException ex) {
- log("Failed to set net.ppp.reset-by-timeout");
- }
}
/**
@@ -4136,6 +4131,9 @@
mEmergencyApn = new ApnSetting.Builder()
.setEntryName("Emergency")
.setProtocol(ApnSetting.PROTOCOL_IPV4V6)
+ .setRoamingProtocol(ApnSetting.PROTOCOL_IPV4V6)
+ .setNetworkTypeBitmask((int)(TelephonyManager.NETWORK_TYPE_BITMASK_LTE
+ | TelephonyManager.NETWORK_TYPE_BITMASK_IWLAN))
.setApnName("sos")
.setApnTypeBitmask(ApnSetting.TYPE_EMERGENCY)
.build();
@@ -4797,8 +4795,8 @@
.setApn(apn.getApnName())
.setProtocolType(apn.getProtocol())
.setAuthType(apn.getAuthType())
- .setUserName(apn.getUser())
- .setPassword(apn.getPassword())
+ .setUserName(apn.getUser() == null ? "" : apn.getUser())
+ .setPassword(apn.getPassword() == null ? "" : apn.getPassword())
.setType(profileType)
.setMaxConnectionsTime(apn.getMaxConnsTime())
.setMaxConnections(apn.getMaxConns())
diff --git a/src/java/com/android/internal/telephony/euicc/EuiccConnector.java b/src/java/com/android/internal/telephony/euicc/EuiccConnector.java
index 647ef57..874bd51 100644
--- a/src/java/com/android/internal/telephony/euicc/EuiccConnector.java
+++ b/src/java/com/android/internal/telephony/euicc/EuiccConnector.java
@@ -69,6 +69,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.content.PackageMonitor;
+import com.android.internal.telephony.util.TelephonyUtils;
import com.android.internal.util.IState;
import com.android.internal.util.State;
import com.android.internal.util.StateMachine;
@@ -634,9 +635,9 @@
isSameComponent = mSelectedComponent != null;
} else {
isSameComponent = mSelectedComponent == null
- || Objects.equals(
- bestComponent.getComponentName(),
- mSelectedComponent.getComponentName());
+ || Objects.equals(new ComponentName(bestComponent.packageName,
+ bestComponent.name),
+ new ComponentName(mSelectedComponent.packageName, mSelectedComponent.name));
}
boolean forceRebind = bestComponent != null
&& Objects.equals(bestComponent.packageName, affectedPackage);
@@ -1041,7 +1042,8 @@
return false;
}
Intent intent = new Intent(EuiccService.EUICC_SERVICE_INTERFACE);
- intent.setComponent(mSelectedComponent.getComponentName());
+ intent.setComponent(new ComponentName(mSelectedComponent.packageName,
+ mSelectedComponent.name));
// We bind this as a foreground service because it is operating directly on the SIM, and we
// do not want it subjected to power-savings restrictions while doing so.
return mContext.bindService(intent, this,
@@ -1065,7 +1067,7 @@
if (resolveInfo.filter.getPriority() > bestPriority) {
bestPriority = resolveInfo.filter.getPriority();
- bestComponent = resolveInfo.getComponentInfo();
+ bestComponent = TelephonyUtils.getComponentInfo(resolveInfo);
}
}
}
@@ -1075,8 +1077,9 @@
private static boolean isValidEuiccComponent(
PackageManager packageManager, ResolveInfo resolveInfo) {
- ComponentInfo componentInfo = resolveInfo.getComponentInfo();
- String packageName = componentInfo.getComponentName().getPackageName();
+ ComponentInfo componentInfo = TelephonyUtils.getComponentInfo(resolveInfo);
+ String packageName = new ComponentName(componentInfo.packageName, componentInfo.name)
+ .getPackageName();
// Verify that the app is privileged (via granting of a privileged permission).
if (packageManager.checkPermission(
diff --git a/src/java/com/android/internal/telephony/gsm/GsmCellBroadcastHandler.java b/src/java/com/android/internal/telephony/gsm/GsmCellBroadcastHandler.java
index b9d4171..052d89c 100644
--- a/src/java/com/android/internal/telephony/gsm/GsmCellBroadcastHandler.java
+++ b/src/java/com/android/internal/telephony/gsm/GsmCellBroadcastHandler.java
@@ -39,6 +39,8 @@
import com.android.internal.telephony.gsm.GsmSmsCbMessage.GeoFencingTriggerMessage;
import com.android.internal.telephony.gsm.GsmSmsCbMessage.GeoFencingTriggerMessage.CellBroadcastIdentity;
+import dalvik.annotation.compat.UnsupportedAppUsage;
+
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
@@ -54,6 +56,7 @@
private static final String MESSAGE_NOT_BROADCASTED = "0";
/** This map holds incomplete concatenated messages waiting for assembly. */
+ @UnsupportedAppUsage
private final HashMap<SmsCbConcatInfo, byte[][]> mSmsCbPageMap =
new HashMap<SmsCbConcatInfo, byte[][]>(4);
@@ -334,6 +337,7 @@
private final SmsCbHeader mHeader;
private final SmsCbLocation mLocation;
+ @UnsupportedAppUsage
SmsCbConcatInfo(SmsCbHeader header, SmsCbLocation location) {
mHeader = header;
mLocation = location;
@@ -369,6 +373,7 @@
* @param cid the current Cell ID
* @return true if this message is valid for the current location; false otherwise
*/
+ @UnsupportedAppUsage
public boolean matchesLocation(String plmn, int lac, int cid) {
return mLocation.isInLocationArea(plmn, lac, cid);
}
diff --git a/src/java/com/android/internal/telephony/gsm/GsmInboundSmsHandler.java b/src/java/com/android/internal/telephony/gsm/GsmInboundSmsHandler.java
index b5413f6..7b5dd0c 100644
--- a/src/java/com/android/internal/telephony/gsm/GsmInboundSmsHandler.java
+++ b/src/java/com/android/internal/telephony/gsm/GsmInboundSmsHandler.java
@@ -23,6 +23,7 @@
import android.content.IntentFilter;
import android.os.AsyncResult;
import android.os.Message;
+import android.os.SystemProperties;
import android.provider.Telephony.Sms.Intents;
import com.android.internal.telephony.CommandsInterface;
@@ -35,6 +36,8 @@
import com.android.internal.telephony.VisualVoicemailSmsFilter;
import com.android.internal.telephony.uicc.UsimServiceTable;
+import dalvik.annotation.compat.UnsupportedAppUsage;
+
/**
* This class broadcasts incoming SMS messages to interested apps after storing them in
* the SmsProvider "raw" table and ACKing them to the SMSC. After each message has been
@@ -46,7 +49,7 @@
private final UsimDataDownloadHandler mDataDownloadHandler;
// When TEST_MODE is on we allow the test intent to trigger an SMS CB alert
- private static final boolean TEST_MODE = true; //STOPSHIP if true
+ private static final boolean TEST_MODE = SystemProperties.getInt("ro.debuggable", 0) == 1;
private static final String TEST_ACTION = "com.android.internal.telephony.gsm"
+ ".TEST_TRIGGER_CELL_BROADCAST";
private static final String TOGGLE_CB_MODULE = "com.android.internal.telephony.gsm"
@@ -264,6 +267,7 @@
* @param result result code indicating any error
* @param response callback message sent when operation completes.
*/
+ @UnsupportedAppUsage
@Override
protected void acknowledgeLastIncomingSms(boolean success, int result, Message response) {
mPhone.mCi.acknowledgeLastIncomingGsmSms(success, resultToCause(result), response);
diff --git a/src/java/com/android/internal/telephony/gsm/GsmMmiCode.java b/src/java/com/android/internal/telephony/gsm/GsmMmiCode.java
index 6e96e25a..01ba6ad 100644
--- a/src/java/com/android/internal/telephony/gsm/GsmMmiCode.java
+++ b/src/java/com/android/internal/telephony/gsm/GsmMmiCode.java
@@ -55,6 +55,8 @@
import com.android.internal.telephony.uicc.UiccCardApplication;
import com.android.internal.util.ArrayUtils;
+import dalvik.annotation.compat.UnsupportedAppUsage;
+
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -136,15 +138,25 @@
//***** Instance Variables
+ @UnsupportedAppUsage
GsmCdmaPhone mPhone;
+ @UnsupportedAppUsage
Context mContext;
UiccCardApplication mUiccApplication;
+ @UnsupportedAppUsage
IccRecords mIccRecords;
String mAction; // One of ACTION_*
+ @UnsupportedAppUsage
String mSc; // Service Code
- String mSia, mSib, mSic; // Service Info a,b,c
+ @UnsupportedAppUsage
+ String mSia; // Service Info a
+ @UnsupportedAppUsage
+ String mSib; // Service Info b
+ @UnsupportedAppUsage
+ String mSic; // Service Info c
String mPoundString; // Entire MMI string up to and including #
+ @UnsupportedAppUsage
public String mDialingNumber;
String mPwd; // For password registration
@@ -165,6 +177,7 @@
// See TS 22.030 6.5.2 "Structure of the MMI"
+ @UnsupportedAppUsage
static Pattern sPatternSuppService = Pattern.compile(
"((\\*|#|\\*#|\\*\\*|##)(\\d{2,3})(\\*([^*#]*)(\\*([^*#]*)(\\*([^*#]*)(\\*([^*#]*))?)?)?)?#)(.*)");
/* 1 2 3 4 5 6 7 8 9 10 11 12
@@ -205,6 +218,7 @@
*
* Please see flow chart in TS 22.030 6.5.3.2
*/
+ @UnsupportedAppUsage
public static GsmMmiCode newFromDialString(String dialString, GsmCdmaPhone phone,
UiccCardApplication app) {
return newFromDialString(dialString, phone, app, null);
@@ -461,6 +475,7 @@
/** make empty strings be null.
* Regexp returns empty strings for empty groups
*/
+ @UnsupportedAppUsage
private static String
makeEmptyNull (String s) {
if (s != null && s.length() == 0) return null;
@@ -498,6 +513,7 @@
}
}
+ @UnsupportedAppUsage
private static int
siToServiceClass(String si) {
if (si == null || si.length() == 0) {
@@ -547,6 +563,7 @@
}
}
+ @UnsupportedAppUsage
static boolean
isServiceCodeCallForwarding(String sc) {
return sc != null &&
@@ -556,6 +573,7 @@
|| sc.equals(SC_CF_All_Conditional));
}
+ @UnsupportedAppUsage
static boolean
isServiceCodeCallBarring(String sc) {
Resources resource = Resources.getSystem();
@@ -600,6 +618,7 @@
//***** Constructor
+ @UnsupportedAppUsage
public GsmMmiCode(GsmCdmaPhone phone, UiccCardApplication app) {
// The telephony unit-test cases may create GsmMmiCode's
// in secondary threads
@@ -782,6 +801,7 @@
* In temporary mode, to invoke CLIR for a single call enter:
* " # 31 # [called number] SEND "
*/
+ @UnsupportedAppUsage
public boolean
isTemporaryModeCLIR() {
return mSc != null && mSc.equals(SC_CLIR) && mDialingNumber != null
@@ -792,6 +812,7 @@
* returns CommandsInterface.CLIR_*
* See also isTemporaryModeCLIR()
*/
+ @UnsupportedAppUsage
public int
getCLIRMode() {
if (mSc != null && mSc.equals(SC_CLIR)) {
@@ -829,22 +850,27 @@
return false;
}
+ @UnsupportedAppUsage
boolean isActivate() {
return mAction != null && mAction.equals(ACTION_ACTIVATE);
}
+ @UnsupportedAppUsage
boolean isDeactivate() {
return mAction != null && mAction.equals(ACTION_DEACTIVATE);
}
+ @UnsupportedAppUsage
boolean isInterrogate() {
return mAction != null && mAction.equals(ACTION_INTERROGATE);
}
+ @UnsupportedAppUsage
boolean isRegister() {
return mAction != null && mAction.equals(ACTION_REGISTER);
}
+ @UnsupportedAppUsage
boolean isErasure() {
return mAction != null && mAction.equals(ACTION_ERASURE);
}
@@ -874,6 +900,7 @@
}
/** Process a MMI code or short code...anything that isn't a dialing number */
+ @UnsupportedAppUsage
public void
processCode() throws CallStateException {
try {
@@ -1264,6 +1291,7 @@
return mContext.getText(com.android.internal.R.string.mmiError);
}
+ @UnsupportedAppUsage
private CharSequence getScString() {
if (mSc != null) {
if (isServiceCodeCallBarring(mSc)) {
diff --git a/src/java/com/android/internal/telephony/gsm/GsmSMSDispatcher.java b/src/java/com/android/internal/telephony/gsm/GsmSMSDispatcher.java
index e9c5403..0e5d83f 100644
--- a/src/java/com/android/internal/telephony/gsm/GsmSMSDispatcher.java
+++ b/src/java/com/android/internal/telephony/gsm/GsmSMSDispatcher.java
@@ -28,9 +28,9 @@
import com.android.internal.telephony.GsmAlphabet.TextEncodingDetails;
import com.android.internal.telephony.InboundSmsHandler;
import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.SMSDispatcher;
import com.android.internal.telephony.SmsConstants;
import com.android.internal.telephony.SmsDispatchersController;
-import com.android.internal.telephony.SMSDispatcher;
import com.android.internal.telephony.SmsHeader;
import com.android.internal.telephony.SmsMessageBase;
import com.android.internal.telephony.uicc.IccRecords;
@@ -39,6 +39,8 @@
import com.android.internal.telephony.uicc.UiccController;
import com.android.internal.telephony.util.SMSDispatcherUtil;
+import dalvik.annotation.compat.UnsupportedAppUsage;
+
import java.util.HashMap;
import java.util.concurrent.atomic.AtomicReference;
@@ -48,6 +50,7 @@
private AtomicReference<IccRecords> mIccRecords = new AtomicReference<IccRecords>();
private AtomicReference<UiccCardApplication> mUiccApplication =
new AtomicReference<UiccCardApplication>();
+ @UnsupportedAppUsage
private GsmInboundSmsHandler mGsmInboundSmsHandler;
/** Status report received */
@@ -70,6 +73,7 @@
mUiccController.unregisterForIccChanged(this);
}
+ @UnsupportedAppUsage
@Override
protected String getFormat() {
return SmsConstants.FORMAT_3GPP;
@@ -166,6 +170,7 @@
}
/** {@inheritDoc} */
+ @UnsupportedAppUsage
@Override
protected void sendSms(SmsTracker tracker) {
int ss = mPhone.getServiceState().getState();
diff --git a/src/java/com/android/internal/telephony/gsm/SimTlv.java b/src/java/com/android/internal/telephony/gsm/SimTlv.java
index c98b9a1..7df1f96 100644
--- a/src/java/com/android/internal/telephony/gsm/SimTlv.java
+++ b/src/java/com/android/internal/telephony/gsm/SimTlv.java
@@ -16,6 +16,8 @@
package com.android.internal.telephony.gsm;
+import dalvik.annotation.compat.UnsupportedAppUsage;
+
/**
* SIM Tag-Length-Value record
* TS 102 223 Annex C
@@ -33,8 +35,10 @@
int mCurOffset;
int mCurDataOffset;
int mCurDataLength;
+ @UnsupportedAppUsage
boolean mHasValidTlvObject;
+ @UnsupportedAppUsage
public SimTlv(byte[] record, int offset, int length) {
mRecord = record;
@@ -45,6 +49,7 @@
mHasValidTlvObject = parseCurrentTlvObject();
}
+ @UnsupportedAppUsage
public boolean nextObject() {
if (!mHasValidTlvObject) return false;
mCurOffset = mCurDataOffset + mCurDataLength;
@@ -52,6 +57,7 @@
return mHasValidTlvObject;
}
+ @UnsupportedAppUsage
public boolean isValidObject() {
return mHasValidTlvObject;
}
@@ -62,6 +68,7 @@
* 0 and 0xff are invalid tag values
* valid tags range from 1 - 0xfe
*/
+ @UnsupportedAppUsage
public int getTag() {
if (!mHasValidTlvObject) return 0;
return mRecord[mCurOffset] & 0xff;
@@ -72,6 +79,7 @@
* returns null if !isValidObject()
*/
+ @UnsupportedAppUsage
public byte[] getData() {
if (!mHasValidTlvObject) return null;
diff --git a/src/java/com/android/internal/telephony/gsm/UsimPhoneBookManager.java b/src/java/com/android/internal/telephony/gsm/UsimPhoneBookManager.java
index 6489014..a541459 100755
--- a/src/java/com/android/internal/telephony/gsm/UsimPhoneBookManager.java
+++ b/src/java/com/android/internal/telephony/gsm/UsimPhoneBookManager.java
@@ -28,6 +28,9 @@
import com.android.internal.telephony.uicc.IccConstants;
import com.android.internal.telephony.uicc.IccFileHandler;
import com.android.internal.telephony.uicc.IccUtils;
+
+import dalvik.annotation.compat.UnsupportedAppUsage;
+
import java.util.ArrayList;
/**
@@ -41,9 +44,12 @@
private static final boolean DBG = true;
private ArrayList<PbrRecord> mPbrRecords;
private Boolean mIsPbrPresent;
+ @UnsupportedAppUsage
private IccFileHandler mFh;
private AdnRecordCache mAdnCache;
+ @UnsupportedAppUsage
private Object mLock = new Object();
+ @UnsupportedAppUsage
private ArrayList<AdnRecord> mPhoneBookRecords;
private ArrayList<byte[]> mIapFileRecord;
private ArrayList<byte[]> mEmailFileRecord;
@@ -119,6 +125,7 @@
mSfiEfidTable = new SparseIntArray();
}
+ @UnsupportedAppUsage
public void reset() {
mPhoneBookRecords.clear();
mIapFileRecord = null;
@@ -131,6 +138,7 @@
}
// Load all phonebook related EFs from the SIM.
+ @UnsupportedAppUsage
public ArrayList<AdnRecord> loadEfFilesFromUsim() {
synchronized (mLock) {
if (!mPhoneBookRecords.isEmpty()) {
@@ -660,6 +668,7 @@
}
}
+ @UnsupportedAppUsage
private void log(String msg) {
if(DBG) Rlog.d(LOG_TAG, msg);
}
diff --git a/src/java/com/android/internal/telephony/imsphone/ImsPhone.java b/src/java/com/android/internal/telephony/imsphone/ImsPhone.java
index e0e1a23..7a863d0 100644
--- a/src/java/com/android/internal/telephony/imsphone/ImsPhone.java
+++ b/src/java/com/android/internal/telephony/imsphone/ImsPhone.java
@@ -237,6 +237,18 @@
this.mCurrentSubscriberUris = currentSubscriberUris;
}
+ @UnsupportedAppUsage
+ @Override
+ public void notifyCallForwardingIndicator() {
+ super.notifyCallForwardingIndicator();
+ }
+
+ @UnsupportedAppUsage
+ @Override
+ public void notifyPreciseCallStateChanged() {
+ super.notifyPreciseCallStateChanged();
+ }
+
@Override
public Uri[] getCurrentSubscriberUris() {
return mCurrentSubscriberUris;
diff --git a/src/java/com/android/internal/telephony/imsphone/ImsPhoneCall.java b/src/java/com/android/internal/telephony/imsphone/ImsPhoneCall.java
index eb01a34..f2fb779 100644
--- a/src/java/com/android/internal/telephony/imsphone/ImsPhoneCall.java
+++ b/src/java/com/android/internal/telephony/imsphone/ImsPhoneCall.java
@@ -18,18 +18,18 @@
import android.annotation.UnsupportedAppUsage;
import android.telecom.ConferenceParticipant;
-import android.telephony.Rlog;
import android.telephony.DisconnectCause;
+import android.telephony.Rlog;
+import android.telephony.ims.ImsStreamMediaProfile;
import android.util.Log;
+import com.android.ims.ImsCall;
+import com.android.ims.ImsException;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telephony.Call;
import com.android.internal.telephony.CallStateException;
import com.android.internal.telephony.Connection;
import com.android.internal.telephony.Phone;
-import com.android.ims.ImsCall;
-import com.android.ims.ImsException;
-import android.telephony.ims.ImsStreamMediaProfile;
import java.util.List;
@@ -300,6 +300,7 @@
* @return The {@link ImsCall}.
*/
@VisibleForTesting
+ @UnsupportedAppUsage
public ImsCall
getImsCall() {
return (getFirstConnection() == null) ? null : getFirstConnection().getImsCall();
diff --git a/src/java/com/android/internal/telephony/imsphone/ImsPhoneCallTracker.java b/src/java/com/android/internal/telephony/imsphone/ImsPhoneCallTracker.java
index 9c5202c..ec3f324 100644
--- a/src/java/com/android/internal/telephony/imsphone/ImsPhoneCallTracker.java
+++ b/src/java/com/android/internal/telephony/imsphone/ImsPhoneCallTracker.java
@@ -399,7 +399,7 @@
private int mClirMode = CommandsInterface.CLIR_DEFAULT;
@UnsupportedAppUsage
private Object mSyncHold = new Object();
-
+ @UnsupportedAppUsage
private ImsCall mUssdSession = null;
@UnsupportedAppUsage
private Message mPendingUssd = null;
@@ -415,6 +415,7 @@
private PhoneConstants.State mState = PhoneConstants.State.IDLE;
+ @UnsupportedAppUsage
private ImsManager mImsManager;
private ImsUtInterface mUtInterface;
@@ -429,6 +430,7 @@
private boolean pendingCallInEcm = false;
@UnsupportedAppUsage
private boolean mSwitchingFgAndBgCalls = false;
+ @UnsupportedAppUsage
private ImsCall mCallExpectedToResume = null;
@UnsupportedAppUsage
private boolean mAllowEmergencyVideoCalls = false;
@@ -1898,6 +1900,7 @@
mUssdSession.terminate(ImsReasonInfo.CODE_USER_TERMINATED);
}
+ @UnsupportedAppUsage
private synchronized ImsPhoneConnection findConnection(final ImsCall imsCall) {
for (ImsPhoneConnection conn : mConnections) {
if (conn.getImsCall() == imsCall) {
@@ -1937,6 +1940,7 @@
}
}
+ @UnsupportedAppUsage
private void processCallStateChange(ImsCall imsCall, ImsPhoneCall.State state, int cause) {
if (DBG) log("processCallStateChange " + imsCall + " state=" + state + " cause=" + cause);
// This method is called on onCallUpdate() where there is not necessarily a call state
@@ -1946,6 +1950,7 @@
processCallStateChange(imsCall, state, cause, false /* do not ignore state update */);
}
+ @UnsupportedAppUsage
private void processCallStateChange(ImsCall imsCall, ImsPhoneCall.State state, int cause,
boolean ignoreState) {
if (DBG) {
@@ -2260,6 +2265,7 @@
/**
* Listen to the IMS call state change
*/
+ @UnsupportedAppUsage
private ImsCall.Listener mImsCallListener = new ImsCall.Listener() {
@Override
public void onCallProgressing(ImsCall imsCall) {
@@ -3728,6 +3734,7 @@
}
/* package */
+ @UnsupportedAppUsage
ImsEcbm getEcbmInterface() throws ImsException {
if (mImsManager == null) {
throw getImsManagerIsNullException();
@@ -3814,6 +3821,7 @@
mImsManagerConnector.connect();
}
+ @UnsupportedAppUsage
private void setVideoCallProvider(ImsPhoneConnection conn, ImsCall imsCall)
throws RemoteException {
IImsVideoCallProvider imsVideoCallProvider =
diff --git a/src/java/com/android/internal/telephony/imsphone/ImsPhoneConnection.java b/src/java/com/android/internal/telephony/imsphone/ImsPhoneConnection.java
index e086f16..f98fc40 100644
--- a/src/java/com/android/internal/telephony/imsphone/ImsPhoneConnection.java
+++ b/src/java/com/android/internal/telephony/imsphone/ImsPhoneConnection.java
@@ -66,6 +66,7 @@
private ImsPhoneCallTracker mOwner;
@UnsupportedAppUsage
private ImsPhoneCall mParent;
+ @UnsupportedAppUsage
private ImsCall mImsCall;
private Bundle mExtras = new Bundle();
private TelephonyMetrics mMetrics = TelephonyMetrics.getInstance();
@@ -727,6 +728,7 @@
* @return {@code true} if the {@link ImsPhoneConnection} or its media capabilities have been
* changed, and {@code false} otherwise.
*/
+ @UnsupportedAppUsage
public boolean update(ImsCall imsCall, ImsPhoneCall.State state) {
if (state == ImsPhoneCall.State.ACTIVE) {
// If the state of the call is active, but there is a pending request to the RIL to hold
diff --git a/src/java/com/android/internal/telephony/metrics/TelephonyMetrics.java b/src/java/com/android/internal/telephony/metrics/TelephonyMetrics.java
index b8dbcf0..2674290 100644
--- a/src/java/com/android/internal/telephony/metrics/TelephonyMetrics.java
+++ b/src/java/com/android/internal/telephony/metrics/TelephonyMetrics.java
@@ -72,6 +72,7 @@
import com.android.internal.telephony.RIL;
import com.android.internal.telephony.RILConstants;
import com.android.internal.telephony.SmsResponse;
+import com.android.internal.telephony.util.TelephonyUtils;
import com.android.internal.telephony.UUSInfo;
import com.android.internal.telephony.imsphone.ImsPhoneCall;
import com.android.internal.telephony.nano.TelephonyProto;
@@ -2469,14 +2470,14 @@
}
// fill in complete matching information from the SIM.
- carrierIdMatchingResult.mccmnc = TextUtils.emptyIfNull(simInfo.mccMnc);
- carrierIdMatchingResult.spn = TextUtils.emptyIfNull(simInfo.spn);
- carrierIdMatchingResult.pnn = TextUtils.emptyIfNull(simInfo.plmn);
- carrierIdMatchingResult.gid1 = TextUtils.emptyIfNull(simInfo.gid1);
- carrierIdMatchingResult.gid2 = TextUtils.emptyIfNull(simInfo.gid2);
- carrierIdMatchingResult.imsiPrefix = TextUtils.emptyIfNull(simInfo.imsiPrefixPattern);
- carrierIdMatchingResult.iccidPrefix = TextUtils.emptyIfNull(simInfo.iccidPrefix);
- carrierIdMatchingResult.preferApn = TextUtils.emptyIfNull(simInfo.apn);
+ carrierIdMatchingResult.mccmnc = TelephonyUtils.emptyIfNull(simInfo.mccMnc);
+ carrierIdMatchingResult.spn = TelephonyUtils.emptyIfNull(simInfo.spn);
+ carrierIdMatchingResult.pnn = TelephonyUtils.emptyIfNull(simInfo.plmn);
+ carrierIdMatchingResult.gid1 = TelephonyUtils.emptyIfNull(simInfo.gid1);
+ carrierIdMatchingResult.gid2 = TelephonyUtils.emptyIfNull(simInfo.gid2);
+ carrierIdMatchingResult.imsiPrefix = TelephonyUtils.emptyIfNull(simInfo.imsiPrefixPattern);
+ carrierIdMatchingResult.iccidPrefix = TelephonyUtils.emptyIfNull(simInfo.iccidPrefix);
+ carrierIdMatchingResult.preferApn = TelephonyUtils.emptyIfNull(simInfo.apn);
if (simInfo.privilegeAccessRule != null) {
carrierIdMatchingResult.privilegeAccessRule =
simInfo.privilegeAccessRule.stream().toArray(String[]::new);
diff --git a/src/java/com/android/internal/telephony/nitz/NewNitzStateMachineImpl.java b/src/java/com/android/internal/telephony/nitz/NewNitzStateMachineImpl.java
new file mode 100644
index 0000000..edc3e67
--- /dev/null
+++ b/src/java/com/android/internal/telephony/nitz/NewNitzStateMachineImpl.java
@@ -0,0 +1,357 @@
+/*
+ * Copyright 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.internal.telephony.nitz;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.timedetector.PhoneTimeSuggestion;
+import android.content.Context;
+import android.telephony.Rlog;
+import android.util.TimestampedValue;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.NitzData;
+import com.android.internal.telephony.NitzStateMachine;
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.TimeZoneLookupHelper;
+import com.android.internal.telephony.nitz.service.PhoneTimeZoneSuggestion;
+import com.android.internal.util.IndentingPrintWriter;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.Objects;
+
+// TODO Update this comment when NitzStateMachineImpl is deleted - it will no longer be appropriate
+// to contrast the behavior of the two implementations.
+/**
+ * A new and more testable implementation of {@link NitzStateMachine}. It is intended to replace
+ * {@link com.android.internal.telephony.NitzStateMachineImpl}.
+ *
+ * <p>This implementation differs in a number of ways:
+ * <ul>
+ * <li>It is decomposed into multiple classes that perform specific, well-defined, usually
+ * stateless, testable behaviors.
+ * </li>
+ * <li>It splits responsibility for setting the device time zone with a "time zone detection
+ * service". The time zone detection service is stateful, recording the latest suggestion from
+ * possibly multiple sources. The {@link NewNitzStateMachineImpl} must now actively signal when
+ * it has no answer for the current time zone, allowing the service to arbitrate between
+ * multiple sources without polling each of them.
+ * </li>
+ * <li>Rate limiting of NITZ signals is performed for time zone as well as time detection.</li>
+ * </ul>
+ */
+public final class NewNitzStateMachineImpl implements NitzStateMachine {
+
+ /**
+ * An interface for predicates applied to incoming NITZ signals to determine whether they must
+ * be processed. See {@link NitzSignalInputFilterPredicateFactory#create(Context, DeviceState)}
+ * for the real implementation. The use of an interface means the behavior can be tested
+ * independently and easily replaced for tests.
+ */
+ @VisibleForTesting
+ @FunctionalInterface
+ public interface NitzSignalInputFilterPredicate {
+
+ /**
+ * See {@link NitzSignalInputFilterPredicate}.
+ */
+ boolean mustProcessNitzSignal(
+ @Nullable TimestampedValue<NitzData> oldSignal,
+ @NonNull TimestampedValue<NitzData> newSignal);
+ }
+
+ /**
+ * An interface for the stateless component that generates suggestions using country and/or NITZ
+ * information. The use of an interface means the behavior can be tested independently.
+ */
+ @VisibleForTesting
+ public interface TimeZoneSuggester {
+
+ /**
+ * Generates a {@link PhoneTimeZoneSuggestion} given the information available. This method
+ * must always return a non-null {@link PhoneTimeZoneSuggestion} but that object does not
+ * have to contain a time zone if the available information is not sufficient to determine
+ * one. {@link PhoneTimeZoneSuggestion#getDebugInfo()} provides debugging / logging
+ * information explaining the choice.
+ */
+ @NonNull
+ PhoneTimeZoneSuggestion getTimeZoneSuggestion(
+ int phoneId, @Nullable String countryIsoCode,
+ @Nullable TimestampedValue<NitzData> nitzSignal);
+ }
+
+ static final String LOG_TAG = "NewNitzStateMachineImpl";
+ static final boolean DBG = true;
+
+ // Miscellaneous dependencies and helpers not related to detection state.
+ private final int mPhoneId;
+ /** Accesses global information about the device. */
+ private final DeviceState mDeviceState;
+ /** Applied to NITZ signals during input filtering. */
+ private final NitzSignalInputFilterPredicate mNitzSignalInputFilter;
+ /** Creates {@link PhoneTimeZoneSuggestion} for passing to the time zone detection service. */
+ private final TimeZoneSuggester mTimeZoneSuggester;
+ /** A facade to the time / time zone detection services. */
+ private final NewTimeServiceHelper mNewTimeServiceHelper;
+
+ // Shared detection state.
+
+ /**
+ * The last / latest NITZ signal <em>processed</em> (i.e. after input filtering). It is used for
+ * input filtering (e.g. rate limiting) and provides the NITZ information when time / time zone
+ * needs to be recalculated when something else has changed.
+ */
+ @Nullable
+ private TimestampedValue<NitzData> mLatestNitzSignal;
+
+ // Time Zone detection state.
+
+ /**
+ * Records whether the device should have a country code available via
+ * {@link DeviceState#getNetworkCountryIsoForPhone()}. Before this an NITZ signal
+ * received is (almost always) not enough to determine time zone. On test networks the country
+ * code should be available but can still be an empty string but this flag indicates that the
+ * information available is unlikely to improve.
+ */
+ private boolean mGotCountryCode = false;
+
+ /**
+ * Creates an instance for the supplied {@link Phone}.
+ */
+ public static NewNitzStateMachineImpl createInstance(@NonNull Phone phone) {
+ Objects.requireNonNull(phone);
+
+ int phoneId = phone.getPhoneId();
+ DeviceState deviceState = new DeviceStateImpl(phone);
+ TimeZoneLookupHelper timeZoneLookupHelper = new TimeZoneLookupHelper();
+ TimeZoneSuggester timeZoneSuggester =
+ new TimeZoneSuggesterImpl(deviceState, timeZoneLookupHelper);
+ NewTimeServiceHelper newTimeServiceHelper = new NewTimeServiceHelperImpl(phone);
+ NitzSignalInputFilterPredicate nitzSignalFilter =
+ NitzSignalInputFilterPredicateFactory.create(phone.getContext(), deviceState);
+ return new NewNitzStateMachineImpl(
+ phoneId, nitzSignalFilter, timeZoneSuggester, newTimeServiceHelper, deviceState);
+ }
+
+ /**
+ * Creates an instance using the supplied components. Used during tests to supply fakes.
+ * See {@link #createInstance(Phone)}
+ */
+ @VisibleForTesting
+ public NewNitzStateMachineImpl(int phoneId,
+ @NonNull NitzSignalInputFilterPredicate nitzSignalInputFilter,
+ @NonNull TimeZoneSuggester timeZoneSuggester,
+ @NonNull NewTimeServiceHelper newTimeServiceHelper, @NonNull DeviceState deviceState) {
+ mPhoneId = phoneId;
+ mTimeZoneSuggester = Objects.requireNonNull(timeZoneSuggester);
+ mNewTimeServiceHelper = Objects.requireNonNull(newTimeServiceHelper);
+ mDeviceState = Objects.requireNonNull(deviceState);
+ mNitzSignalInputFilter = Objects.requireNonNull(nitzSignalInputFilter);
+ }
+
+ @Override
+ public void handleNetworkAvailable() {
+ // Assume any previous NITZ signals received are now invalid.
+ mLatestNitzSignal = null;
+
+ String countryIsoCode =
+ mGotCountryCode ? mDeviceState.getNetworkCountryIsoForPhone() : null;
+
+ if (DBG) {
+ Rlog.d(LOG_TAG, "handleNetworkAvailable: countryIsoCode=" + countryIsoCode
+ + ", mLatestNitzSignal=" + mLatestNitzSignal);
+ }
+
+ String reason = "handleNetworkAvailable()";
+
+ // Generate a new time zone suggestion and update the service as needed.
+ doTimeZoneDetection(countryIsoCode, null /* nitzSignal */, reason);
+
+ // Generate a new time suggestion and update the service as needed.
+ doTimeDetection(null /* nitzSignal */, reason);
+ }
+
+ @Override
+ public void handleNetworkCountryCodeSet(boolean countryChanged) {
+ if (DBG) {
+ Rlog.d(LOG_TAG, "handleNetworkCountryCodeSet: countryChanged=" + countryChanged
+ + ", mLatestNitzSignal=" + mLatestNitzSignal);
+ }
+
+ mGotCountryCode = true;
+
+ // Generate a new time zone suggestion and update the service as needed.
+ String countryIsoCode = mDeviceState.getNetworkCountryIsoForPhone();
+ doTimeZoneDetection(countryIsoCode, mLatestNitzSignal,
+ "handleNetworkCountryCodeSet(" + countryChanged + ")");
+ }
+
+ @Override
+ public void handleNetworkCountryCodeUnavailable() {
+ if (DBG) {
+ Rlog.d(LOG_TAG, "handleNetworkCountryCodeUnavailable:"
+ + " mLatestNitzSignal=" + mLatestNitzSignal);
+ }
+ mGotCountryCode = false;
+
+ // Generate a new time zone suggestion and update the service as needed.
+ doTimeZoneDetection(null /* countryIsoCode */, mLatestNitzSignal,
+ "handleNetworkCountryCodeUnavailable()");
+ }
+
+ @Override
+ public void handleNitzReceived(@NonNull TimestampedValue<NitzData> nitzSignal) {
+ if (DBG) {
+ Rlog.d(LOG_TAG, "handleNitzReceived: nitzSignal=" + nitzSignal);
+ }
+ Objects.requireNonNull(nitzSignal);
+
+ // Perform input filtering to filter bad data and avoid processing signals too often.
+ TimestampedValue<NitzData> previousNitzSignal = mLatestNitzSignal;
+ if (!mNitzSignalInputFilter.mustProcessNitzSignal(previousNitzSignal, nitzSignal)) {
+ return;
+ }
+
+ // Always store the latest valid NITZ signal to be processed.
+ mLatestNitzSignal = nitzSignal;
+
+ String reason = "handleNitzReceived(" + nitzSignal + ")";
+
+ // Generate a new time zone suggestion and update the service as needed.
+ String countryIsoCode =
+ mGotCountryCode ? mDeviceState.getNetworkCountryIsoForPhone() : null;
+ doTimeZoneDetection(countryIsoCode, nitzSignal, reason);
+
+ // Generate a new time suggestion and update the service as needed.
+ doTimeDetection(nitzSignal, reason);
+ }
+
+ @Override
+ public void handleAirplaneModeChanged(boolean on) {
+ if (DBG) {
+ Rlog.d(LOG_TAG, "handleAirplaneModeChanged: on=" + on);
+ }
+
+ // Treat entry / exit from airplane mode as a strong signal that the user wants to clear
+ // cached state. If the user really is boarding a plane they won't want cached state from
+ // before their flight influencing behavior.
+ //
+ // State is cleared on entry AND exit: on entry because the detection code shouldn't be
+ // opinionated while in airplane mode, and on exit to avoid any unexpected signals received
+ // while in airplane mode from influencing behavior afterwards.
+ //
+ // After clearing detection state, the time zone detection should work out from first
+ // principles what the time / time zone is. This assumes calls like handleNetworkAvailable()
+ // will be made after airplane mode is re-enabled as the device re-establishes network
+ // connectivity.
+
+ // Clear shared state.
+ mLatestNitzSignal = null;
+
+ // Clear time zone detection state.
+ mGotCountryCode = false;
+
+ String reason = "handleAirplaneModeChanged(" + on + ")";
+
+ // Generate a new time zone suggestion and update the service as needed.
+ doTimeZoneDetection(null /* countryIsoCode */, null /* nitzSignal */,
+ reason);
+
+ // Generate a new time suggestion and update the service as needed.
+ doTimeDetection(null /* nitzSignal */, reason);
+ }
+
+ /**
+ * Perform a round of time zone detection and notify the time zone detection service as needed.
+ */
+ private void doTimeZoneDetection(
+ @Nullable String countryIsoCode, @Nullable TimestampedValue<NitzData> nitzSignal,
+ @NonNull String reason) {
+ try {
+ Objects.requireNonNull(reason);
+
+ PhoneTimeZoneSuggestion suggestion =
+ mTimeZoneSuggester.getTimeZoneSuggestion(mPhoneId, countryIsoCode, nitzSignal);
+ suggestion.addDebugInfo("Detection reason=" + reason);
+ if (DBG) {
+ Rlog.d(LOG_TAG, "doTimeZoneDetection: countryIsoCode=" + countryIsoCode
+ + ", nitzSignal=" + nitzSignal + ", suggestion=" + suggestion
+ + ", reason=" + reason);
+ }
+ mNewTimeServiceHelper.maybeSuggestDeviceTimeZone(suggestion);
+ } catch (RuntimeException ex) {
+ Rlog.e(LOG_TAG, "doTimeZoneDetection: Exception thrown"
+ + " mPhoneId=" + mPhoneId
+ + ", countryIsoCode=" + countryIsoCode
+ + ", nitzSignal=" + nitzSignal
+ + ", reason=" + reason
+ + ", ex=" + ex, ex);
+ }
+ }
+
+ /**
+ * Perform a round of time detection and notify the time detection service as needed.
+ */
+ private void doTimeDetection(@Nullable TimestampedValue<NitzData> nitzSignal,
+ @NonNull String reason) {
+ try {
+ Objects.requireNonNull(reason);
+ if (nitzSignal == null) {
+ // Do nothing to withdraw previous suggestions: the service currently does not
+ // support withdrawing suggestions.
+ return;
+ }
+
+ Objects.requireNonNull(nitzSignal.getValue());
+
+ TimestampedValue<Long> newNitzTime = new TimestampedValue<>(
+ nitzSignal.getReferenceTimeMillis(),
+ nitzSignal.getValue().getCurrentTimeInMillis());
+ PhoneTimeSuggestion timeSuggestion = new PhoneTimeSuggestion(mPhoneId, newNitzTime);
+ timeSuggestion.addDebugInfo("doTimeDetection: NITZ signal used"
+ + " nitzSignal=" + nitzSignal
+ + ", newNitzTime=" + newNitzTime
+ + ", reason=" + reason);
+ mNewTimeServiceHelper.suggestDeviceTime(timeSuggestion);
+ } catch (RuntimeException ex) {
+ Rlog.e(LOG_TAG, "doTimeDetection: Exception thrown"
+ + " mPhoneId=" + mPhoneId
+ + ", nitzSignal=" + nitzSignal
+ + ", reason=" + reason
+ + ", ex=" + ex, ex);
+ }
+ }
+
+ @Override
+ public void dumpState(PrintWriter pw) {
+ pw.println(" NewNitzStateMachineImpl.mLatestNitzSignal=" + mLatestNitzSignal);
+ pw.println(" NewNitzStateMachineImpl.mGotCountryCode=" + mGotCountryCode);
+ mNewTimeServiceHelper.dumpState(pw);
+ pw.flush();
+ }
+
+ @Override
+ public void dumpLogs(FileDescriptor fd, IndentingPrintWriter ipw, String[] args) {
+ mNewTimeServiceHelper.dumpLogs(ipw);
+ }
+
+ @Nullable
+ public NitzData getCachedNitzData() {
+ return mLatestNitzSignal != null ? mLatestNitzSignal.getValue() : null;
+ }
+}
diff --git a/src/java/com/android/internal/telephony/nitz/NewTimeServiceHelper.java b/src/java/com/android/internal/telephony/nitz/NewTimeServiceHelper.java
new file mode 100644
index 0000000..7984c97
--- /dev/null
+++ b/src/java/com/android/internal/telephony/nitz/NewTimeServiceHelper.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 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.internal.telephony.nitz;
+
+import android.annotation.NonNull;
+import android.app.timedetector.PhoneTimeSuggestion;
+import android.app.timedetector.TimeDetector;
+
+import com.android.internal.telephony.nitz.service.PhoneTimeZoneSuggestion;
+import com.android.internal.util.IndentingPrintWriter;
+
+import java.io.PrintWriter;
+
+/**
+ * An interface to various time / time zone detection behaviors that should be centralized into
+ * new services.
+ */
+public interface NewTimeServiceHelper {
+
+ /**
+ * Suggests the time to the {@link TimeDetector}.
+ *
+ * @param suggestion the time
+ */
+ void suggestDeviceTime(@NonNull PhoneTimeSuggestion suggestion);
+
+ /**
+ * Suggests the time zone to the time zone detector.
+ *
+ * <p>NOTE: The PhoneTimeZoneSuggestion cannot be null. The zoneId it contains can be null to
+ * indicate there is no active suggestion; this can be used to clear a previous suggestion.
+ *
+ * @param suggestion the time zone
+ */
+ void maybeSuggestDeviceTimeZone(@NonNull PhoneTimeZoneSuggestion suggestion);
+
+ /**
+ * Dumps any logs held to the supplied writer.
+ */
+ void dumpLogs(IndentingPrintWriter ipw);
+
+ /**
+ * Dumps internal state such as field values.
+ */
+ void dumpState(PrintWriter pw);
+}
diff --git a/src/java/com/android/internal/telephony/nitz/NewTimeServiceHelperImpl.java b/src/java/com/android/internal/telephony/nitz/NewTimeServiceHelperImpl.java
new file mode 100644
index 0000000..337423b
--- /dev/null
+++ b/src/java/com/android/internal/telephony/nitz/NewTimeServiceHelperImpl.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright 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.internal.telephony.nitz;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.timedetector.PhoneTimeSuggestion;
+import android.app.timedetector.TimeDetector;
+import android.content.Context;
+import android.util.LocalLog;
+import android.util.TimestampedValue;
+
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.metrics.TelephonyMetrics;
+import com.android.internal.telephony.nitz.service.PhoneTimeZoneSuggestion;
+import com.android.internal.telephony.nitz.service.TimeZoneDetectionService;
+import com.android.internal.util.IndentingPrintWriter;
+
+import java.io.PrintWriter;
+import java.util.Objects;
+
+/**
+ * The real implementation of {@link NewTimeServiceHelper}.
+ */
+public final class NewTimeServiceHelperImpl implements NewTimeServiceHelper {
+
+ private final int mPhoneId;
+ private final TimeDetector mTimeDetector;
+ private final TimeZoneDetectionService mTimeZoneDetector;
+
+ private final LocalLog mTimeZoneLog = new LocalLog(30);
+ private final LocalLog mTimeLog = new LocalLog(30);
+
+ /**
+ * Records the last time zone suggestion made. Used to avoid sending duplicate suggestions to
+ * the time zone service. The value can be {@code null} to indicate no previous suggestion has
+ * been made.
+ */
+ @NonNull
+ private PhoneTimeZoneSuggestion mLastSuggestedTimeZone;
+
+ public NewTimeServiceHelperImpl(@NonNull Phone phone) {
+ mPhoneId = phone.getPhoneId();
+ Context context = Objects.requireNonNull(phone.getContext());
+ mTimeDetector = Objects.requireNonNull(context.getSystemService(TimeDetector.class));
+ mTimeZoneDetector = Objects.requireNonNull(TimeZoneDetectionService.getInstance(context));
+ }
+
+ @Override
+ public void suggestDeviceTime(@NonNull PhoneTimeSuggestion phoneTimeSuggestion) {
+ mTimeLog.log("Suggesting system clock update: " + phoneTimeSuggestion);
+
+ // 3 nullness assertions in 1 line
+ Objects.requireNonNull(phoneTimeSuggestion.getUtcTime().getValue());
+
+ TimestampedValue<Long> utcTime = phoneTimeSuggestion.getUtcTime();
+ TelephonyMetrics.getInstance().writeNITZEvent(mPhoneId, utcTime.getValue());
+ mTimeDetector.suggestPhoneTime(phoneTimeSuggestion);
+ }
+
+ @Override
+ public void maybeSuggestDeviceTimeZone(@NonNull PhoneTimeZoneSuggestion newSuggestion) {
+ Objects.requireNonNull(newSuggestion);
+
+ PhoneTimeZoneSuggestion oldSuggestion = mLastSuggestedTimeZone;
+ if (shouldSendNewTimeZoneSuggestion(oldSuggestion, newSuggestion)) {
+ mTimeZoneLog.log("Suggesting time zone update: " + newSuggestion);
+ mTimeZoneDetector.suggestPhoneTimeZone(newSuggestion);
+ mLastSuggestedTimeZone = newSuggestion;
+ }
+ }
+
+ private static boolean shouldSendNewTimeZoneSuggestion(
+ @Nullable PhoneTimeZoneSuggestion oldSuggestion,
+ @NonNull PhoneTimeZoneSuggestion newSuggestion) {
+ if (oldSuggestion == null) {
+ // No previous suggestion.
+ return true;
+ }
+ // This code relies on PhoneTimeZoneSuggestion.equals() to only check meaningful fields.
+ return !Objects.equals(newSuggestion, oldSuggestion);
+ }
+
+ @Override
+ public void dumpLogs(IndentingPrintWriter ipw) {
+ ipw.println("NewTimeServiceHelperImpl:");
+ ipw.increaseIndent();
+ ipw.println("Time Logs:");
+ ipw.increaseIndent();
+ mTimeLog.dump(ipw);
+ ipw.decreaseIndent();
+
+ ipw.println("Time zone Logs:");
+ ipw.increaseIndent();
+ mTimeZoneLog.dump(ipw);
+ ipw.decreaseIndent();
+ ipw.decreaseIndent();
+
+ // TODO Remove this line when the service moves to the system server.
+ mTimeZoneDetector.dumpLogs(ipw);
+ }
+
+ @Override
+ public void dumpState(PrintWriter pw) {
+ pw.println(" NewTimeServiceHelperImpl.mLastSuggestedTimeZone=" + mLastSuggestedTimeZone);
+ mTimeZoneDetector.dumpState(pw);
+ }
+}
diff --git a/src/java/com/android/internal/telephony/nitz/NitzSignalInputFilterPredicateFactory.java b/src/java/com/android/internal/telephony/nitz/NitzSignalInputFilterPredicateFactory.java
new file mode 100644
index 0000000..58cbaaa
--- /dev/null
+++ b/src/java/com/android/internal/telephony/nitz/NitzSignalInputFilterPredicateFactory.java
@@ -0,0 +1,273 @@
+/*
+ * Copyright 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.internal.telephony.nitz;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.os.PowerManager;
+import android.os.PowerManager.WakeLock;
+import android.telephony.Rlog;
+import android.util.TimestampedValue;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.NitzData;
+import com.android.internal.telephony.NitzStateMachine.DeviceState;
+import com.android.internal.telephony.nitz.NewNitzStateMachineImpl.NitzSignalInputFilterPredicate;
+
+import java.util.Arrays;
+import java.util.Objects;
+
+/**
+ * A factory class for the {@link NitzSignalInputFilterPredicate} instance used by
+ * {@link NewNitzStateMachineImpl}. This class is exposed for testing and provides access to various
+ * internal components.
+ */
+@VisibleForTesting
+public final class NitzSignalInputFilterPredicateFactory {
+
+ private static final String LOG_TAG = NewNitzStateMachineImpl.LOG_TAG;
+ private static final boolean DBG = NewNitzStateMachineImpl.DBG;
+ private static final String WAKELOCK_TAG = "NitzSignalInputFilterPredicateFactory";
+
+ private NitzSignalInputFilterPredicateFactory() {}
+
+ /**
+ * Returns the real {@link NitzSignalInputFilterPredicate} to use for NITZ signal input
+ * filtering.
+ */
+ @NonNull
+ public static NitzSignalInputFilterPredicate create(
+ @NonNull Context context, @NonNull DeviceState deviceState) {
+ Objects.requireNonNull(context);
+ Objects.requireNonNull(deviceState);
+
+ TrivalentPredicate[] components = new TrivalentPredicate[] {
+ // Disables NITZ processing entirely: can return false or null.
+ createIgnoreNitzPropertyCheck(deviceState),
+ // Filters bad reference times from new signals: can return false or null.
+ createBogusElapsedRealtimeCheck(context, deviceState),
+ // Ensures oldSignal == null is always processed: can return true or null.
+ createNoOldSignalCheck(),
+ // Adds rate limiting: can return true or false.
+ createRateLimitCheck(deviceState),
+ };
+ return new NitzSignalInputFilterPredicateImpl(components);
+ }
+
+ /**
+ * A filtering function that can give a {@code true} (must process), {@code false} (must not
+ * process) and a {@code null} (no opinion) response given a previous NITZ signal and a new
+ * signal. The previous signal may be {@code null} (unless ruled out by a prior
+ * {@link TrivalentPredicate}).
+ */
+ @VisibleForTesting
+ @FunctionalInterface
+ public interface TrivalentPredicate {
+
+ /**
+ * See {@link TrivalentPredicate}.
+ */
+ @Nullable
+ Boolean mustProcessNitzSignal(
+ @Nullable TimestampedValue<NitzData> previousSignal,
+ @NonNull TimestampedValue<NitzData> newSignal);
+ }
+
+ /**
+ * Returns a {@link TrivalentPredicate} function that implements a check for the
+ * "gsm.ignore-nitz" Android system property. The function can return {@code false} or
+ * {@code null}.
+ */
+ @VisibleForTesting
+ @NonNull
+ public static TrivalentPredicate createIgnoreNitzPropertyCheck(
+ @NonNull DeviceState deviceState) {
+ return (oldSignal, newSignal) -> {
+ boolean ignoreNitz = deviceState.getIgnoreNitz();
+ if (ignoreNitz) {
+ if (DBG) {
+ Rlog.d(LOG_TAG, "mustProcessNitzSignal: Not processing NITZ signal because"
+ + " gsm.ignore-nitz is set");
+ }
+ return false;
+ }
+ return null;
+ };
+ }
+
+ /**
+ * Returns a {@link TrivalentPredicate} function that implements a check for a bad reference
+ * time associated with {@code newSignal}. The function can return {@code false} or
+ * {@code null}.
+ */
+ @VisibleForTesting
+ @NonNull
+ public static TrivalentPredicate createBogusElapsedRealtimeCheck(
+ @NonNull Context context, @NonNull DeviceState deviceState) {
+ PowerManager powerManager =
+ (PowerManager) context.getSystemService(Context.POWER_SERVICE);
+ final WakeLock wakeLock =
+ powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKELOCK_TAG);
+
+ return (oldSignal, newSignal) -> {
+ Objects.requireNonNull(newSignal);
+
+ // Validate the newSignal to reject obviously bogus elapsedRealtime values.
+ try {
+ // Acquire the wake lock as we are reading the elapsed realtime clock below.
+ wakeLock.acquire();
+
+ long elapsedRealtime = deviceState.elapsedRealtime();
+ long millisSinceNitzReceived = elapsedRealtime - newSignal.getReferenceTimeMillis();
+ if (millisSinceNitzReceived < 0 || millisSinceNitzReceived > Integer.MAX_VALUE) {
+ if (DBG) {
+ Rlog.d(LOG_TAG, "mustProcessNitzSignal: Not processing NITZ signal"
+ + " because unexpected elapsedRealtime=" + elapsedRealtime
+ + " nitzSignal=" + newSignal);
+ }
+ return false;
+ }
+ return null;
+ } finally {
+ wakeLock.release();
+ }
+ };
+ }
+
+ /**
+ * Returns a {@link TrivalentPredicate} function that implements a check for a {@code null}
+ * {@code oldSignal} (indicating there's no history). The function can return {@code true}
+ * or {@code null}.
+ */
+ @VisibleForTesting
+ @NonNull
+ public static TrivalentPredicate createNoOldSignalCheck() {
+ // Always process a signal when there was no previous signal.
+ return (oldSignal, newSignal) -> oldSignal == null ? true : null;
+ }
+
+ /**
+ * Returns a {@link TrivalentPredicate} function that implements filtering using
+ * {@code oldSignal} and {@code newSignal}. The function can return {@code true} or
+ * {@code false} and so is intended as the final function in a chain.
+ *
+ * Function detail: if an NITZ signal received that is too similar to a previous one
+ * it should be disregarded if it's received within a configured time period.
+ * The general contract for {@link TrivalentPredicate} allows {@code previousSignal} to be
+ * {@code null}, but previous functions are expected to prevent it in this case.
+ */
+ @VisibleForTesting
+ @NonNull
+ public static TrivalentPredicate createRateLimitCheck(@NonNull DeviceState deviceState) {
+ return new TrivalentPredicate() {
+ @Override
+ @NonNull
+ public Boolean mustProcessNitzSignal(
+ @NonNull TimestampedValue<NitzData> previousSignal,
+ @NonNull TimestampedValue<NitzData> newSignal) {
+ Objects.requireNonNull(newSignal);
+ Objects.requireNonNull(newSignal.getValue());
+ Objects.requireNonNull(previousSignal);
+ Objects.requireNonNull(previousSignal.getValue());
+
+ NitzData newNitzData = newSignal.getValue();
+ NitzData previousNitzData = previousSignal.getValue();
+
+ // Compare the discrete NitzData fields associated with local time offset. Any
+ // difference and we should process the signal regardless of how recent the last one
+ // was.
+ if (!offsetInfoIsTheSame(previousNitzData, newNitzData)) {
+ return true;
+ }
+
+ // Now check the continuous NitzData field (time) to see if it is sufficiently
+ // different.
+ int nitzUpdateSpacing = deviceState.getNitzUpdateSpacingMillis();
+ int nitzUpdateDiff = deviceState.getNitzUpdateDiffMillis();
+
+ // Calculate the elapsed time between the new signal and the last signal.
+ long elapsedRealtimeSinceLastSaved = newSignal.getReferenceTimeMillis()
+ - previousSignal.getReferenceTimeMillis();
+
+ // Calculate the UTC difference between the time the two signals hold.
+ long utcTimeDifferenceMillis = newNitzData.getCurrentTimeInMillis()
+ - previousNitzData.getCurrentTimeInMillis();
+
+ // Ideally the difference between elapsedRealtimeSinceLastSaved and
+ // utcTimeDifferenceMillis would be zero.
+ long millisGainedOrLost = Math
+ .abs(utcTimeDifferenceMillis - elapsedRealtimeSinceLastSaved);
+
+ if (elapsedRealtimeSinceLastSaved > nitzUpdateSpacing
+ || millisGainedOrLost > nitzUpdateDiff) {
+ return true;
+ }
+
+ if (DBG) {
+ Rlog.d(LOG_TAG, "mustProcessNitzSignal: NITZ signal filtered"
+ + " previousSignal=" + previousSignal
+ + ", newSignal=" + newSignal
+ + ", nitzUpdateSpacing=" + nitzUpdateSpacing
+ + ", nitzUpdateDiff=" + nitzUpdateDiff);
+ }
+ return false;
+ }
+
+ private boolean offsetInfoIsTheSame(NitzData one, NitzData two) {
+ return Objects.equals(two.getDstAdjustmentMillis(), one.getDstAdjustmentMillis())
+ && Objects.equals(
+ two.getEmulatorHostTimeZone(), one.getEmulatorHostTimeZone())
+ && two.getLocalOffsetMillis() == one.getLocalOffsetMillis();
+ }
+ };
+ }
+
+ /**
+ * An implementation of {@link NitzSignalInputFilterPredicate} that tries a series of
+ * {@link TrivalentPredicate} instances until one provides a {@code true} or {@code false}
+ * response indicating that the {@code newSignal} should be processed or not. If all return
+ * {@code null} then a default of {@code true} is returned.
+ */
+ @VisibleForTesting
+ public static class NitzSignalInputFilterPredicateImpl
+ implements NitzSignalInputFilterPredicate {
+
+ @NonNull
+ private final TrivalentPredicate[] mComponents;
+
+ @VisibleForTesting
+ public NitzSignalInputFilterPredicateImpl(@NonNull TrivalentPredicate[] components) {
+ this.mComponents = Arrays.copyOf(components, components.length);
+ }
+
+ @Override
+ public boolean mustProcessNitzSignal(@Nullable TimestampedValue<NitzData> oldSignal,
+ @NonNull TimestampedValue<NitzData> newSignal) {
+ Objects.requireNonNull(newSignal);
+
+ for (TrivalentPredicate component : mComponents) {
+ Boolean result = component.mustProcessNitzSignal(oldSignal, newSignal);
+ if (result != null) {
+ return result;
+ }
+ }
+ // The default is to process.
+ return true;
+ }
+ }
+}
diff --git a/src/java/com/android/internal/telephony/nitz/TimeZoneSuggesterImpl.java b/src/java/com/android/internal/telephony/nitz/TimeZoneSuggesterImpl.java
new file mode 100644
index 0000000..c5d9df6
--- /dev/null
+++ b/src/java/com/android/internal/telephony/nitz/TimeZoneSuggesterImpl.java
@@ -0,0 +1,288 @@
+/*
+ * Copyright 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.internal.telephony.nitz;
+
+import static com.android.internal.telephony.TimeZoneLookupHelper.CountryResult.QUALITY_DEFAULT_BOOSTED;
+import static com.android.internal.telephony.TimeZoneLookupHelper.CountryResult.QUALITY_MULTIPLE_ZONES_DIFFERENT_OFFSETS;
+import static com.android.internal.telephony.TimeZoneLookupHelper.CountryResult.QUALITY_MULTIPLE_ZONES_SAME_OFFSET;
+import static com.android.internal.telephony.TimeZoneLookupHelper.CountryResult.QUALITY_SINGLE_ZONE;
+import static com.android.internal.telephony.nitz.service.PhoneTimeZoneSuggestion.createEmptySuggestion;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.telephony.Rlog;
+import android.text.TextUtils;
+import android.util.TimestampedValue;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.NitzData;
+import com.android.internal.telephony.NitzStateMachine.DeviceState;
+import com.android.internal.telephony.TimeZoneLookupHelper;
+import com.android.internal.telephony.TimeZoneLookupHelper.CountryResult;
+import com.android.internal.telephony.nitz.NewNitzStateMachineImpl.TimeZoneSuggester;
+import com.android.internal.telephony.nitz.service.PhoneTimeZoneSuggestion;
+
+import java.util.Objects;
+
+/**
+ * The real implementation of {@link TimeZoneSuggester}.
+ */
+@VisibleForTesting
+public class TimeZoneSuggesterImpl implements TimeZoneSuggester {
+
+ private static final String LOG_TAG = NewNitzStateMachineImpl.LOG_TAG;
+
+ private final DeviceState mDeviceState;
+ private final TimeZoneLookupHelper mTimeZoneLookupHelper;
+
+ @VisibleForTesting
+ public TimeZoneSuggesterImpl(
+ @NonNull DeviceState deviceState, @NonNull TimeZoneLookupHelper timeZoneLookupHelper) {
+ mDeviceState = Objects.requireNonNull(deviceState);
+ mTimeZoneLookupHelper = Objects.requireNonNull(timeZoneLookupHelper);
+ }
+
+ @Override
+ @NonNull
+ public PhoneTimeZoneSuggestion getTimeZoneSuggestion(int phoneId,
+ @Nullable String countryIsoCode, @Nullable TimestampedValue<NitzData> nitzSignal) {
+ try {
+ // Check for overriding NITZ-based signals from Android running in an emulator.
+ PhoneTimeZoneSuggestion overridingSuggestion = null;
+ if (nitzSignal != null) {
+ NitzData nitzData = nitzSignal.getValue();
+ if (nitzData.getEmulatorHostTimeZone() != null) {
+ overridingSuggestion = new PhoneTimeZoneSuggestion(phoneId);
+ overridingSuggestion.setZoneId(nitzData.getEmulatorHostTimeZone().getID());
+ overridingSuggestion.setMatchType(PhoneTimeZoneSuggestion.EMULATOR_ZONE_ID);
+ overridingSuggestion.setQuality(PhoneTimeZoneSuggestion.SINGLE_ZONE);
+ overridingSuggestion.addDebugInfo("Emulator time zone override: " + nitzData);
+ }
+ }
+
+ PhoneTimeZoneSuggestion suggestion;
+ if (overridingSuggestion != null) {
+ suggestion = overridingSuggestion;
+ } else if (countryIsoCode == null) {
+ if (nitzSignal == null) {
+ suggestion = createEmptySuggestion(phoneId,
+ "getTimeZoneSuggestion: nitzSignal=null, countryIsoCode=null");
+ } else {
+ // NITZ only - wait until we have a country.
+ suggestion = createEmptySuggestion(phoneId, "getTimeZoneSuggestion:"
+ + " nitzSignal=" + nitzSignal + ", countryIsoCode=null");
+ }
+ } else { // countryIsoCode != null
+ if (nitzSignal == null) {
+ if (countryIsoCode.isEmpty()) {
+ // This is assumed to be a test network with no NITZ data to go on.
+ suggestion = createEmptySuggestion(phoneId,
+ "getTimeZoneSuggestion: nitzSignal=null, countryIsoCode=\"\"");
+ } else {
+ // Country only
+ suggestion = findTimeZoneFromNetworkCountryCode(
+ phoneId, countryIsoCode, mDeviceState.currentTimeMillis());
+ }
+ } else { // nitzSignal != null
+ if (countryIsoCode.isEmpty()) {
+ // We have been told we have a country code but it's empty. This is most
+ // likely because we're on a test network that's using a bogus MCC
+ // (eg, "001"). Obtain a TimeZone based only on the NITZ parameters: without
+ // a country it will be arbitrary, but it should at least have the correct
+ // offset.
+ suggestion = findTimeZoneForTestNetwork(phoneId, nitzSignal);
+ } else {
+ // We have both NITZ and Country code.
+ suggestion = findTimeZoneFromCountryAndNitz(
+ phoneId, countryIsoCode, nitzSignal);
+ }
+ }
+ }
+
+ // Ensure the return value is never null.
+ Objects.requireNonNull(suggestion);
+
+ return suggestion;
+ } catch (RuntimeException e) {
+ // This would suggest a coding error. Log at a high level and try to avoid leaving the
+ // device in a bad state by making an "empty" suggestion.
+ String message = "getTimeZoneSuggestion: Error during lookup: "
+ + " countryIsoCode=" + countryIsoCode
+ + ", nitzSignal=" + nitzSignal
+ + ", e=" + e.getMessage();
+ PhoneTimeZoneSuggestion errorSuggestion = createEmptySuggestion(phoneId, message);
+ errorSuggestion.addDebugInfo(message);
+ Rlog.w(LOG_TAG, message, e);
+ return errorSuggestion;
+ }
+ }
+
+ /**
+ * Creates a {@link PhoneTimeZoneSuggestion} using only NITZ. This happens when the device
+ * is attached to a test cell with an unrecognized MCC. In these cases we try to return a
+ * suggestion for an arbitrary time zone that matches the NITZ offset information.
+ */
+ @NonNull
+ private PhoneTimeZoneSuggestion findTimeZoneForTestNetwork(
+ int phoneId, @NonNull TimestampedValue<NitzData> nitzSignal) {
+ Objects.requireNonNull(nitzSignal);
+ NitzData nitzData = Objects.requireNonNull(nitzSignal.getValue());
+
+ PhoneTimeZoneSuggestion result = new PhoneTimeZoneSuggestion(phoneId);
+ result.addDebugInfo("findTimeZoneForTestNetwork: nitzSignal=" + nitzSignal);
+ TimeZoneLookupHelper.OffsetResult lookupResult =
+ mTimeZoneLookupHelper.lookupByNitz(nitzData);
+ if (lookupResult == null) {
+ result.addDebugInfo("findTimeZoneForTestNetwork: No zone found");
+ } else {
+ result.setZoneId(lookupResult.getTimeZone().getID());
+ result.setMatchType(PhoneTimeZoneSuggestion.TEST_NETWORK_OFFSET_ONLY);
+ int quality = lookupResult.getIsOnlyMatch() ? PhoneTimeZoneSuggestion.SINGLE_ZONE
+ : PhoneTimeZoneSuggestion.MULTIPLE_ZONES_WITH_SAME_OFFSET;
+ result.setQuality(quality);
+ result.addDebugInfo("findTimeZoneForTestNetwork: lookupResult=" + lookupResult);
+ }
+ return result;
+ }
+
+ /**
+ * Creates a {@link PhoneTimeZoneSuggestion} using network country code and NITZ.
+ */
+ @NonNull
+ private PhoneTimeZoneSuggestion findTimeZoneFromCountryAndNitz(
+ int phoneId, @NonNull String countryIsoCode,
+ @NonNull TimestampedValue<NitzData> nitzSignal) {
+ Objects.requireNonNull(countryIsoCode);
+ Objects.requireNonNull(nitzSignal);
+
+ PhoneTimeZoneSuggestion suggestion = new PhoneTimeZoneSuggestion(phoneId);
+ suggestion.addDebugInfo("findTimeZoneFromCountryAndNitz: countryIsoCode=" + countryIsoCode
+ + ", nitzSignal=" + nitzSignal);
+ NitzData nitzData = Objects.requireNonNull(nitzSignal.getValue());
+ if (isNitzSignalOffsetInfoBogus(countryIsoCode, nitzData)) {
+ suggestion.addDebugInfo("findTimeZoneFromCountryAndNitz: NITZ signal looks bogus");
+ return suggestion;
+ }
+
+ // Try to find a match using both country + NITZ signal.
+ TimeZoneLookupHelper.OffsetResult lookupResult =
+ mTimeZoneLookupHelper.lookupByNitzCountry(nitzData, countryIsoCode);
+ if (lookupResult != null) {
+ suggestion.setZoneId(lookupResult.getTimeZone().getID());
+ suggestion.setMatchType(PhoneTimeZoneSuggestion.NETWORK_COUNTRY_AND_OFFSET);
+ int quality = lookupResult.getIsOnlyMatch()
+ ? PhoneTimeZoneSuggestion.SINGLE_ZONE
+ : PhoneTimeZoneSuggestion.MULTIPLE_ZONES_WITH_SAME_OFFSET;
+ suggestion.setQuality(quality);
+ suggestion.addDebugInfo("findTimeZoneFromCountryAndNitz: lookupResult=" + lookupResult);
+ return suggestion;
+ }
+
+ // The country + offset provided no match, so see if the country by itself would be enough.
+ CountryResult countryResult = mTimeZoneLookupHelper.lookupByCountry(
+ countryIsoCode, nitzData.getCurrentTimeInMillis());
+ if (countryResult == null) {
+ // Country not recognized.
+ suggestion.addDebugInfo(
+ "findTimeZoneFromCountryAndNitz: lookupByCountry() country not recognized");
+ return suggestion;
+ }
+
+ // If the country has a single zone, or it has multiple zones but the default zone is
+ // "boosted" (i.e. the country default is considered a good suggestion in most cases) then
+ // use it.
+ if (countryResult.quality == QUALITY_SINGLE_ZONE
+ || countryResult.quality == QUALITY_DEFAULT_BOOSTED) {
+ suggestion.setZoneId(countryResult.zoneId);
+ suggestion.setMatchType(PhoneTimeZoneSuggestion.NETWORK_COUNTRY_ONLY);
+ suggestion.setQuality(PhoneTimeZoneSuggestion.SINGLE_ZONE);
+ suggestion.addDebugInfo(
+ "findTimeZoneFromCountryAndNitz: high quality country-only suggestion:"
+ + " countryResult=" + countryResult);
+ return suggestion;
+ }
+
+ // Quality is not high enough to set the zone using country only.
+ suggestion.addDebugInfo("findTimeZoneFromCountryAndNitz: country-only suggestion quality"
+ + " not high enough. countryResult=" + countryResult);
+ return suggestion;
+ }
+
+ /**
+ * Creates a {@link PhoneTimeZoneSuggestion} using only network country code; works well on
+ * countries which only have one time zone or multiple zones with the same offset.
+ *
+ * @param countryIsoCode country code from network MCC
+ * @param whenMillis the time to use when looking at time zone rules data
+ */
+ @NonNull
+ private PhoneTimeZoneSuggestion findTimeZoneFromNetworkCountryCode(
+ int phoneId, @NonNull String countryIsoCode, long whenMillis) {
+ Objects.requireNonNull(countryIsoCode);
+ if (TextUtils.isEmpty(countryIsoCode)) {
+ throw new IllegalArgumentException("countryIsoCode must not be empty");
+ }
+
+ PhoneTimeZoneSuggestion result = new PhoneTimeZoneSuggestion(phoneId);
+ result.addDebugInfo("findTimeZoneFromNetworkCountryCode:"
+ + " whenMillis=" + whenMillis + ", countryIsoCode=" + countryIsoCode);
+ CountryResult lookupResult = mTimeZoneLookupHelper.lookupByCountry(
+ countryIsoCode, whenMillis);
+ if (lookupResult != null) {
+ result.setZoneId(lookupResult.zoneId);
+ result.setMatchType(PhoneTimeZoneSuggestion.NETWORK_COUNTRY_ONLY);
+
+ int quality;
+ if (lookupResult.quality == QUALITY_SINGLE_ZONE
+ || lookupResult.quality == QUALITY_DEFAULT_BOOSTED) {
+ quality = PhoneTimeZoneSuggestion.SINGLE_ZONE;
+ } else if (lookupResult.quality == QUALITY_MULTIPLE_ZONES_SAME_OFFSET) {
+ quality = PhoneTimeZoneSuggestion.MULTIPLE_ZONES_WITH_SAME_OFFSET;
+ } else if (lookupResult.quality == QUALITY_MULTIPLE_ZONES_DIFFERENT_OFFSETS) {
+ quality = PhoneTimeZoneSuggestion.MULTIPLE_ZONES_WITH_DIFFERENT_OFFSETS;
+ } else {
+ // This should never happen.
+ throw new IllegalArgumentException(
+ "lookupResult.quality not recognized: countryIsoCode=" + countryIsoCode
+ + ", whenMillis=" + whenMillis + ", lookupResult=" + lookupResult);
+ }
+ result.setQuality(quality);
+ result.addDebugInfo("findTimeZoneFromNetworkCountryCode: lookupResult=" + lookupResult);
+ } else {
+ result.addDebugInfo("findTimeZoneFromNetworkCountryCode: Country not recognized?");
+ }
+ return result;
+ }
+
+ /**
+ * Returns true if the NITZ signal is definitely bogus, assuming that the country is correct.
+ */
+ private boolean isNitzSignalOffsetInfoBogus(String countryIsoCode, NitzData nitzData) {
+ if (TextUtils.isEmpty(countryIsoCode)) {
+ // We cannot say for sure.
+ return false;
+ }
+
+ boolean zeroOffsetNitz = nitzData.getLocalOffsetMillis() == 0;
+ return zeroOffsetNitz && !countryUsesUtc(countryIsoCode, nitzData);
+ }
+
+ private boolean countryUsesUtc(String countryIsoCode, NitzData nitzData) {
+ return mTimeZoneLookupHelper.countryUsesUtc(
+ countryIsoCode, nitzData.getCurrentTimeInMillis());
+ }
+}
diff --git a/src/java/com/android/internal/telephony/nitz/service/PhoneTimeZoneSuggestion.java b/src/java/com/android/internal/telephony/nitz/service/PhoneTimeZoneSuggestion.java
new file mode 100644
index 0000000..a5078a7
--- /dev/null
+++ b/src/java/com/android/internal/telephony/nitz/service/PhoneTimeZoneSuggestion.java
@@ -0,0 +1,254 @@
+/*
+ * Copyright 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.internal.telephony.nitz.service;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * A suggested time zone from a Phone-based signal, e.g. from MCC and NITZ information.
+ */
+public final class PhoneTimeZoneSuggestion implements Parcelable {
+
+ public static final Creator<PhoneTimeZoneSuggestion> CREATOR =
+ new Creator<PhoneTimeZoneSuggestion>() {
+ public PhoneTimeZoneSuggestion createFromParcel(Parcel in) {
+ return PhoneTimeZoneSuggestion.createFromParcel(in);
+ }
+
+ public PhoneTimeZoneSuggestion[] newArray(int size) {
+ return new PhoneTimeZoneSuggestion[size];
+ }
+ };
+
+ /**
+ * Creates an empty time zone suggestion, i.e. one that will cancel previous suggestions with
+ * the same {@code phoneId}.
+ */
+ @NonNull
+ public static PhoneTimeZoneSuggestion createEmptySuggestion(
+ int phoneId, @NonNull String debugInfo) {
+ PhoneTimeZoneSuggestion timeZoneSuggestion = new PhoneTimeZoneSuggestion(phoneId);
+ timeZoneSuggestion.addDebugInfo(debugInfo);
+ return timeZoneSuggestion;
+ }
+
+ @IntDef({ MATCH_TYPE_NA, NETWORK_COUNTRY_ONLY, NETWORK_COUNTRY_AND_OFFSET, EMULATOR_ZONE_ID,
+ TEST_NETWORK_OFFSET_ONLY })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface MatchType {}
+
+ /** Used when match type is not applicable. */
+ public static final int MATCH_TYPE_NA = 0;
+
+ /**
+ * Only the network country is known.
+ */
+ public static final int NETWORK_COUNTRY_ONLY = 2;
+
+ /**
+ * Both the network county and offset were known.
+ */
+ public static final int NETWORK_COUNTRY_AND_OFFSET = 3;
+
+ /**
+ * The device is running in an emulator and an NITZ signal was simulated containing an
+ * Android extension with an explicit Olson ID.
+ */
+ public static final int EMULATOR_ZONE_ID = 4;
+
+ /**
+ * The phone is most likely running in a test network not associated with a country (this is
+ * distinct from the country just not being known yet).
+ * Historically, Android has just picked an arbitrary time zone with the correct offset when
+ * on a test network.
+ */
+ public static final int TEST_NETWORK_OFFSET_ONLY = 5;
+
+ @IntDef({ QUALITY_NA, SINGLE_ZONE, MULTIPLE_ZONES_WITH_SAME_OFFSET,
+ MULTIPLE_ZONES_WITH_DIFFERENT_OFFSETS })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Quality {}
+
+ /** Used when quality is not applicable. */
+ public static final int QUALITY_NA = 0;
+
+ /** There is only one answer */
+ public static final int SINGLE_ZONE = 1;
+
+ /**
+ * There are multiple answers, but they all shared the same offset / DST state at the time
+ * the suggestion was created. i.e. it might be the wrong zone but the user won't notice
+ * immediately if it is wrong.
+ */
+ public static final int MULTIPLE_ZONES_WITH_SAME_OFFSET = 2;
+
+ /**
+ * There are multiple answers with different offsets. The one given is just one possible.
+ */
+ public static final int MULTIPLE_ZONES_WITH_DIFFERENT_OFFSETS = 3;
+
+ /**
+ * The ID of the phone this suggestion is associated with. For multiple-sim devices this
+ * helps to establish origin so filtering / stickiness can be implemented.
+ */
+ private final int mPhoneId;
+
+ /**
+ * The suggestion. {@code null} means there is no current suggestion and any previous suggestion
+ * should be forgotten.
+ */
+ private String mZoneId;
+
+ /**
+ * The type of "match" used to establish the time zone.
+ */
+ @MatchType
+ private int mMatchType;
+
+ /**
+ * A measure of the quality of the time zone suggestion, i.e. how confident one could be in
+ * it.
+ */
+ @Quality
+ private int mQuality;
+
+ /**
+ * Free-form debug information about how the signal was derived. Used for debug only,
+ * intentionally not used in equals(), etc.
+ */
+ private List<String> mDebugInfo;
+
+ public PhoneTimeZoneSuggestion(int phoneId) {
+ this.mPhoneId = phoneId;
+ }
+
+ @SuppressWarnings("unchecked")
+ private static PhoneTimeZoneSuggestion createFromParcel(Parcel in) {
+ int phoneId = in.readInt();
+ PhoneTimeZoneSuggestion phoneTimeZoneSuggestion = new PhoneTimeZoneSuggestion(phoneId);
+ phoneTimeZoneSuggestion.mZoneId = in.readString();
+ phoneTimeZoneSuggestion.mMatchType = in.readInt();
+ phoneTimeZoneSuggestion.mQuality = in.readInt();
+ phoneTimeZoneSuggestion.mDebugInfo =
+ (List<String>) in.readArrayList(PhoneTimeZoneSuggestion.class.getClassLoader());
+ return phoneTimeZoneSuggestion;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeInt(mPhoneId);
+ dest.writeString(mZoneId);
+ dest.writeInt(mMatchType);
+ dest.writeInt(mQuality);
+ dest.writeList(mDebugInfo);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ public int getPhoneId() {
+ return mPhoneId;
+ }
+
+ @Nullable
+ public String getZoneId() {
+ return mZoneId;
+ }
+
+ public void setZoneId(@Nullable String zoneId) {
+ this.mZoneId = zoneId;
+ }
+
+
+ @MatchType
+ public int getMatchType() {
+ return mMatchType;
+ }
+
+ public void setMatchType(@MatchType int matchType) {
+ this.mMatchType = matchType;
+ }
+ @Quality
+ public int getQuality() {
+ return mQuality;
+ }
+
+ public void setQuality(@Quality int quality) {
+ this.mQuality = quality;
+ }
+
+ public List<String> getDebugInfo() {
+ return Collections.unmodifiableList(mDebugInfo);
+ }
+
+ /**
+ * Associates information with the instance that can be useful for debugging / logging. The
+ * information is present in {@link #toString()} but is not considered for
+ * {@link #equals(Object)} and {@link #hashCode()}.
+ */
+ public void addDebugInfo(String... debugInfos) {
+ if (mDebugInfo == null) {
+ mDebugInfo = new ArrayList<>();
+ }
+ mDebugInfo.addAll(Arrays.asList(debugInfos));
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ PhoneTimeZoneSuggestion that = (PhoneTimeZoneSuggestion) o;
+ return mPhoneId == that.mPhoneId
+ && mMatchType == that.mMatchType
+ && mQuality == that.mQuality
+ && Objects.equals(mZoneId, that.mZoneId);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mPhoneId, mZoneId, mMatchType, mQuality);
+ }
+
+ @Override
+ public String toString() {
+ return "PhoneTimeZoneSuggestion{"
+ + "mPhoneId=" + mPhoneId
+ + ", mZoneId='" + mZoneId + '\''
+ + ", mMatchType=" + mMatchType
+ + ", mQuality=" + mQuality
+ + ", mDebugInfo=" + mDebugInfo
+ + '}';
+ }
+}
diff --git a/src/java/com/android/internal/telephony/nitz/service/TimeZoneDetectionService.java b/src/java/com/android/internal/telephony/nitz/service/TimeZoneDetectionService.java
new file mode 100644
index 0000000..a766d9a
--- /dev/null
+++ b/src/java/com/android/internal/telephony/nitz/service/TimeZoneDetectionService.java
@@ -0,0 +1,489 @@
+/*
+ * Copyright 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.internal.telephony.nitz.service;
+
+import static com.android.internal.telephony.nitz.service.PhoneTimeZoneSuggestion.EMULATOR_ZONE_ID;
+import static com.android.internal.telephony.nitz.service.PhoneTimeZoneSuggestion.MATCH_TYPE_NA;
+import static com.android.internal.telephony.nitz.service.PhoneTimeZoneSuggestion.MULTIPLE_ZONES_WITH_DIFFERENT_OFFSETS;
+import static com.android.internal.telephony.nitz.service.PhoneTimeZoneSuggestion.MULTIPLE_ZONES_WITH_SAME_OFFSET;
+import static com.android.internal.telephony.nitz.service.PhoneTimeZoneSuggestion.NETWORK_COUNTRY_AND_OFFSET;
+import static com.android.internal.telephony.nitz.service.PhoneTimeZoneSuggestion.NETWORK_COUNTRY_ONLY;
+import static com.android.internal.telephony.nitz.service.PhoneTimeZoneSuggestion.QUALITY_NA;
+import static com.android.internal.telephony.nitz.service.PhoneTimeZoneSuggestion.SINGLE_ZONE;
+import static com.android.internal.telephony.nitz.service.PhoneTimeZoneSuggestion.TEST_NETWORK_OFFSET_ONLY;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.util.ArrayMap;
+import android.util.Log;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.IndentingPrintWriter;
+
+import java.io.PrintWriter;
+import java.util.LinkedList;
+import java.util.Objects;
+
+/**
+ * A singleton, stateful time zone detection service that is aware of multiple phone devices. It
+ * keeps track of the most recent suggestion from each phone and it uses the best based on a scoring
+ * algorithm. If both phones provide the same score then the phone with the lowest numeric ID
+ * "wins". If the situation changes and it is no longer possible to be confident about the time
+ * zone, phones must submit an empty suggestion in order to "withdraw" their previous suggestion.
+ *
+ * <p>Ultimately, this responsibility will be moved to system server and then it will be extended /
+ * rewritten to handle non-telephony time zone signals.
+ */
+public class TimeZoneDetectionService {
+
+ /**
+ * Used by {@link TimeZoneDetectionService} to interact with device settings. It can be faked
+ * for tests.
+ */
+ @VisibleForTesting
+ public interface Helper {
+
+ /**
+ * Callback interface for automatic detection enable/disable changes.
+ */
+ interface Listener {
+ /**
+ * Automatic time zone detection has been enabled or disabled.
+ */
+ void onTimeZoneDetectionChange(boolean enabled);
+ }
+
+ /**
+ * Sets a listener that will be called when the automatic time / time zone detection setting
+ * changes.
+ */
+ void setListener(Listener listener);
+
+ /**
+ * Returns true if automatic time zone detection is enabled in settings.
+ */
+ boolean isTimeZoneDetectionEnabled();
+
+ /**
+ * Returns true if the device has had an explicit time zone set.
+ */
+ boolean isTimeZoneSettingInitialized();
+
+ /**
+ * Set the device time zone from the suggestion as needed.
+ */
+ void setDeviceTimeZoneFromSuggestion(@NonNull PhoneTimeZoneSuggestion timeZoneSuggestion);
+
+ /**
+ * Dumps any logs held to the supplied writer.
+ */
+ void dumpLogs(IndentingPrintWriter ipw);
+
+ /**
+ * Dumps internal state such as field values.
+ */
+ void dumpState(PrintWriter pw);
+ }
+
+ static final String LOG_TAG = "TimeZoneDetectionService";
+ static final boolean DBG = true;
+
+ /**
+ * The abstract score for an empty or invalid suggestion.
+ *
+ * Used to score suggestions where there is no zone.
+ */
+ @VisibleForTesting
+ public static final int SCORE_NONE = 0;
+
+ /**
+ * The abstract score for a low quality suggestion.
+ *
+ * Used to score suggestions where:
+ * The suggested zone ID is one of several possibilities, and the possibilities have different
+ * offsets.
+ *
+ * You would have to be quite desperate to want to use this choice.
+ */
+ @VisibleForTesting
+ public static final int SCORE_LOW = 1;
+
+ /**
+ * The abstract score for a medium quality suggestion.
+ *
+ * Used for:
+ * The suggested zone ID is one of several possibilities but at least the possibilities have the
+ * same offset. Users would get the correct time but for the wrong reason. i.e. their device may
+ * switch to DST at the wrong time and (for example) their calendar events.
+ */
+ @VisibleForTesting
+ public static final int SCORE_MEDIUM = 2;
+
+ /**
+ * The abstract score for a high quality suggestion.
+ *
+ * Used for:
+ * The suggestion was for one zone ID and the answer was unambiguous and likely correct given
+ * the info available.
+ */
+ @VisibleForTesting
+ public static final int SCORE_HIGH = 3;
+
+ /**
+ * The abstract score for a highest quality suggestion.
+ *
+ * Used for:
+ * Suggestions that must "win" because they constitute test or emulator zone ID.
+ */
+ @VisibleForTesting
+ public static final int SCORE_HIGHEST = 4;
+
+ /** The threshold at which suggestions are good enough to use to set the device's time zone. */
+ @VisibleForTesting
+ public static final int SCORE_USAGE_THRESHOLD = SCORE_MEDIUM;
+
+ /** The singleton instance. */
+ private static TimeZoneDetectionService sInstance;
+
+ /**
+ * Returns the singleton instance, constructing as needed with the supplied context.
+ */
+ public static synchronized TimeZoneDetectionService getInstance(Context context) {
+ if (sInstance == null) {
+ Helper timeZoneDetectionServiceHelper = new TimeZoneDetectionServiceHelperImpl(context);
+ sInstance = new TimeZoneDetectionService(timeZoneDetectionServiceHelper);
+ }
+ return sInstance;
+ }
+
+ private static final int KEEP_SUGGESTION_HISTORY_SIZE = 30;
+
+ /**
+ * A mapping from phoneId to a linked list of time zone suggestions (the head being the latest).
+ * We typically expect one or two entries in this Map: devices will have a small number
+ * of telephony devices and phoneIds are assumed to be stable. The LinkedList associated with
+ * the ID will not exceed {@link #KEEP_SUGGESTION_HISTORY_SIZE} in size.
+ */
+ @GuardedBy("this")
+ private ArrayMap<Integer, LinkedList<QualifiedPhoneTimeZoneSuggestion>> mSuggestionByPhoneId =
+ new ArrayMap<>();
+
+ /**
+ * The most recent best guess of time zone from all phones. Can be {@code null} to indicate
+ * there would be no current suggestion.
+ */
+ @GuardedBy("this")
+ @Nullable
+ private QualifiedPhoneTimeZoneSuggestion mCurrentSuggestion;
+
+ // Dependencies and log state.
+ private final Helper mTimeZoneDetectionServiceHelper;
+
+ @VisibleForTesting
+ public TimeZoneDetectionService(Helper timeZoneDetectionServiceHelper) {
+ mTimeZoneDetectionServiceHelper = timeZoneDetectionServiceHelper;
+ mTimeZoneDetectionServiceHelper.setListener(enabled -> {
+ if (enabled) {
+ handleAutoTimeZoneEnabled();
+ }
+ });
+ }
+
+ /**
+ * Suggests a time zone for the device, or withdraws a previous suggestion if
+ * {@link PhoneTimeZoneSuggestion#getZoneId()} is {@code null}. The suggestion is scoped to a
+ * specific {@link PhoneTimeZoneSuggestion#getPhoneId() phone}.
+ * See {@link PhoneTimeZoneSuggestion} for an explanation of the metadata associated with a
+ * suggestion. The service uses suggestions to decide whether to modify the device's time zone
+ * setting and what to set it to.
+ */
+ public synchronized void suggestPhoneTimeZone(@NonNull PhoneTimeZoneSuggestion newSuggestion) {
+ if (DBG) {
+ Log.d(LOG_TAG, "suggestPhoneTimeZone: newSuggestion=" + newSuggestion);
+ }
+ Objects.requireNonNull(newSuggestion);
+
+ int score = scoreSuggestion(newSuggestion);
+ QualifiedPhoneTimeZoneSuggestion scoredSuggestion =
+ new QualifiedPhoneTimeZoneSuggestion(newSuggestion, score);
+
+ // Record the suggestion against the correct phoneId.
+ LinkedList<QualifiedPhoneTimeZoneSuggestion> suggestions =
+ mSuggestionByPhoneId.get(newSuggestion.getPhoneId());
+ if (suggestions == null) {
+ suggestions = new LinkedList<>();
+ mSuggestionByPhoneId.put(newSuggestion.getPhoneId(), suggestions);
+ }
+ suggestions.addFirst(scoredSuggestion);
+ if (suggestions.size() > KEEP_SUGGESTION_HISTORY_SIZE) {
+ suggestions.removeLast();
+ }
+
+ // Now run the competition between the phones' suggestions.
+ doTimeZoneDetection();
+ }
+
+ private static int scoreSuggestion(@NonNull PhoneTimeZoneSuggestion suggestion) {
+ int score;
+ if (suggestion.getZoneId() == null || !isValid(suggestion)) {
+ score = SCORE_NONE;
+ } else if (suggestion.getMatchType() == TEST_NETWORK_OFFSET_ONLY
+ || suggestion.getMatchType() == EMULATOR_ZONE_ID) {
+ // Handle emulator / test cases : These suggestions should always just be used.
+ score = SCORE_HIGHEST;
+ } else if (suggestion.getQuality() == SINGLE_ZONE) {
+ score = SCORE_HIGH;
+ } else if (suggestion.getQuality() == MULTIPLE_ZONES_WITH_SAME_OFFSET) {
+ // The suggestion may be wrong, but at least the offset should be correct.
+ score = SCORE_MEDIUM;
+ } else if (suggestion.getQuality() == MULTIPLE_ZONES_WITH_DIFFERENT_OFFSETS) {
+ // The suggestion has a good chance of being wrong.
+ score = SCORE_LOW;
+ } else {
+ throw new AssertionError();
+ }
+ return score;
+ }
+
+ private static boolean isValid(@NonNull PhoneTimeZoneSuggestion suggestion) {
+ int quality = suggestion.getQuality();
+ int matchType = suggestion.getMatchType();
+ if (suggestion.getZoneId() == null) {
+ return quality == QUALITY_NA && matchType == MATCH_TYPE_NA;
+ } else {
+ boolean qualityValid = quality == SINGLE_ZONE
+ || quality == MULTIPLE_ZONES_WITH_SAME_OFFSET
+ || quality == MULTIPLE_ZONES_WITH_DIFFERENT_OFFSETS;
+ boolean matchTypeValid = matchType == NETWORK_COUNTRY_ONLY
+ || matchType == NETWORK_COUNTRY_AND_OFFSET
+ || matchType == EMULATOR_ZONE_ID
+ || matchType == TEST_NETWORK_OFFSET_ONLY;
+ return qualityValid && matchTypeValid;
+ }
+ }
+
+ /**
+ * Finds the best available time zone suggestion from all phones. If it is high-enough quality
+ * and automatic time zone detection is enabled then it will be set on the device. The outcome
+ * can be that this service becomes / remains un-opinionated and nothing is set.
+ */
+ @GuardedBy("this")
+ private void doTimeZoneDetection() {
+ QualifiedPhoneTimeZoneSuggestion bestSuggestion = findBestSuggestion();
+ boolean timeZoneDetectionEnabled =
+ mTimeZoneDetectionServiceHelper.isTimeZoneDetectionEnabled();
+
+ // Work out what to do with the best suggestion.
+ if (bestSuggestion == null) {
+ // There is no suggestion. Become un-opinionated.
+ if (DBG) {
+ Log.d(LOG_TAG, "doTimeZoneDetection: No good suggestion."
+ + " bestSuggestion=null"
+ + ", timeZoneDetectionEnabled=" + timeZoneDetectionEnabled);
+ }
+ mCurrentSuggestion = null;
+ return;
+ }
+
+ // Special case handling for uninitialized devices. This should only happen once.
+ String newZoneId = bestSuggestion.suggestion.getZoneId();
+ if (newZoneId != null && !mTimeZoneDetectionServiceHelper.isTimeZoneSettingInitialized()) {
+ Log.i(LOG_TAG, "doTimeZoneDetection: Device has no time zone set so might set the"
+ + " device to the best available suggestion."
+ + " bestSuggestion=" + bestSuggestion
+ + ", timeZoneDetectionEnabled=" + timeZoneDetectionEnabled);
+
+ mCurrentSuggestion = bestSuggestion;
+ if (timeZoneDetectionEnabled) {
+ mTimeZoneDetectionServiceHelper.setDeviceTimeZoneFromSuggestion(
+ bestSuggestion.suggestion);
+ }
+ return;
+ }
+
+ boolean suggestionGoodEnough = bestSuggestion.score >= SCORE_USAGE_THRESHOLD;
+ if (!suggestionGoodEnough) {
+ if (DBG) {
+ Log.d(LOG_TAG, "doTimeZoneDetection: Suggestion not good enough."
+ + " bestSuggestion=" + bestSuggestion);
+ }
+ mCurrentSuggestion = null;
+ return;
+ }
+
+ // Paranoia: Every suggestion above the SCORE_USAGE_THRESHOLD should have a non-null time
+ // zone ID.
+ if (newZoneId == null) {
+ Log.w(LOG_TAG, "Empty zone suggestion scored higher than expected. This is an error:"
+ + " bestSuggestion=" + bestSuggestion);
+ mCurrentSuggestion = null;
+ return;
+ }
+
+ // There is a good suggestion. Store the suggestion and set the device time zone if
+ // settings allow.
+ mCurrentSuggestion = bestSuggestion;
+
+ // Only set the device time zone if time zone detection is enabled.
+ if (!timeZoneDetectionEnabled) {
+ if (DBG) {
+ Log.d(LOG_TAG, "doTimeZoneDetection: Not setting the time zone because time zone"
+ + " detection is disabled."
+ + " bestSuggestion=" + bestSuggestion);
+ }
+ return;
+ }
+ mTimeZoneDetectionServiceHelper.setDeviceTimeZoneFromSuggestion(bestSuggestion.suggestion);
+ }
+
+ @GuardedBy("this")
+ @Nullable
+ private QualifiedPhoneTimeZoneSuggestion findBestSuggestion() {
+ QualifiedPhoneTimeZoneSuggestion bestSuggestion = null;
+
+ // Iterate over the latest QualifiedPhoneTimeZoneSuggestion objects received for each phone
+ // and find the best. Note that we deliberately do not look at age: the caller can
+ // rate-limit so age is not a strong indicator of confidence. Instead, the callers are
+ // expected to withdraw suggestions they no longer have confidence in.
+ for (int i = 0; i < mSuggestionByPhoneId.size(); i++) {
+ LinkedList<QualifiedPhoneTimeZoneSuggestion> phoneSuggestions =
+ mSuggestionByPhoneId.valueAt(i);
+ if (phoneSuggestions == null) {
+ // Unexpected
+ continue;
+ }
+ QualifiedPhoneTimeZoneSuggestion candidateSuggestion = phoneSuggestions.getFirst();
+ if (candidateSuggestion == null) {
+ // Unexpected
+ continue;
+ }
+
+ if (bestSuggestion == null) {
+ bestSuggestion = candidateSuggestion;
+ } else if (candidateSuggestion.score > bestSuggestion.score) {
+ bestSuggestion = candidateSuggestion;
+ } else if (candidateSuggestion.score == bestSuggestion.score) {
+ // Tie! Use the suggestion with the lowest phoneId.
+ int candidatePhoneId = candidateSuggestion.suggestion.getPhoneId();
+ int bestPhoneId = bestSuggestion.suggestion.getPhoneId();
+ if (candidatePhoneId < bestPhoneId) {
+ bestSuggestion = candidateSuggestion;
+ }
+ }
+ }
+ return bestSuggestion;
+ }
+
+ /**
+ * Returns the current best suggestion. Not intended for general use: it is used during tests
+ * to check service behavior.
+ */
+ @VisibleForTesting
+ @Nullable
+ public synchronized QualifiedPhoneTimeZoneSuggestion findBestSuggestionForTests() {
+ return findBestSuggestion();
+ }
+
+ private synchronized void handleAutoTimeZoneEnabled() {
+ if (DBG) {
+ Log.d(LOG_TAG, "handleAutoTimeEnabled() called");
+ }
+ // When the user enabled time zone detection, run the time zone detection and change the
+ // device time zone if possible.
+ doTimeZoneDetection();
+ }
+
+ /**
+ * Dumps any logs held to the supplied writer.
+ */
+ public void dumpLogs(IndentingPrintWriter ipw) {
+ mTimeZoneDetectionServiceHelper.dumpLogs(ipw);
+ }
+
+ /**
+ * Dumps internal state such as field values.
+ */
+ public void dumpState(PrintWriter pw) {
+ pw.println(" TimeZoneDetectionService.mCurrentSuggestion=" + mCurrentSuggestion);
+ pw.println(" TimeZoneDetectionService.mSuggestionsByPhoneId=" + mSuggestionByPhoneId);
+ mTimeZoneDetectionServiceHelper.dumpState(pw);
+ pw.flush();
+ }
+
+ /**
+ * A method used to inspect service state during tests. Not intended for general use.
+ */
+ @VisibleForTesting
+ public synchronized QualifiedPhoneTimeZoneSuggestion getLatestPhoneSuggestion(int phoneId) {
+ LinkedList<QualifiedPhoneTimeZoneSuggestion> suggestions =
+ mSuggestionByPhoneId.get(phoneId);
+ if (suggestions == null) {
+ return null;
+ }
+ return suggestions.getFirst();
+ }
+
+ /**
+ * A {@link PhoneTimeZoneSuggestion} with additional qualifying metadata.
+ */
+ @VisibleForTesting
+ public static class QualifiedPhoneTimeZoneSuggestion {
+
+ @VisibleForTesting
+ public final PhoneTimeZoneSuggestion suggestion;
+
+ /**
+ * The score the suggestion has been given. This can be used to rank against other
+ * suggestions of the same type.
+ */
+ @VisibleForTesting
+ public final int score;
+
+ @VisibleForTesting
+ public QualifiedPhoneTimeZoneSuggestion(PhoneTimeZoneSuggestion suggestion, int score) {
+ this.suggestion = suggestion;
+ this.score = score;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ QualifiedPhoneTimeZoneSuggestion that = (QualifiedPhoneTimeZoneSuggestion) o;
+ return score == that.score
+ && suggestion.equals(that.suggestion);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(score, suggestion);
+ }
+
+ @Override
+ public String toString() {
+ return "QualifiedPhoneTimeZoneSuggestion{"
+ + "suggestion=" + suggestion
+ + ", score=" + score
+ + '}';
+ }
+ }
+}
diff --git a/src/java/com/android/internal/telephony/nitz/service/TimeZoneDetectionServiceHelperImpl.java b/src/java/com/android/internal/telephony/nitz/service/TimeZoneDetectionServiceHelperImpl.java
new file mode 100644
index 0000000..fc857a7
--- /dev/null
+++ b/src/java/com/android/internal/telephony/nitz/service/TimeZoneDetectionServiceHelperImpl.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright 2017 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.internal.telephony.nitz.service;
+
+import android.annotation.Nullable;
+import android.app.AlarmManager;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.database.ContentObserver;
+import android.os.Handler;
+import android.os.SystemProperties;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.util.LocalLog;
+import android.util.Log;
+
+import com.android.internal.telephony.TelephonyIntents;
+import com.android.internal.util.IndentingPrintWriter;
+
+import java.io.PrintWriter;
+
+/**
+ * The real implementation of {@link TimeZoneDetectionService.Helper}.
+ */
+public final class TimeZoneDetectionServiceHelperImpl implements TimeZoneDetectionService.Helper {
+
+ private static final String LOG_TAG = TimeZoneDetectionService.LOG_TAG;
+ private static final boolean DBG = TimeZoneDetectionService.DBG;
+ private static final String TIMEZONE_PROPERTY = "persist.sys.timezone";
+
+ private final Context mContext;
+ private final ContentResolver mCr;
+ private final LocalLog mTimeZoneLog = new LocalLog(30);
+
+ private Listener mListener;
+
+ /** Creates a TimeServiceHelper */
+ public TimeZoneDetectionServiceHelperImpl(Context context) {
+ mContext = context;
+ mCr = context.getContentResolver();
+ }
+
+ @Override
+ public void setListener(Listener listener) {
+ if (listener == null) {
+ throw new NullPointerException("listener==null");
+ }
+ if (mListener != null) {
+ throw new IllegalStateException("listener already set");
+ }
+ this.mListener = listener;
+ mCr.registerContentObserver(
+ Settings.Global.getUriFor(Settings.Global.AUTO_TIME_ZONE), true,
+ new ContentObserver(new Handler()) {
+ public void onChange(boolean selfChange) {
+ listener.onTimeZoneDetectionChange(isTimeZoneDetectionEnabled());
+ }
+ });
+ }
+
+ @Override
+ public boolean isTimeZoneDetectionEnabled() {
+ try {
+ return Settings.Global.getInt(mCr, Settings.Global.AUTO_TIME_ZONE) > 0;
+ } catch (Settings.SettingNotFoundException snfe) {
+ return true;
+ }
+ }
+
+ @Override
+ public boolean isTimeZoneSettingInitialized() {
+ // timezone.equals("GMT") will be true and only true if the time zone was
+ // set to a default value by the system server (when starting, system server
+ // sets the persist.sys.timezone to "GMT" if it's not set). "GMT" is not used by
+ // any code that sets it explicitly (in case where something sets GMT explicitly,
+ // "Etc/GMT" Olson ID would be used).
+
+ String timeZoneId = getTimeZoneSetting();
+ return timeZoneId != null && timeZoneId.length() > 0 && !timeZoneId.equals("GMT");
+ }
+
+ @Override
+ public void setDeviceTimeZoneFromSuggestion(PhoneTimeZoneSuggestion timeZoneSuggestion) {
+ String currentZoneId = getTimeZoneSetting();
+ String newZoneId = timeZoneSuggestion.getZoneId();
+ if (newZoneId.equals(currentZoneId)) {
+ // No need to set the device time zone - the setting is already what we would be
+ // suggesting.
+ if (DBG) {
+ Log.d(LOG_TAG, "setDeviceTimeZoneAsNeeded: No need to change the time zone;"
+ + " device is already set to the suggested zone."
+ + " timeZoneSuggestion=" + timeZoneSuggestion);
+ }
+ return;
+ }
+
+ String msg = "Changing device time zone. currentZoneId=" + currentZoneId
+ + ", timeZoneSuggestion=" + timeZoneSuggestion;
+ if (DBG) {
+ Log.d(LOG_TAG, msg);
+ }
+ mTimeZoneLog.log(msg);
+
+ AlarmManager alarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
+ alarmManager.setTimeZone(newZoneId);
+ Intent intent = new Intent(TelephonyIntents.ACTION_NETWORK_SET_TIMEZONE);
+ intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
+ intent.putExtra("time-zone", newZoneId);
+ mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
+ }
+
+ @Nullable
+ private String getTimeZoneSetting() {
+ return SystemProperties.get(TIMEZONE_PROPERTY);
+ }
+
+ @Override
+ public void dumpState(PrintWriter pw) {
+ pw.println(" TimeZoneDetectionServiceHelperImpl.getTimeZoneSetting()="
+ + getTimeZoneSetting());
+ }
+
+ @Override
+ public void dumpLogs(IndentingPrintWriter ipw) {
+ ipw.println("TimeZoneDetectionServiceHelperImpl:");
+
+ ipw.increaseIndent();
+ ipw.println("Time zone logs:");
+ ipw.increaseIndent();
+ mTimeZoneLog.dump(ipw);
+ ipw.decreaseIndent();
+
+ ipw.decreaseIndent();
+ }
+}
diff --git a/src/java/com/android/internal/telephony/sip/SipPhone.java b/src/java/com/android/internal/telephony/sip/SipPhone.java
index 82a3287..7fcaf96 100644
--- a/src/java/com/android/internal/telephony/sip/SipPhone.java
+++ b/src/java/com/android/internal/telephony/sip/SipPhone.java
@@ -29,9 +29,9 @@
import android.os.Message;
import android.telephony.DisconnectCause;
import android.telephony.PhoneNumberUtils;
+import android.telephony.Rlog;
import android.telephony.ServiceState;
import android.text.TextUtils;
-import android.telephony.Rlog;
import com.android.internal.telephony.Call;
import com.android.internal.telephony.CallStateException;
@@ -40,6 +40,8 @@
import com.android.internal.telephony.PhoneConstants;
import com.android.internal.telephony.PhoneNotifier;
+import dalvik.annotation.compat.UnsupportedAppUsage;
+
import java.text.ParseException;
import java.util.List;
import java.util.regex.Pattern;
@@ -59,7 +61,9 @@
// A call that is ringing or (call) waiting
private SipCall mRingingCall = new SipCall();
+ @UnsupportedAppUsage
private SipCall mForegroundCall = new SipCall();
+ @UnsupportedAppUsage
private SipCall mBackgroundCall = new SipCall();
private SipManager mSipManager;
@@ -431,6 +435,7 @@
return false;
}
+ @UnsupportedAppUsage
private void log(String s) {
Rlog.d(LOG_TAG, s);
}
@@ -439,6 +444,7 @@
Rlog.d(LOG_TAG, s);
}
+ @UnsupportedAppUsage
private void loge(String s) {
Rlog.e(LOG_TAG, s);
}
@@ -458,6 +464,7 @@
setState(Call.State.IDLE);
}
+ @UnsupportedAppUsage
void switchWith(SipCall that) {
if (SC_DBG) log("switchWith");
synchronized (SipPhone.class) {
@@ -594,6 +601,7 @@
audioGroup.getMode()));
}
+ @UnsupportedAppUsage
void hold() throws CallStateException {
if (SC_DBG) log("hold:");
setState(State.HOLDING);
@@ -601,6 +609,7 @@
setAudioGroupMode();
}
+ @UnsupportedAppUsage
void unhold() throws CallStateException {
if (SC_DBG) log("unhold:");
setState(State.ACTIVE);
diff --git a/src/java/com/android/internal/telephony/test/ModelInterpreter.java b/src/java/com/android/internal/telephony/test/ModelInterpreter.java
index 7930b56..2a046d9 100644
--- a/src/java/com/android/internal/telephony/test/ModelInterpreter.java
+++ b/src/java/com/android/internal/telephony/test/ModelInterpreter.java
@@ -20,6 +20,8 @@
import android.os.Looper;
import android.telephony.Rlog;
+import dalvik.annotation.compat.UnsupportedAppUsage;
+
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
@@ -121,6 +123,7 @@
class InterpreterEx extends Exception
{
+ @UnsupportedAppUsage
public
InterpreterEx (String result)
{
diff --git a/src/java/com/android/internal/telephony/test/SimulatedCommands.java b/src/java/com/android/internal/telephony/test/SimulatedCommands.java
index 965d559..db581b2 100644
--- a/src/java/com/android/internal/telephony/test/SimulatedCommands.java
+++ b/src/java/com/android/internal/telephony/test/SimulatedCommands.java
@@ -69,6 +69,8 @@
import com.android.internal.telephony.uicc.IccIoResult;
import com.android.internal.telephony.uicc.IccSlotStatus;
+import dalvik.annotation.compat.UnsupportedAppUsage;
+
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -113,6 +115,7 @@
//***** Instance Variables
+ @UnsupportedAppUsage
SimulatedGsmCallState simulatedCallState;
HandlerThread mHandlerThread;
SimLockState mSimLockedState;
@@ -153,6 +156,7 @@
int mNextCallFailCause = CallFailCause.NORMAL_CLEARING;
+ @UnsupportedAppUsage
private boolean mDcSuccess = true;
private SetupDataCallResult mSetupDataCallResult;
private boolean mIsRadioPowerFailResponse = false;
@@ -801,6 +805,7 @@
* ar.userObject contains the original value of result.obj
* ar.result is null on success and failure
*/
+ @UnsupportedAppUsage
@Override
public void acceptCall (Message result) {
boolean success;
@@ -1654,6 +1659,7 @@
//***** Private Methods
+ @UnsupportedAppUsage
private void unimplemented(Message result) {
if (result != null) {
AsyncResult.forMessage(result).exception
@@ -1667,6 +1673,7 @@
}
}
+ @UnsupportedAppUsage
private void resultSuccess(Message result, Object ret) {
if (result != null) {
AsyncResult.forMessage(result).result = ret;
@@ -1678,6 +1685,7 @@
}
}
+ @UnsupportedAppUsage
private void resultFail(Message result, Object ret, Throwable tr) {
if (result != null) {
AsyncResult.forMessage(result, ret, tr);
diff --git a/src/java/com/android/internal/telephony/test/SimulatedCommandsVerifier.java b/src/java/com/android/internal/telephony/test/SimulatedCommandsVerifier.java
index 13d8a9b..c550f2c 100644
--- a/src/java/com/android/internal/telephony/test/SimulatedCommandsVerifier.java
+++ b/src/java/com/android/internal/telephony/test/SimulatedCommandsVerifier.java
@@ -32,6 +32,8 @@
import com.android.internal.telephony.cdma.CdmaSmsBroadcastConfigInfo;
import com.android.internal.telephony.gsm.SmsBroadcastConfigInfo;
+import dalvik.annotation.compat.UnsupportedAppUsage;
+
public class SimulatedCommandsVerifier implements CommandsInterface {
private static SimulatedCommandsVerifier sInstance;
@@ -39,6 +41,7 @@
}
+ @UnsupportedAppUsage
public static SimulatedCommandsVerifier getInstance() {
if (sInstance == null) {
sInstance = new SimulatedCommandsVerifier();
@@ -926,6 +929,7 @@
}
+ @UnsupportedAppUsage
@Override
public void setCallForward(int action, int cfReason, int serviceClass, String number,
int timeSeconds, Message response) {
diff --git a/src/java/com/android/internal/telephony/test/SimulatedGsmCallState.java b/src/java/com/android/internal/telephony/test/SimulatedGsmCallState.java
index 75e84c4..72e2d93 100644
--- a/src/java/com/android/internal/telephony/test/SimulatedGsmCallState.java
+++ b/src/java/com/android/internal/telephony/test/SimulatedGsmCallState.java
@@ -16,16 +16,19 @@
package com.android.internal.telephony.test;
+import android.os.Handler;
import android.os.Looper;
import android.os.Message;
-import android.os.Handler;
import android.telephony.PhoneNumberUtils;
+import android.telephony.Rlog;
+
import com.android.internal.telephony.ATParseEx;
import com.android.internal.telephony.DriverCall;
-import java.util.List;
-import java.util.ArrayList;
-import android.telephony.Rlog;
+import dalvik.annotation.compat.UnsupportedAppUsage;
+
+import java.util.ArrayList;
+import java.util.List;
class CallInfo {
enum State {
@@ -388,6 +391,7 @@
return found;
}
+ @UnsupportedAppUsage
public boolean
onChld(char c0, char c1) {
boolean ret;
@@ -444,6 +448,7 @@
return ret;
}
+ @UnsupportedAppUsage
public boolean
releaseHeldOrUDUB() {
boolean found = false;
@@ -474,6 +479,7 @@
}
+ @UnsupportedAppUsage
public boolean
releaseActiveAcceptHeldOrWaiting() {
boolean foundHeld = false;
@@ -529,6 +535,7 @@
return true;
}
+ @UnsupportedAppUsage
public boolean
switchActiveAndHeldOrWaiting() {
boolean hasHeld = false;
@@ -562,6 +569,7 @@
}
+ @UnsupportedAppUsage
public boolean
separateCall(int index) {
try {
@@ -603,6 +611,7 @@
+ @UnsupportedAppUsage
public boolean
conference() {
int countCalls = 0;
diff --git a/src/java/com/android/internal/telephony/uicc/IccCardApplicationStatus.java b/src/java/com/android/internal/telephony/uicc/IccCardApplicationStatus.java
index 76242c4..87bbc69 100644
--- a/src/java/com/android/internal/telephony/uicc/IccCardApplicationStatus.java
+++ b/src/java/com/android/internal/telephony/uicc/IccCardApplicationStatus.java
@@ -28,7 +28,14 @@
* {@hide}
*/
public class IccCardApplicationStatus {
+
+ @UnsupportedAppUsage
+ public IccCardApplicationStatus() {
+ }
+
// TODO: Replace with constants from PhoneConstants.APPTYPE_xxx
+ @UnsupportedAppUsage(implicitMember =
+ "values()[Lcom/android/internal/telephony/uicc/IccCardApplicationStatus$AppType;")
public enum AppType{
@UnsupportedAppUsage
APPTYPE_UNKNOWN,
@@ -44,6 +51,8 @@
APPTYPE_ISIM
}
+ @UnsupportedAppUsage(implicitMember =
+ "values()[Lcom/android/internal/telephony/uicc/IccCardApplicationStatus$AppState;")
public enum AppState{
@UnsupportedAppUsage
APPSTATE_UNKNOWN,
@@ -80,6 +89,8 @@
}
}
+ @UnsupportedAppUsage(implicitMember =
+ "values()[Lcom/android/internal/telephony/uicc/IccCardApplicationStatus$PersoSubState;")
public enum PersoSubState{
@UnsupportedAppUsage
PERSOSUBSTATE_UNKNOWN,
diff --git a/src/java/com/android/internal/telephony/uicc/IccRefreshResponse.java b/src/java/com/android/internal/telephony/uicc/IccRefreshResponse.java
index 7d0f845..ccb6f98 100644
--- a/src/java/com/android/internal/telephony/uicc/IccRefreshResponse.java
+++ b/src/java/com/android/internal/telephony/uicc/IccRefreshResponse.java
@@ -40,6 +40,10 @@
0x30, 0x30, 0x30 */
/* Example: a0000000871002f310ffff89080000ff */
+ @UnsupportedAppUsage
+ public IccRefreshResponse() {
+ }
+
@Override
public String toString() {
return "{" + refreshResult + ", " + aid +", " + efId + "}";
diff --git a/src/java/com/android/internal/telephony/uicc/SIMRecords.java b/src/java/com/android/internal/telephony/uicc/SIMRecords.java
index 6917563..663ebd6 100644
--- a/src/java/com/android/internal/telephony/uicc/SIMRecords.java
+++ b/src/java/com/android/internal/telephony/uicc/SIMRecords.java
@@ -1690,6 +1690,8 @@
/**
* States of Get SPN Finite State Machine which only used by getSpnFsm()
*/
+ @UnsupportedAppUsage(implicitMember =
+ "values()[Lcom/android/internal/telephony/uicc/SIMRecords$GetSpnFsmState;")
private enum GetSpnFsmState {
IDLE, // No initialized
@UnsupportedAppUsage
diff --git a/src/java/com/android/internal/telephony/uicc/UiccController.java b/src/java/com/android/internal/telephony/uicc/UiccController.java
index 0cb8c1c..6112dda 100644
--- a/src/java/com/android/internal/telephony/uicc/UiccController.java
+++ b/src/java/com/android/internal/telephony/uicc/UiccController.java
@@ -830,7 +830,17 @@
boolean isDefaultEuiccCardIdSet = false;
boolean anyEuiccIsActive = false;
mHasActiveBuiltInEuicc = false;
- for (int i = 0; i < status.size(); i++) {
+
+ int numSlots = status.size();
+ if (mUiccSlots.length < numSlots) {
+ String logStr = "The number of the physical slots reported " + numSlots
+ + " is greater than the expectation " + mUiccSlots.length + ".";
+ Rlog.e(LOG_TAG, logStr);
+ sLocalLog.log(logStr);
+ numSlots = mUiccSlots.length;
+ }
+
+ for (int i = 0; i < numSlots; i++) {
IccSlotStatus iss = status.get(i);
boolean isActive = (iss.slotState == IccSlotStatus.SlotState.SLOTSTATE_ACTIVE);
if (isActive) {
@@ -895,7 +905,7 @@
// Note that on HAL<1.2, it's possible that a built-in eUICC exists, but does not
// correspond to any slot in mUiccSlots. This logic is still safe in that case because
// SlotStatus is only for HAL >= 1.2
- for (int i = 0; i < status.size(); i++) {
+ for (int i = 0; i < numSlots; i++) {
if (mUiccSlots[i].isEuicc()) {
String eid = status.get(i).eid;
if (!TextUtils.isEmpty(eid)) {
diff --git a/src/java/com/android/internal/telephony/util/TelephonyUtils.java b/src/java/com/android/internal/telephony/util/TelephonyUtils.java
new file mode 100644
index 0000000..1048e5c
--- /dev/null
+++ b/src/java/com/android/internal/telephony/util/TelephonyUtils.java
@@ -0,0 +1,42 @@
+/*
+ * 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.internal.telephony.util;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.SystemProperties;
+import android.content.pm.ComponentInfo;
+import android.content.pm.ResolveInfo;
+
+/**
+ * This class provides various util functions
+ */
+public final class TelephonyUtils {
+ /** {@hide} */
+ public static String emptyIfNull(@Nullable String str) {
+ return str == null ? "" : str;
+ }
+
+ public static boolean IS_DEBUGGABLE =
+ SystemProperties.getInt("ro.debuggable", 0) == 1;
+
+ public static ComponentInfo getComponentInfo(@NonNull ResolveInfo resolveInfo) {
+ if (resolveInfo.activityInfo != null) return resolveInfo.activityInfo;
+ if (resolveInfo.serviceInfo != null) return resolveInfo.serviceInfo;
+ if (resolveInfo.providerInfo != null) return resolveInfo.providerInfo;
+ throw new IllegalStateException("Missing ComponentInfo!");
+ }
+ }
diff --git a/tests/telephonytests/src/com/android/internal/telephony/NitzStateMachineTestSupport.java b/tests/telephonytests/src/com/android/internal/telephony/NitzStateMachineTestSupport.java
index d023c79..02a0cc9 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/NitzStateMachineTestSupport.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/NitzStateMachineTestSupport.java
@@ -34,6 +34,7 @@
// Values used to when initializing device state but where the value isn't important.
public static final long ARBITRARY_SYSTEM_CLOCK_TIME = createUtcTime(1977, 1, 1, 12, 0, 0);
public static final long ARBITRARY_REALTIME_MILLIS = 123456789L;
+ // This zone isn't used in any of the scenarios below.
public static final String ARBITRARY_TIME_ZONE_ID = "Europe/Paris";
public static final String ARBITRARY_DEBUG_INFO = "Test debug info";
@@ -44,6 +45,7 @@
.setActualTimeUtc(2018, 1, 1, 12, 0, 0)
.setCountryIso("gb")
.buildFrozen();
+
public static final String UNITED_KINGDOM_COUNTRY_DEFAULT_ZONE_ID = "Europe/London";
// The US is a country that has multiple zones, but there is only one matching time zone at the
@@ -62,6 +64,17 @@
.setActualTimeUtc(2018, 1, 1, 12, 0, 0)
.setCountryIso("us")
.buildFrozen();
+
+ // A non-unique US scenario: the offset information is ambiguous between America/Phoenix and
+ // America/Denver during winter.
+ public static final Scenario NON_UNIQUE_US_ZONE_SCENARIO = new Scenario.Builder()
+ .setTimeZone("America/Denver")
+ .setActualTimeUtc(2018, 1, 1, 12, 0, 0)
+ .setCountryIso("us")
+ .buildFrozen();
+ public static final String[] NON_UNIQUE_US_ZONE_SCENARIO_ZONES =
+ { "America/Denver", "America/Phoenix" };
+
public static final String US_COUNTRY_DEFAULT_ZONE_ID = "America/New_York";
// New Zealand is a country with multiple zones, but the default zone has the "boost" modifier
@@ -76,6 +89,7 @@
.setActualTimeUtc(2018, 1, 1, 12, 0, 0)
.setCountryIso("nz")
.buildFrozen();
+
public static final String NEW_ZEALAND_COUNTRY_DEFAULT_ZONE_ID = "Pacific/Auckland";
// A country with a single zone: the zone can be guessed from the country alone. CZ never uses
@@ -85,6 +99,7 @@
.setActualTimeUtc(2018, 1, 1, 12, 0, 0)
.setCountryIso("cz")
.buildFrozen();
+
public static final String CZECHIA_COUNTRY_DEFAULT_ZONE_ID = "Europe/Prague";
/**
diff --git a/tests/telephonytests/src/com/android/internal/telephony/NitzStateMachineTestSupportTest.java b/tests/telephonytests/src/com/android/internal/telephony/NitzStateMachineTestSupportTest.java
index 6f5eed0..4df0adf 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/NitzStateMachineTestSupportTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/NitzStateMachineTestSupportTest.java
@@ -23,6 +23,8 @@
import static com.android.internal.telephony.NitzStateMachineTestSupport.NEW_ZEALAND_COUNTRY_DEFAULT_ZONE_ID;
import static com.android.internal.telephony.NitzStateMachineTestSupport.NEW_ZEALAND_DEFAULT_SCENARIO;
import static com.android.internal.telephony.NitzStateMachineTestSupport.NEW_ZEALAND_OTHER_SCENARIO;
+import static com.android.internal.telephony.NitzStateMachineTestSupport.NON_UNIQUE_US_ZONE_SCENARIO;
+import static com.android.internal.telephony.NitzStateMachineTestSupport.NON_UNIQUE_US_ZONE_SCENARIO_ZONES;
import static com.android.internal.telephony.NitzStateMachineTestSupport.UNIQUE_US_ZONE_SCENARIO1;
import static com.android.internal.telephony.NitzStateMachineTestSupport.UNIQUE_US_ZONE_SCENARIO2;
import static com.android.internal.telephony.NitzStateMachineTestSupport.UNITED_KINGDOM_COUNTRY_DEFAULT_ZONE_ID;
@@ -33,6 +35,8 @@
import static com.android.internal.telephony.TimeZoneLookupHelper.CountryResult.QUALITY_SINGLE_ZONE;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
import com.android.internal.telephony.TimeZoneLookupHelper.CountryResult;
import com.android.internal.telephony.TimeZoneLookupHelper.OffsetResult;
@@ -40,6 +44,9 @@
import org.junit.Before;
import org.junit.Test;
+import java.util.Arrays;
+import java.util.List;
+
public class NitzStateMachineTestSupportTest {
private TimeZoneLookupHelper mTimeZoneLookupHelper;
@@ -87,6 +94,32 @@
}
@Test
+ public void test_nonUniqueUs_assumptions() {
+ // Check we'll get the expected behavior from TimeZoneLookupHelper.
+
+ // quality == QUALITY_MULTIPLE_ZONES_DIFFERENT_OFFSETS, therefore the country's default zone
+ // shouldn't be considered a good match.
+ CountryResult expectedCountryLookupResult = new CountryResult(
+ US_COUNTRY_DEFAULT_ZONE_ID, QUALITY_MULTIPLE_ZONES_DIFFERENT_OFFSETS,
+ ARBITRARY_DEBUG_INFO);
+ CountryResult actualCountryLookupResult =
+ mTimeZoneLookupHelper.lookupByCountry(
+ NON_UNIQUE_US_ZONE_SCENARIO.getNetworkCountryIsoCode(),
+ ARBITRARY_SYSTEM_CLOCK_TIME);
+ assertEquals(expectedCountryLookupResult, actualCountryLookupResult);
+
+ // By definition, there are multiple matching zones for the NON_UNIQUE_US_ZONE_SCENARIO.
+ {
+ OffsetResult actualLookupResult = mTimeZoneLookupHelper.lookupByNitzCountry(
+ NON_UNIQUE_US_ZONE_SCENARIO.createNitzData(),
+ NON_UNIQUE_US_ZONE_SCENARIO.getNetworkCountryIsoCode());
+ List<String> possibleZones = Arrays.asList(NON_UNIQUE_US_ZONE_SCENARIO_ZONES);
+ assertTrue(possibleZones.contains(actualLookupResult.getTimeZone().getID()));
+ assertFalse(actualLookupResult.getIsOnlyMatch());
+ }
+ }
+
+ @Test
public void test_unitedKingdom_assumptions() {
assertEquals(UNITED_KINGDOM_SCENARIO.getTimeZone().getID(),
UNITED_KINGDOM_COUNTRY_DEFAULT_ZONE_ID);
diff --git a/tests/telephonytests/src/com/android/internal/telephony/PhoneSubInfoControllerTest.java b/tests/telephonytests/src/com/android/internal/telephony/PhoneSubInfoControllerTest.java
index ff57227..a69a9a6 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/PhoneSubInfoControllerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/PhoneSubInfoControllerTest.java
@@ -53,6 +53,7 @@
doReturn(0).when(mSubscriptionController).getPhoneId(eq(0));
doReturn(1).when(mSubscriptionController).getPhoneId(eq(1));
doReturn(2).when(mTelephonyManager).getPhoneCount();
+ doReturn(2).when(mTelephonyManager).getActiveModemCount();
doReturn(true).when(mSubscriptionController).isActiveSubId(0, TAG);
doReturn(true).when(mSubscriptionController).isActiveSubId(1, TAG);
doReturn(new int[]{0, 1}).when(mSubscriptionManager)
diff --git a/tests/telephonytests/src/com/android/internal/telephony/PhoneSwitcherTest.java b/tests/telephonytests/src/com/android/internal/telephony/PhoneSwitcherTest.java
index 62da291..fe0e74f 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/PhoneSwitcherTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/PhoneSwitcherTest.java
@@ -1040,6 +1040,7 @@
}
doReturn(numPhones).when(mTelephonyManager).getPhoneCount();
+ doReturn(numPhones).when(mTelephonyManager).getActiveModemCount();
if (numPhones == 1) {
mCommandsInterfaces = new CommandsInterface[] {mCommandsInterface0};
mPhones = new Phone[] {mPhone};
diff --git a/tests/telephonytests/src/com/android/internal/telephony/SubscriptionControllerTest.java b/tests/telephonytests/src/com/android/internal/telephony/SubscriptionControllerTest.java
index 7d3f6e8..ce3281d 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/SubscriptionControllerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/SubscriptionControllerTest.java
@@ -964,6 +964,7 @@
public void testGetEnabledSubscriptionIdDualSIM() {
doReturn(SINGLE_SIM).when(mTelephonyManager).getSimCount();
doReturn(SINGLE_SIM).when(mTelephonyManager).getPhoneCount();
+ doReturn(SINGLE_SIM).when(mTelephonyManager).getActiveModemCount();
// A dual SIM device may have logical slot 0 mapped to physical slot 0
// (i.e. logical slot 1 mapped to physical slot 1)
UiccSlotInfo slot0 = getFakeUiccSlotInfo(true, 0);
@@ -972,6 +973,7 @@
UiccSlot [] uiccSlots = {mUiccSlot, mUiccSlot};
doReturn(2).when(mTelephonyManager).getPhoneCount();
+ doReturn(2).when(mTelephonyManager).getActiveModemCount();
doReturn(uiccSlotInfos).when(mTelephonyManager).getUiccSlotsInfo();
doReturn(uiccSlots).when(mUiccController).getUiccSlots();
assertEquals(2, UiccController.getInstance().getUiccSlots().length);
diff --git a/tests/telephonytests/src/com/android/internal/telephony/SubscriptionInfoUpdaterTest.java b/tests/telephonytests/src/com/android/internal/telephony/SubscriptionInfoUpdaterTest.java
index c1cbe8b..6c34903 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/SubscriptionInfoUpdaterTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/SubscriptionInfoUpdaterTest.java
@@ -120,7 +120,7 @@
replaceInstance(SubscriptionInfoUpdater.class, "sIccId", null, new String[1]);
replaceInstance(SubscriptionInfoUpdater.class, "sContext", null, null);
- replaceInstance(SubscriptionInfoUpdater.class, "PROJECT_SIM_NUM", null, 1);
+ replaceInstance(SubscriptionInfoUpdater.class, "SUPPORTED_MODEM_COUNT", null, 1);
replaceInstance(SubscriptionInfoUpdater.class, "sSimCardState", null, new int[1]);
replaceInstance(SubscriptionInfoUpdater.class, "sSimApplicationState", null, new int[1]);
replaceInstance(SubscriptionInfoUpdater.class, "sIsSubInfoInitialized", null, false);
@@ -132,6 +132,7 @@
doReturn(mUiccSlot).when(mUiccController).getUiccSlotForPhone(anyInt());
doReturn(1).when(mTelephonyManager).getSimCount();
doReturn(1).when(mTelephonyManager).getPhoneCount();
+ doReturn(1).when(mTelephonyManager).getActiveModemCount();
when(mContentProvider.update(any(), any(), any(), isNull())).thenAnswer(
new Answer<Integer>() {
@@ -389,7 +390,7 @@
replaceInstance(PhoneFactory.class, "sPhones", null, new Phone[]{mPhone, mPhone});
replaceInstance(SubscriptionInfoUpdater.class, "sIccId", null,
new String[]{null, null});
- replaceInstance(SubscriptionInfoUpdater.class, "PROJECT_SIM_NUM", null, 2);
+ replaceInstance(SubscriptionInfoUpdater.class, "SUPPORTED_MODEM_COUNT", null, 2);
replaceInstance(SubscriptionInfoUpdater.class, "sSimCardState", null,
new int[]{0, 0});
replaceInstance(SubscriptionInfoUpdater.class, "sSimApplicationState", null,
@@ -400,6 +401,7 @@
doReturn(FAKE_SUB_ID_1).when(mSubscriptionController).getPhoneId(eq(FAKE_SUB_ID_1));
doReturn(FAKE_SUB_ID_2).when(mSubscriptionController).getPhoneId(eq(FAKE_SUB_ID_2));
doReturn(2).when(mTelephonyManager).getPhoneCount();
+ doReturn(2).when(mTelephonyManager).getActiveModemCount();
doReturn(FAKE_MCC_MNC_1).when(mTelephonyManager).getSimOperatorNumeric(eq(FAKE_SUB_ID_1));
doReturn(FAKE_MCC_MNC_2).when(mTelephonyManager).getSimOperatorNumeric(eq(FAKE_SUB_ID_2));
verify(mSubscriptionController, times(0)).clearSubInfo();
diff --git a/tests/telephonytests/src/com/android/internal/telephony/TelephonyPermissionsTest.java b/tests/telephonytests/src/com/android/internal/telephony/TelephonyPermissionsTest.java
index f2d4a17..08defe3 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/TelephonyPermissionsTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/TelephonyPermissionsTest.java
@@ -32,8 +32,11 @@
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
+import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
+import android.os.IBinder;
+import android.os.ServiceManager;
import android.provider.DeviceConfig;
import android.provider.Settings;
import android.telephony.SubscriptionManager;
@@ -50,14 +53,15 @@
import org.mockito.MockitoAnnotations;
import java.lang.reflect.Field;
+import java.util.Map;
@SmallTest
public class TelephonyPermissionsTest {
private static final int SUB_ID = 55555;
private static final int SUB_ID_2 = 22222;
- private static final int PID = 12345;
- private static final int UID = 54321;
+ private static final int PID = Binder.getCallingPid();
+ private static final int UID = Binder.getCallingUid();
private static final String PACKAGE = "com.example";
private static final String MSG = "message";
@@ -70,6 +74,8 @@
@Mock
private ITelephony mMockTelephony;
@Mock
+ private IBinder mMockTelephonyBinder;
+ @Mock
private PackageManager mMockPackageManager;
@Mock
private ApplicationInfo mMockApplicationInfo;
@@ -101,10 +107,13 @@
AppOpsManager.MODE_ERRORED);
when(mMockTelephony.getCarrierPrivilegeStatusForUid(eq(SUB_ID), eq(UID)))
.thenReturn(TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS);
+ when(mMockTelephony.getCarrierPrivilegeStatusForUid(eq(SUB_ID_2), eq(UID)))
+ .thenReturn(TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS);
when(mMockContext.checkPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
PID, UID)).thenReturn(PackageManager.PERMISSION_DENIED);
when(mMockDevicePolicyManager.checkDeviceIdentifierAccess(eq(PACKAGE), eq(PID),
eq(UID))).thenReturn(false);
+ setTelephonyMockAsService();
}
@Test
@@ -243,8 +252,8 @@
public void testCheckReadDeviceIdentifiers_noPermissions() throws Exception {
setupMocksForDeviceIdentifiersErrorPath();
try {
- TelephonyPermissions.checkReadDeviceIdentifiers(mMockContext, () -> mMockTelephony,
- SUB_ID, PID, UID, PACKAGE, MSG);
+ TelephonyPermissions.checkCallingOrSelfReadDeviceIdentifiers(mMockContext,
+ SUB_ID, PACKAGE, MSG);
fail("Should have thrown SecurityException");
} catch (SecurityException e) {
// expected
@@ -256,8 +265,8 @@
when(mMockContext.checkPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
PID, UID)).thenReturn(PackageManager.PERMISSION_GRANTED);
assertTrue(
- TelephonyPermissions.checkReadDeviceIdentifiers(mMockContext, () -> mMockTelephony,
- SUB_ID, PID, UID, PACKAGE, MSG));
+ TelephonyPermissions.checkCallingOrSelfReadDeviceIdentifiers(mMockContext,
+ SUB_ID, PACKAGE, MSG));
}
@Test
@@ -265,8 +274,8 @@
when(mMockTelephony.getCarrierPrivilegeStatusForUid(eq(SUB_ID), eq(UID)))
.thenReturn(TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS);
assertTrue(
- TelephonyPermissions.checkReadDeviceIdentifiers(mMockContext, () -> mMockTelephony,
- SUB_ID, PID, UID, PACKAGE, MSG));
+ TelephonyPermissions.checkCallingOrSelfReadDeviceIdentifiers(mMockContext,
+ SUB_ID, PACKAGE, MSG));
}
@Test
@@ -274,8 +283,8 @@
when(mMockAppOps.noteOpNoThrow(AppOpsManager.OPSTR_READ_DEVICE_IDENTIFIERS, UID,
PACKAGE)).thenReturn(AppOpsManager.MODE_ALLOWED);
assertTrue(
- TelephonyPermissions.checkReadDeviceIdentifiers(mMockContext, () -> mMockTelephony,
- SUB_ID, PID, UID, PACKAGE, MSG));
+ TelephonyPermissions.checkCallingOrSelfReadDeviceIdentifiers(mMockContext,
+ SUB_ID, PACKAGE, MSG));
}
@Test
@@ -283,8 +292,8 @@
when(mMockDevicePolicyManager.checkDeviceIdentifierAccess(eq(PACKAGE), eq(PID),
eq(UID))).thenReturn(true);
assertTrue(
- TelephonyPermissions.checkReadDeviceIdentifiers(mMockContext, () -> mMockTelephony,
- SUB_ID, PID, UID, PACKAGE, MSG));
+ TelephonyPermissions.checkCallingOrSelfReadDeviceIdentifiers(mMockContext,
+ SUB_ID, PACKAGE, MSG));
}
@Test
@@ -296,8 +305,8 @@
UID)).thenReturn(PackageManager.PERMISSION_GRANTED);
setupMocksForDeviceIdentifiersErrorPath();
try {
- TelephonyPermissions.checkReadDeviceIdentifiers(mMockContext, () -> mMockTelephony,
- SUB_ID, PID, UID, PACKAGE, MSG);
+ TelephonyPermissions.checkCallingOrSelfReadDeviceIdentifiers(mMockContext,
+ SUB_ID, PACKAGE, MSG);
fail("Should have thrown SecurityException");
} catch (SecurityException e) {
// expected
@@ -314,8 +323,8 @@
setupMocksForDeviceIdentifiersErrorPath();
mMockApplicationInfo.targetSdkVersion = Build.VERSION_CODES.P;
assertFalse(
- TelephonyPermissions.checkReadDeviceIdentifiers(mMockContext, () -> mMockTelephony,
- SUB_ID, PID, UID, PACKAGE, MSG));
+ TelephonyPermissions.checkCallingOrSelfReadDeviceIdentifiers(mMockContext,
+ SUB_ID, PACKAGE, MSG));
}
@Test
@@ -326,8 +335,8 @@
when(mMockTelephony.getCarrierPrivilegeStatusForUid(eq(SUB_ID_2), eq(UID))).thenReturn(
TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS);
assertTrue(
- TelephonyPermissions.checkReadDeviceIdentifiers(mMockContext, () -> mMockTelephony,
- SUB_ID, PID, UID, PACKAGE, MSG));
+ TelephonyPermissions.checkCallingOrSelfReadDeviceIdentifiers(mMockContext,
+ SUB_ID, PACKAGE, MSG));
}
@Test
@@ -340,8 +349,8 @@
when(mMockTelephony.getCarrierPrivilegeStatusForUid(eq(SUB_ID_2), eq(UID)))
.thenReturn(TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS);
assertTrue(
- TelephonyPermissions.checkReadDeviceIdentifiers(mMockContext, () -> mMockTelephony,
- SUB_ID_2, PID, UID, PACKAGE, MSG));
+ TelephonyPermissions.checkCallingOrSelfReadDeviceIdentifiers(mMockContext,
+ SUB_ID, PACKAGE, MSG));
}
@Test
@@ -354,8 +363,8 @@
when(mMockAppOps.noteOpNoThrow(AppOpsManager.OPSTR_READ_DEVICE_IDENTIFIERS, UID,
PACKAGE)).thenReturn(AppOpsManager.MODE_ALLOWED);
assertTrue(
- TelephonyPermissions.checkReadDeviceIdentifiers(mMockContext, () -> mMockTelephony,
- SUB_ID, PID, UID, PACKAGE, MSG));
+ TelephonyPermissions.checkCallingOrSelfReadDeviceIdentifiers(mMockContext,
+ SUB_ID, PACKAGE, MSG));
}
@Test
@@ -365,14 +374,79 @@
// case.
setupMocksForDeviceIdentifiersErrorPath();
try {
- TelephonyPermissions.checkReadDeviceIdentifiers(mMockContext, () -> mMockTelephony,
- SUB_ID, PID, UID, null, MSG);
+ TelephonyPermissions.checkCallingOrSelfReadDeviceIdentifiers(mMockContext,
+ SUB_ID, null, MSG);
fail("Should have thrown SecurityException");
} catch (SecurityException e) {
// expected
}
}
+ @Test
+ public void testCheckCallingOrSelfReadSubscriberIdentifiers_noPermissions() throws Exception {
+ setupMocksForDeviceIdentifiersErrorPath();
+ setTelephonyMockAsService();
+ when(mMockContext.checkPermission(
+ eq(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE),
+ anyInt(), anyInt())).thenReturn(PackageManager.PERMISSION_DENIED);
+ when(mMockAppOps.noteOpNoThrow(anyString(), anyInt(), eq(PACKAGE))).thenReturn(
+ AppOpsManager.MODE_ERRORED);
+ when(mMockDevicePolicyManager.checkDeviceIdentifierAccess(eq(PACKAGE), anyInt(),
+ anyInt())).thenReturn(false);
+ try {
+ TelephonyPermissions.checkCallingOrSelfReadSubscriberIdentifiers(mMockContext,
+ SUB_ID, PACKAGE, MSG);
+ fail("Should have thrown SecurityException");
+ } catch (SecurityException e) {
+ // expected
+ }
+ }
+
+ @Test
+ public void testCheckCallingOrSelfReadSubscriberIdentifiers_carrierPrivileges()
+ throws Exception {
+ setTelephonyMockAsService();
+ when(mMockContext.checkPermission(
+ eq(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE),
+ anyInt(), anyInt())).thenReturn(PackageManager.PERMISSION_DENIED);
+ when(mMockTelephony.getCarrierPrivilegeStatusForUid(eq(SUB_ID), anyInt()))
+ .thenReturn(TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS);
+ assertTrue(
+ TelephonyPermissions.checkCallingOrSelfReadSubscriberIdentifiers(mMockContext,
+ SUB_ID, PACKAGE, MSG));
+ }
+
+ @Test
+ public void testCheckCallingOrSelfReadSubscriberIdentifiers_carrierPrivilegesOnOtherSub()
+ throws Exception {
+ setupMocksForDeviceIdentifiersErrorPath();
+ setTelephonyMockAsService();
+ when(mMockContext.checkPermission(
+ eq(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE),
+ anyInt(), anyInt())).thenReturn(PackageManager.PERMISSION_DENIED);
+ when(mMockSubscriptionManager.getActiveSubscriptionIdList(anyBoolean())).thenReturn(
+ new int[]{SUB_ID, SUB_ID_2});
+ when(mMockTelephony.getCarrierPrivilegeStatusForUid(eq(SUB_ID_2), anyInt())).thenReturn(
+ TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS);
+ // Carrier privilege on the other active sub shouldn't allow access to this sub.
+ try {
+ TelephonyPermissions.checkCallingOrSelfReadSubscriberIdentifiers(mMockContext,
+ SUB_ID, PACKAGE, MSG);
+ fail("Should have thrown SecurityException");
+ } catch (SecurityException e) {
+ // expected
+ }
+ }
+
+ // Put mMockTelephony into service cache so that TELEPHONY_SUPPLIER will get it.
+ private void setTelephonyMockAsService() throws Exception {
+ when(mMockTelephonyBinder.queryLocalInterface(anyString())).thenReturn(mMockTelephony);
+ Field field = ServiceManager.class.getDeclaredField("sCache");
+ field.setAccessible(true);
+ ((Map<String, IBinder>) field.get(null)).put(Context.TELEPHONY_SERVICE,
+ mMockTelephonyBinder);
+ }
+
public static class FakeSettingsConfigProvider extends FakeSettingsProvider {
private static final String PROPERTY_DEVICE_IDENTIFIER_ACCESS_RESTRICTIONS_DISABLED =
DeviceConfig.NAMESPACE_PRIVACY + "/"
diff --git a/tests/telephonytests/src/com/android/internal/telephony/nitz/NewNitzStateMachineImplTest.java b/tests/telephonytests/src/com/android/internal/telephony/nitz/NewNitzStateMachineImplTest.java
new file mode 100644
index 0000000..d422687
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/nitz/NewNitzStateMachineImplTest.java
@@ -0,0 +1,628 @@
+/*
+ * Copyright 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.internal.telephony.nitz;
+
+import static com.android.internal.telephony.NitzStateMachineTestSupport.ARBITRARY_SYSTEM_CLOCK_TIME;
+import static com.android.internal.telephony.NitzStateMachineTestSupport.UNIQUE_US_ZONE_SCENARIO1;
+import static com.android.internal.telephony.NitzStateMachineTestSupport.UNITED_KINGDOM_SCENARIO;
+import static com.android.internal.telephony.NitzStateMachineTestSupport.createTimeSuggestionFromNitzSignal;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import android.app.timedetector.PhoneTimeSuggestion;
+import android.util.TimestampedValue;
+
+import com.android.internal.telephony.NitzData;
+import com.android.internal.telephony.NitzStateMachineTestSupport.FakeDeviceState;
+import com.android.internal.telephony.NitzStateMachineTestSupport.Scenario;
+import com.android.internal.telephony.TelephonyTest;
+import com.android.internal.telephony.TimeZoneLookupHelper;
+import com.android.internal.telephony.nitz.NewNitzStateMachineImpl.NitzSignalInputFilterPredicate;
+import com.android.internal.telephony.nitz.service.PhoneTimeZoneSuggestion;
+import com.android.internal.util.IndentingPrintWriter;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.PrintWriter;
+import java.util.LinkedList;
+import java.util.concurrent.TimeUnit;
+
+public class NewNitzStateMachineImplTest extends TelephonyTest {
+
+ private static final int PHONE_ID = 99999;
+ private static final PhoneTimeZoneSuggestion EMPTY_TIME_ZONE_SUGGESTION =
+ new PhoneTimeZoneSuggestion(PHONE_ID);
+
+ private FakeNewTimeServiceHelper mFakeNewTimeServiceHelper;
+ private FakeDeviceState mFakeDeviceState;
+ private TimeZoneSuggesterImpl mRealTimeZoneSuggester;
+
+ private NewNitzStateMachineImpl mNitzStateMachineImpl;
+
+
+ @Before
+ public void setUp() throws Exception {
+ TelephonyTest.logd("NewNitzStateMachineImplTest +Setup!");
+ super.setUp("NewNitzStateMachineImplTest");
+
+ // In tests we use a fake impls for NewTimeServiceHelper and DeviceState.
+ mFakeDeviceState = new FakeDeviceState();
+ mFakeNewTimeServiceHelper = new FakeNewTimeServiceHelper(mFakeDeviceState);
+
+ // In tests we disable NITZ signal input filtering. The real NITZ signal filter is tested
+ // independently. This makes constructing test data simpler: we can be sure the signals
+ // won't be filtered for reasons like rate-limiting.
+ NitzSignalInputFilterPredicate mFakeNitzSignalInputFilter = (oldSignal, newSignal) -> true;
+
+ // In tests a real TimeZoneSuggesterImpl is used with the real TimeZoneLookupHelper and real
+ // country time zone data. A fake device state is used (which allows tests to fake the
+ // system clock / user settings). The tests can perform the expected lookups and confirm the
+ // state machine takes the correct action. Picking real examples from the past is easier
+ // than inventing countries / scenarios and configuring fakes.
+ TimeZoneLookupHelper timeZoneLookupHelper = new TimeZoneLookupHelper();
+ mRealTimeZoneSuggester = new TimeZoneSuggesterImpl(mFakeDeviceState, timeZoneLookupHelper);
+
+ mNitzStateMachineImpl = new NewNitzStateMachineImpl(
+ PHONE_ID, mFakeNitzSignalInputFilter, mRealTimeZoneSuggester,
+ mFakeNewTimeServiceHelper, mFakeDeviceState);
+
+ TelephonyTest.logd("NewNitzStateMachineImplTest -Setup!");
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ super.tearDown();
+ }
+
+ @Test
+ public void test_countryThenNitz() throws Exception {
+ Scenario scenario = UNIQUE_US_ZONE_SCENARIO1;
+ String networkCountryIsoCode = scenario.getNetworkCountryIsoCode();
+ TimestampedValue<NitzData> nitzSignal =
+ scenario.createNitzSignal(mFakeDeviceState.elapsedRealtime());
+
+ // Capture expected results from the real suggester and confirm we can tell the difference
+ // between them.
+ PhoneTimeZoneSuggestion expectedTimeZoneSuggestion1 =
+ mRealTimeZoneSuggester.getTimeZoneSuggestion(
+ PHONE_ID, networkCountryIsoCode, null /* nitzSignal */);
+ PhoneTimeZoneSuggestion expectedTimeZoneSuggestion2 =
+ mRealTimeZoneSuggester.getTimeZoneSuggestion(
+ PHONE_ID, networkCountryIsoCode, nitzSignal);
+ assertNotNull(expectedTimeZoneSuggestion2);
+ assertNotEquals(expectedTimeZoneSuggestion1, expectedTimeZoneSuggestion2);
+
+ Script script = new Script()
+ .initializeSystemClock(ARBITRARY_SYSTEM_CLOCK_TIME)
+ .networkAvailable();
+
+ // Simulate country being known.
+ script.countryReceived(networkCountryIsoCode);
+
+ script.verifyOnlyTimeZoneWasSuggestedAndReset(expectedTimeZoneSuggestion1);
+
+ // Check NitzStateMachine exposed state.
+ assertNull(mNitzStateMachineImpl.getCachedNitzData());
+
+ // Simulate NITZ being received and verify the behavior.
+ script.nitzReceived(nitzSignal);
+
+ PhoneTimeSuggestion expectedTimeSuggestion =
+ createTimeSuggestionFromNitzSignal(PHONE_ID, nitzSignal);
+ script.verifyTimeAndTimeZoneSuggestedAndReset(
+ expectedTimeSuggestion, expectedTimeZoneSuggestion2);
+
+ // Check NitzStateMachine exposed state.
+ assertEquals(nitzSignal.getValue(), mNitzStateMachineImpl.getCachedNitzData());
+ }
+
+ @Test
+ public void test_nitzThenCountry() throws Exception {
+ Scenario scenario = UNIQUE_US_ZONE_SCENARIO1;
+ TimestampedValue<NitzData> nitzSignal =
+ scenario.createNitzSignal(mFakeDeviceState.elapsedRealtime());
+
+ String networkCountryIsoCode = scenario.getNetworkCountryIsoCode();
+
+ // Capture test expectations from the real suggester and confirm we can tell the difference
+ // between them.
+ PhoneTimeZoneSuggestion expectedTimeZoneSuggestion1 =
+ mRealTimeZoneSuggester.getTimeZoneSuggestion(
+ PHONE_ID, null /* countryIsoCode */, nitzSignal);
+ PhoneTimeZoneSuggestion expectedTimeZoneSuggestion2 =
+ mRealTimeZoneSuggester.getTimeZoneSuggestion(
+ PHONE_ID, networkCountryIsoCode, nitzSignal);
+ assertNotEquals(expectedTimeZoneSuggestion1, expectedTimeZoneSuggestion2);
+
+ Script script = new Script()
+ .initializeSystemClock(ARBITRARY_SYSTEM_CLOCK_TIME)
+ .networkAvailable();
+
+ // Simulate receiving the NITZ signal.
+ script.nitzReceived(nitzSignal);
+
+ // Verify the state machine did the right thing.
+ PhoneTimeSuggestion expectedTimeSuggestion =
+ createTimeSuggestionFromNitzSignal(PHONE_ID, nitzSignal);
+ script.verifyTimeAndTimeZoneSuggestedAndReset(
+ expectedTimeSuggestion, expectedTimeZoneSuggestion1);
+
+ // Check NitzStateMachine exposed state.
+ assertEquals(nitzSignal.getValue(), mNitzStateMachineImpl.getCachedNitzData());
+
+ // Simulate country being known and verify the behavior.
+ script.countryReceived(networkCountryIsoCode)
+ .verifyOnlyTimeZoneWasSuggestedAndReset(expectedTimeZoneSuggestion2);
+
+ // Check NitzStateMachine exposed state.
+ assertEquals(nitzSignal.getValue(), mNitzStateMachineImpl.getCachedNitzData());
+ }
+
+ @Test
+ public void test_emptyCountryString_countryReceivedFirst() throws Exception {
+ Scenario scenario = UNIQUE_US_ZONE_SCENARIO1;
+ TimestampedValue<NitzData> nitzSignal =
+ scenario.createNitzSignal(mFakeDeviceState.elapsedRealtime());
+
+ Script script = new Script()
+ .initializeSystemClock(ARBITRARY_SYSTEM_CLOCK_TIME)
+ .networkAvailable();
+
+ // Simulate an empty country being set.
+ script.countryReceived("");
+
+ // Nothing should be set. The country is not valid.
+ script.verifyOnlyTimeZoneWasSuggestedAndReset(EMPTY_TIME_ZONE_SUGGESTION);
+
+ // Check NitzStateMachine exposed state.
+ assertNull(mNitzStateMachineImpl.getCachedNitzData());
+
+ // Simulate receiving the NITZ signal.
+ script.nitzReceived(nitzSignal);
+
+ PhoneTimeSuggestion expectedTimeSuggestion =
+ createTimeSuggestionFromNitzSignal(PHONE_ID, nitzSignal);
+ // Capture output from the real suggester and confirm it meets the test's needs /
+ // expectations.
+ PhoneTimeZoneSuggestion expectedTimeZoneSuggestion =
+ mRealTimeZoneSuggester.getTimeZoneSuggestion(
+ PHONE_ID, "" /* countryIsoCode */, nitzSignal);
+ assertEquals(PhoneTimeZoneSuggestion.TEST_NETWORK_OFFSET_ONLY,
+ expectedTimeZoneSuggestion.getMatchType());
+ assertEquals(PhoneTimeZoneSuggestion.MULTIPLE_ZONES_WITH_SAME_OFFSET,
+ expectedTimeZoneSuggestion.getQuality());
+
+ // Verify the state machine did the right thing.
+ script.verifyTimeAndTimeZoneSuggestedAndReset(
+ expectedTimeSuggestion, expectedTimeZoneSuggestion);
+
+ // Check NitzStateMachine exposed state.
+ assertEquals(nitzSignal.getValue(), mNitzStateMachineImpl.getCachedNitzData());
+ }
+
+ @Test
+ public void test_emptyCountryStringUsTime_nitzReceivedFirst() throws Exception {
+ Scenario scenario = UNIQUE_US_ZONE_SCENARIO1;
+ TimestampedValue<NitzData> nitzSignal =
+ scenario.createNitzSignal(mFakeDeviceState.elapsedRealtime());
+
+ Script script = new Script()
+ .initializeSystemClock(ARBITRARY_SYSTEM_CLOCK_TIME)
+ .networkAvailable();
+
+ // Simulate receiving the NITZ signal.
+ script.nitzReceived(nitzSignal);
+
+ // Verify the state machine did the right thing.
+ // No time zone should be set. A NITZ signal by itself is not enough.
+ PhoneTimeSuggestion expectedTimeSuggestion =
+ createTimeSuggestionFromNitzSignal(PHONE_ID, nitzSignal);
+ script.verifyTimeAndTimeZoneSuggestedAndReset(
+ expectedTimeSuggestion, EMPTY_TIME_ZONE_SUGGESTION);
+
+ // Check NitzStateMachine exposed state.
+ assertEquals(nitzSignal.getValue(), mNitzStateMachineImpl.getCachedNitzData());
+
+ // Simulate an empty country being set.
+ script.countryReceived("");
+
+ // Capture output from the real suggester and confirm it meets the test's needs /
+ // expectations.
+ PhoneTimeZoneSuggestion expectedTimeZoneSuggestion =
+ mRealTimeZoneSuggester.getTimeZoneSuggestion(
+ PHONE_ID, "" /* countryIsoCode */, nitzSignal);
+ assertEquals(PhoneTimeZoneSuggestion.TEST_NETWORK_OFFSET_ONLY,
+ expectedTimeZoneSuggestion.getMatchType());
+ assertEquals(PhoneTimeZoneSuggestion.MULTIPLE_ZONES_WITH_SAME_OFFSET,
+ expectedTimeZoneSuggestion.getQuality());
+
+ // Verify the state machine did the right thing.
+ script.verifyOnlyTimeZoneWasSuggestedAndReset(expectedTimeZoneSuggestion);
+
+ // Check NitzStateMachine exposed state.
+ assertEquals(nitzSignal.getValue(), mNitzStateMachineImpl.getCachedNitzData());
+ }
+
+ @Test
+ public void test_airplaneModeClearsState() throws Exception {
+ Scenario scenario = UNITED_KINGDOM_SCENARIO.mutableCopy();
+ int timeStepMillis = (int) TimeUnit.HOURS.toMillis(3);
+
+ Script script = new Script()
+ .initializeSystemClock(ARBITRARY_SYSTEM_CLOCK_TIME)
+ .networkAvailable();
+
+ // Pre-flight: Simulate a device receiving signals that allow it to detect time and time
+ // zone.
+ TimestampedValue<NitzData> preFlightNitzSignal =
+ scenario.createNitzSignal(mFakeDeviceState.elapsedRealtime());
+ PhoneTimeSuggestion expectedPreFlightTimeSuggestion =
+ createTimeSuggestionFromNitzSignal(PHONE_ID, preFlightNitzSignal);
+ String preFlightCountryIsoCode = scenario.getNetworkCountryIsoCode();
+
+ // Simulate receiving the NITZ signal and country.
+ script.nitzReceived(preFlightNitzSignal)
+ .countryReceived(preFlightCountryIsoCode);
+
+ // Verify the state machine did the right thing.
+ PhoneTimeZoneSuggestion expectedPreFlightTimeZoneSuggestion =
+ mRealTimeZoneSuggester.getTimeZoneSuggestion(
+ PHONE_ID, preFlightCountryIsoCode, preFlightNitzSignal);
+ script.verifyTimeAndTimeZoneSuggestedAndReset(
+ expectedPreFlightTimeSuggestion, expectedPreFlightTimeZoneSuggestion);
+
+ // Check state that NitzStateMachine must expose.
+ assertEquals(preFlightNitzSignal.getValue(), mNitzStateMachineImpl.getCachedNitzData());
+
+ // Boarded flight: Airplane mode turned on / time zone detection still enabled.
+ // The NitzStateMachine must lose all state and stop having an opinion about time zone.
+
+ // Simulate the passage of time and update the device realtime clock.
+ scenario.incrementTime(timeStepMillis);
+ script.incrementTime(timeStepMillis);
+
+ // Simulate airplane mode being turned on.
+ script.toggleAirplaneMode(true);
+
+ // Verify the state machine did the right thing.
+ // Check the time and time zone suggestion was withdrawn.
+ script.verifyOnlyTimeZoneWasSuggestedAndReset(EMPTY_TIME_ZONE_SUGGESTION);
+
+ // Check state that NitzStateMachine must expose.
+ assertNull(mNitzStateMachineImpl.getCachedNitzData());
+
+ // During flight: Airplane mode turned off / time zone detection still enabled.
+ // The NitzStateMachine still must not have an opinion about time zone / hold any state.
+
+ // Simulate the passage of time and update the device realtime clock.
+ scenario.incrementTime(timeStepMillis);
+ script.incrementTime(timeStepMillis);
+
+ // Simulate airplane mode being turned off.
+ script.toggleAirplaneMode(false);
+
+ // Verify the time zone suggestion was withdrawn.
+ script.verifyOnlyTimeZoneWasSuggestedAndReset(EMPTY_TIME_ZONE_SUGGESTION);
+
+ // Check the state that NitzStateMachine must expose.
+ assertNull(mNitzStateMachineImpl.getCachedNitzData());
+
+ // Post flight: Device has moved and receives new signals.
+
+ // Simulate the passage of time and update the device realtime clock.
+ scenario.incrementTime(timeStepMillis);
+ script.incrementTime(timeStepMillis);
+
+ // Simulate the movement to the destination.
+ scenario.changeCountry(UNIQUE_US_ZONE_SCENARIO1.getTimeZoneId(),
+ UNIQUE_US_ZONE_SCENARIO1.getNetworkCountryIsoCode());
+
+ // Simulate the device receiving NITZ signal and country again after the flight. Now the
+ // NitzStateMachine should be opinionated again.
+ TimestampedValue<NitzData> postFlightNitzSignal =
+ scenario.createNitzSignal(mFakeDeviceState.elapsedRealtime());
+ String postFlightCountryCode = scenario.getNetworkCountryIsoCode();
+ script.countryReceived(postFlightCountryCode)
+ .nitzReceived(postFlightNitzSignal);
+
+ // Verify the state machine did the right thing.
+ PhoneTimeSuggestion expectedPostFlightTimeSuggestion =
+ createTimeSuggestionFromNitzSignal(PHONE_ID, postFlightNitzSignal);
+ PhoneTimeZoneSuggestion expectedPostFlightTimeZoneSuggestion =
+ mRealTimeZoneSuggester.getTimeZoneSuggestion(
+ PHONE_ID, postFlightCountryCode, postFlightNitzSignal);
+ script.verifyTimeAndTimeZoneSuggestedAndReset(
+ expectedPostFlightTimeSuggestion, expectedPostFlightTimeZoneSuggestion);
+
+ // Check state that NitzStateMachine must expose.
+ assertEquals(postFlightNitzSignal.getValue(), mNitzStateMachineImpl.getCachedNitzData());
+ }
+
+ @Test
+ public void test_countryUnavailableClearsTimeZoneSuggestion() throws Exception {
+ Scenario scenario = UNIQUE_US_ZONE_SCENARIO1;
+ TimestampedValue<NitzData> nitzSignal =
+ scenario.createNitzSignal(mFakeDeviceState.elapsedRealtime());
+
+ Script script = new Script()
+ .initializeSystemClock(ARBITRARY_SYSTEM_CLOCK_TIME)
+ .networkAvailable();
+
+ // Simulate receiving the country and verify the state machine does the right thing.
+ script.countryReceived(scenario.getNetworkCountryIsoCode());
+ PhoneTimeZoneSuggestion expectedTimeZoneSuggestion1 =
+ mRealTimeZoneSuggester.getTimeZoneSuggestion(
+ PHONE_ID, scenario.getNetworkCountryIsoCode(), null /* nitzSignal */);
+ script.verifyOnlyTimeZoneWasSuggestedAndReset(expectedTimeZoneSuggestion1);
+
+ // Simulate receiving an NITZ signal and verify the state machine does the right thing.
+ script.nitzReceived(nitzSignal);
+ PhoneTimeSuggestion expectedTimeSuggestion =
+ createTimeSuggestionFromNitzSignal(PHONE_ID, nitzSignal);
+ PhoneTimeZoneSuggestion expectedTimeZoneSuggestion2 =
+ mRealTimeZoneSuggester.getTimeZoneSuggestion(
+ PHONE_ID, scenario.getNetworkCountryIsoCode(), nitzSignal);
+ script.verifyTimeAndTimeZoneSuggestedAndReset(
+ expectedTimeSuggestion, expectedTimeZoneSuggestion2);
+
+ // Check state that NitzStateMachine must expose.
+ assertEquals(nitzSignal.getValue(), mNitzStateMachineImpl.getCachedNitzData());
+
+ // Simulate the country becoming unavailable and verify the state machine does the right
+ // thing.
+ script.countryUnavailable();
+ PhoneTimeZoneSuggestion expectedTimeZoneSuggestion3 =
+ mRealTimeZoneSuggester.getTimeZoneSuggestion(
+ PHONE_ID, null /* countryIsoCode */, nitzSignal);
+ script.verifyOnlyTimeZoneWasSuggestedAndReset(expectedTimeZoneSuggestion3);
+
+ // Check state that NitzStateMachine must expose.
+ assertEquals(nitzSignal.getValue(), mNitzStateMachineImpl.getCachedNitzData());
+ }
+
+ @Test
+ public void test_networkAvailableClearsCacheNitzAndTimeZoneSuggestion() throws Exception {
+ Scenario scenario = UNIQUE_US_ZONE_SCENARIO1;
+ TimestampedValue<NitzData> nitzSignal =
+ scenario.createNitzSignal(mFakeDeviceState.elapsedRealtime());
+
+ Script script = new Script()
+ .initializeSystemClock(ARBITRARY_SYSTEM_CLOCK_TIME)
+ .networkAvailable();
+
+ // Simulate receiving the country and verify the state machine does the right thing.
+ script.countryReceived(scenario.getNetworkCountryIsoCode());
+ PhoneTimeZoneSuggestion expectedTimeZoneSuggestion1 =
+ mRealTimeZoneSuggester.getTimeZoneSuggestion(
+ PHONE_ID, scenario.getNetworkCountryIsoCode(), null /* nitzSignal */);
+ script.verifyOnlyTimeZoneWasSuggestedAndReset(expectedTimeZoneSuggestion1);
+
+ // Simulate receiving an NITZ signal and verify the state machine does the right thing.
+ script.nitzReceived(nitzSignal);
+ PhoneTimeSuggestion expectedTimeSuggestion =
+ createTimeSuggestionFromNitzSignal(PHONE_ID, nitzSignal);
+ PhoneTimeZoneSuggestion expectedTimeZoneSuggestion2 =
+ mRealTimeZoneSuggester.getTimeZoneSuggestion(
+ PHONE_ID, scenario.getNetworkCountryIsoCode(), nitzSignal);
+ script.verifyTimeAndTimeZoneSuggestedAndReset(
+ expectedTimeSuggestion, expectedTimeZoneSuggestion2);
+
+ // Check state that NitzStateMachine must expose.
+ assertEquals(nitzSignal.getValue(), mNitzStateMachineImpl.getCachedNitzData());
+
+ // Simulate network becoming available and verify the state machine does the right thing.
+ script.networkAvailable();
+ PhoneTimeZoneSuggestion expectedTimeZoneSuggestion3 =
+ mRealTimeZoneSuggester.getTimeZoneSuggestion(
+ PHONE_ID, scenario.getNetworkCountryIsoCode(), null /* nitzSignal */);
+ script.verifyOnlyTimeZoneWasSuggestedAndReset(expectedTimeZoneSuggestion3);
+
+ // Check state that NitzStateMachine must expose.
+ assertNull(mNitzStateMachineImpl.getCachedNitzData());
+ }
+
+ /**
+ * A "fluent" helper class allowing reuse of logic for test state initialization, simulation of
+ * events, and verification of device state changes with self-describing method names.
+ */
+ private class Script {
+
+ Script() {
+ // Set initial fake device state.
+ mFakeDeviceState.ignoreNitz = false;
+ mFakeDeviceState.nitzUpdateDiffMillis = 2000;
+ mFakeDeviceState.nitzUpdateSpacingMillis = 1000 * 60 * 10;
+
+ mFakeDeviceState.networkCountryIsoForPhone = "";
+ }
+
+ // Initialization methods for setting simulated device state, usually before simulation.
+
+ Script initializeSystemClock(long timeMillis) {
+ mFakeDeviceState.currentTimeMillis = timeMillis;
+ return this;
+ }
+
+ // Simulation methods that are used by tests to pretend that something happens.
+
+ Script incrementTime(int timeIncrementMillis) {
+ mFakeDeviceState.simulateTimeIncrement(timeIncrementMillis);
+ return this;
+ }
+
+ Script networkAvailable() {
+ mNitzStateMachineImpl.handleNetworkAvailable();
+ return this;
+ }
+
+ Script countryUnavailable() {
+ mNitzStateMachineImpl.handleNetworkCountryCodeUnavailable();
+ return this;
+ }
+
+ Script countryReceived(String countryIsoCode) {
+ mFakeDeviceState.networkCountryIsoForPhone = countryIsoCode;
+ mNitzStateMachineImpl.handleNetworkCountryCodeSet(true);
+ return this;
+ }
+
+ Script nitzReceived(TimestampedValue<NitzData> nitzSignal) {
+ mNitzStateMachineImpl.handleNitzReceived(nitzSignal);
+ return this;
+ }
+
+ Script toggleAirplaneMode(boolean on) {
+ mNitzStateMachineImpl.handleAirplaneModeChanged(on);
+ return this;
+ }
+
+ // Verification methods.
+
+ Script verifyOnlyTimeZoneWasSuggestedAndReset(PhoneTimeZoneSuggestion timeZoneSuggestion) {
+ justVerifyTimeZoneWasSuggested(timeZoneSuggestion);
+ justVerifyTimeWasNotSuggested();
+ commitStateChanges();
+ return this;
+ }
+
+ Script verifyTimeAndTimeZoneSuggestedAndReset(
+ PhoneTimeSuggestion timeSuggestion, PhoneTimeZoneSuggestion timeZoneSuggestion) {
+ justVerifyTimeZoneWasSuggested(timeZoneSuggestion);
+ justVerifyTimeWasSuggested(timeSuggestion);
+ commitStateChanges();
+ return this;
+ }
+
+ private void justVerifyTimeWasNotSuggested() {
+ mFakeNewTimeServiceHelper.suggestedTimes.assertHasNotBeenSet();
+ }
+
+ private void justVerifyTimeZoneWasSuggested(PhoneTimeZoneSuggestion timeZoneSuggestion) {
+ mFakeNewTimeServiceHelper.suggestedTimeZones.assertHasBeenSet();
+ mFakeNewTimeServiceHelper.suggestedTimeZones.assertLatestEquals(timeZoneSuggestion);
+ }
+
+ private void justVerifyTimeWasSuggested(PhoneTimeSuggestion timeSuggestion) {
+ mFakeNewTimeServiceHelper.suggestedTimes.assertChangeCount(1);
+ mFakeNewTimeServiceHelper.suggestedTimes.assertLatestEquals(timeSuggestion);
+ }
+
+ private void commitStateChanges() {
+ mFakeNewTimeServiceHelper.commitState();
+ }
+ }
+
+ /** Some piece of state that tests want to track. */
+ private static class TestState<T> {
+ private T mInitialValue;
+ private LinkedList<T> mValues = new LinkedList<>();
+
+ void init(T value) {
+ mValues.clear();
+ mInitialValue = value;
+ }
+
+ void set(T value) {
+ mValues.addFirst(value);
+ }
+
+ boolean hasBeenSet() {
+ return mValues.size() > 0;
+ }
+
+ void assertHasNotBeenSet() {
+ assertFalse(hasBeenSet());
+ }
+
+ void assertHasBeenSet() {
+ assertTrue(hasBeenSet());
+ }
+
+ void commitLatest() {
+ if (hasBeenSet()) {
+ mInitialValue = mValues.getLast();
+ mValues.clear();
+ }
+ }
+
+ void assertLatestEquals(T expected) {
+ assertEquals(expected, getLatest());
+ }
+
+ void assertChangeCount(int expectedCount) {
+ assertEquals(expectedCount, mValues.size());
+ }
+
+ public T getLatest() {
+ if (hasBeenSet()) {
+ return mValues.getFirst();
+ }
+ return mInitialValue;
+ }
+ }
+
+ /**
+ * A fake implementation of {@link NewTimeServiceHelper} that enables tests to detect what
+ * {@link NewNitzStateMachineImpl} would do to a real device's state.
+ */
+ private static class FakeNewTimeServiceHelper implements NewTimeServiceHelper {
+
+ private final FakeDeviceState mFakeDeviceState;
+
+ // State we want to track.
+ public final TestState<PhoneTimeSuggestion> suggestedTimes = new TestState<>();
+ public final TestState<PhoneTimeZoneSuggestion> suggestedTimeZones = new TestState<>();
+
+ FakeNewTimeServiceHelper(FakeDeviceState fakeDeviceState) {
+ mFakeDeviceState = fakeDeviceState;
+ }
+
+ @Override
+ public void suggestDeviceTime(PhoneTimeSuggestion timeSuggestion) {
+ suggestedTimes.set(timeSuggestion);
+ // The fake time service just uses the latest suggestion.
+ mFakeDeviceState.currentTimeMillis = timeSuggestion.getUtcTime().getValue();
+ }
+
+ @Override
+ public void maybeSuggestDeviceTimeZone(PhoneTimeZoneSuggestion timeZoneSuggestion) {
+ suggestedTimeZones.set(timeZoneSuggestion);
+ }
+
+ @Override
+ public void dumpLogs(IndentingPrintWriter ipw) {
+ // No-op in tests
+ }
+
+ @Override
+ public void dumpState(PrintWriter pw) {
+ // No-op in tests
+ }
+
+ void commitState() {
+ suggestedTimeZones.commitLatest();
+ suggestedTimes.commitLatest();
+ }
+ }
+}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/nitz/NitzSignalInputFilterPredicateFactoryTest.java b/tests/telephonytests/src/com/android/internal/telephony/nitz/NitzSignalInputFilterPredicateFactoryTest.java
new file mode 100644
index 0000000..3e69d23
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/nitz/NitzSignalInputFilterPredicateFactoryTest.java
@@ -0,0 +1,333 @@
+/*
+ * Copyright 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.internal.telephony.nitz;
+
+import static com.android.internal.telephony.NitzStateMachineTestSupport.UNIQUE_US_ZONE_SCENARIO1;
+import static com.android.internal.telephony.nitz.NitzSignalInputFilterPredicateFactory.createBogusElapsedRealtimeCheck;
+import static com.android.internal.telephony.nitz.NitzSignalInputFilterPredicateFactory.createIgnoreNitzPropertyCheck;
+import static com.android.internal.telephony.nitz.NitzSignalInputFilterPredicateFactory.createRateLimitCheck;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.util.TimestampedValue;
+
+import com.android.internal.telephony.NitzData;
+import com.android.internal.telephony.NitzStateMachineTestSupport.FakeDeviceState;
+import com.android.internal.telephony.NitzStateMachineTestSupport.Scenario;
+import com.android.internal.telephony.TelephonyTest;
+import com.android.internal.telephony.nitz.NitzSignalInputFilterPredicateFactory.NitzSignalInputFilterPredicateImpl;
+import com.android.internal.telephony.nitz.NitzSignalInputFilterPredicateFactory.TrivalentPredicate;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+public class NitzSignalInputFilterPredicateFactoryTest extends TelephonyTest {
+
+ private FakeDeviceState mFakeDeviceState;
+
+ @Before
+ public void setUp() throws Exception {
+ super.setUp("NitzSignalInputFilterPredicateFactoryTest");
+ mFakeDeviceState = new FakeDeviceState();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ super.tearDown();
+ }
+
+ @Test
+ public void testNitzSignalInputFilterPredicateImpl_nullSecondArgumentRejected() {
+ Scenario scenario = UNIQUE_US_ZONE_SCENARIO1;
+ TimestampedValue<NitzData> nitzSignal =
+ scenario.createNitzSignal(mFakeDeviceState.elapsedRealtime());
+ TrivalentPredicate[] triPredicates = {};
+ NitzSignalInputFilterPredicateImpl impl =
+ new NitzSignalInputFilterPredicateImpl(triPredicates);
+ try {
+ impl.mustProcessNitzSignal(nitzSignal, null);
+ fail();
+ } catch (NullPointerException expected) {
+ }
+ }
+
+ @Test
+ public void testNitzSignalInputFilterPredicateImpl_defaultIsTrue() {
+ Scenario scenario = UNIQUE_US_ZONE_SCENARIO1;
+ TimestampedValue<NitzData> nitzSignal = scenario
+ .createNitzSignal(mFakeDeviceState.elapsedRealtime());
+ NitzSignalInputFilterPredicateImpl impl =
+ new NitzSignalInputFilterPredicateImpl(new TrivalentPredicate[0]);
+ assertTrue(impl.mustProcessNitzSignal(null, nitzSignal));
+ }
+
+ @Test
+ public void testNitzSignalInputFilterPredicateImpl_nullIsIgnored() {
+ Scenario scenario = UNIQUE_US_ZONE_SCENARIO1;
+ TimestampedValue<NitzData> nitzSignal =
+ scenario.createNitzSignal(mFakeDeviceState.elapsedRealtime());
+ TrivalentPredicate nullPredicate = (x, y) -> null;
+ TrivalentPredicate[] triPredicates = { nullPredicate };
+ NitzSignalInputFilterPredicateImpl impl =
+ new NitzSignalInputFilterPredicateImpl(triPredicates);
+ assertTrue(impl.mustProcessNitzSignal(null, nitzSignal));
+ }
+
+ @Test
+ public void testNitzSignalInputFilterPredicateImpl_trueIsHonored() {
+ Scenario scenario = UNIQUE_US_ZONE_SCENARIO1;
+ TimestampedValue<NitzData> nitzSignal =
+ scenario.createNitzSignal(mFakeDeviceState.elapsedRealtime());
+ TrivalentPredicate nullPredicate = (x, y) -> null;
+ TrivalentPredicate truePredicate = (x, y) -> true;
+ TrivalentPredicate exceptionPredicate = (x, y) -> {
+ throw new RuntimeException();
+ };
+ TrivalentPredicate[] triPredicates = {
+ nullPredicate,
+ truePredicate,
+ exceptionPredicate,
+ };
+ NitzSignalInputFilterPredicateImpl impl =
+ new NitzSignalInputFilterPredicateImpl(triPredicates);
+ assertTrue(impl.mustProcessNitzSignal(null, nitzSignal));
+ }
+
+ @Test
+ public void testNitzSignalInputFilterPredicateImpl_falseIsHonored() {
+ Scenario scenario = UNIQUE_US_ZONE_SCENARIO1;
+ TimestampedValue<NitzData> nitzSignal =
+ scenario.createNitzSignal(mFakeDeviceState.elapsedRealtime());
+ TrivalentPredicate nullPredicate = (x, y) -> null;
+ TrivalentPredicate falsePredicate = (x, y) -> false;
+ TrivalentPredicate exceptionPredicate = (x, y) -> {
+ throw new RuntimeException();
+ };
+ TrivalentPredicate[] triPredicates = {
+ nullPredicate,
+ falsePredicate,
+ exceptionPredicate,
+ };
+ NitzSignalInputFilterPredicateImpl impl =
+ new NitzSignalInputFilterPredicateImpl(triPredicates);
+ assertFalse(impl.mustProcessNitzSignal(null, nitzSignal));
+ }
+
+ @Test
+ public void testTrivalentPredicate_ignoreNitzPropertyCheck() {
+ TrivalentPredicate triPredicate = createIgnoreNitzPropertyCheck(mFakeDeviceState);
+
+ mFakeDeviceState.ignoreNitz = true;
+ assertFalse(triPredicate.mustProcessNitzSignal(null, null));
+
+ mFakeDeviceState.ignoreNitz = false;
+ assertNull(triPredicate.mustProcessNitzSignal(null, null));
+ }
+
+ @Test
+ public void testTrivalentPredicate_bogusElapsedRealtimeCheck() {
+ Scenario scenario = UNIQUE_US_ZONE_SCENARIO1;
+ long elapsedRealtimeClock = mFakeDeviceState.elapsedRealtime();
+ TimestampedValue<NitzData> nitzSignal = scenario.createNitzSignal(elapsedRealtimeClock);
+
+ TrivalentPredicate triPredicate =
+ createBogusElapsedRealtimeCheck(mContext, mFakeDeviceState);
+ assertNull(triPredicate.mustProcessNitzSignal(null, nitzSignal));
+
+ // Any signal that claims to be from the future must be rejected.
+ TimestampedValue<NitzData> bogusNitzSignal = new TimestampedValue<>(
+ elapsedRealtimeClock + 1, nitzSignal.getValue());
+ assertFalse(triPredicate.mustProcessNitzSignal(null, bogusNitzSignal));
+ }
+
+ @Test
+ public void testTrivalentPredicate_noOldSignalCheck() {
+ Scenario scenario = UNIQUE_US_ZONE_SCENARIO1;
+ TimestampedValue<NitzData> nitzSignal =
+ scenario.createNitzSignal(mFakeDeviceState.elapsedRealtime());
+
+ TrivalentPredicate triPredicate =
+ NitzSignalInputFilterPredicateFactory.createNoOldSignalCheck();
+ assertTrue(triPredicate.mustProcessNitzSignal(null, nitzSignal));
+ assertNull(triPredicate.mustProcessNitzSignal(nitzSignal, nitzSignal));
+ }
+
+ @Test
+ public void testTrivalentPredicate_rateLimitCheck_elapsedRealtime() {
+ Scenario scenario = UNIQUE_US_ZONE_SCENARIO1;
+ int nitzSpacingThreshold = mFakeDeviceState.getNitzUpdateSpacingMillis();
+ NitzData baseNitzData = scenario.createNitzData();
+
+ TrivalentPredicate triPredicate = createRateLimitCheck(mFakeDeviceState);
+
+ long baseElapsedRealtimeMillis = mFakeDeviceState.elapsedRealtime();
+ TimestampedValue<NitzData> baseSignal =
+ new TimestampedValue<>(baseElapsedRealtimeMillis, baseNitzData);
+
+ // Two identical signals: no spacing so the new signal should not be processed.
+ {
+ assertFalse(triPredicate.mustProcessNitzSignal(baseSignal, baseSignal));
+ }
+
+ // Two signals not spaced apart enough: the new signal should not processed.
+ {
+ int elapsedTimeIncrement = nitzSpacingThreshold - 1;
+ TimestampedValue<NitzData> newSignal =
+ createIncrementedNitzSignal(baseSignal, elapsedTimeIncrement);
+ assertFalse(triPredicate.mustProcessNitzSignal(baseSignal, newSignal));
+ }
+
+ // Two signals spaced apart: the new signal should be processed.
+ {
+ int elapsedTimeIncrement = nitzSpacingThreshold + 1;
+ TimestampedValue<NitzData> newSignal =
+ createIncrementedNitzSignal(baseSignal, elapsedTimeIncrement);
+ assertTrue(triPredicate.mustProcessNitzSignal(baseSignal, newSignal));
+ }
+ }
+
+ @Test
+ public void testTrivalentPredicate_rateLimitCheck_offsetDifference() {
+ Scenario scenario = UNIQUE_US_ZONE_SCENARIO1;
+ int nitzSpacingThreshold = mFakeDeviceState.getNitzUpdateSpacingMillis();
+ NitzData baseNitzData = scenario.createNitzData();
+
+ TrivalentPredicate triPredicate = createRateLimitCheck(mFakeDeviceState);
+
+ long baseElapsedRealtimeMillis = mFakeDeviceState.elapsedRealtime();
+ TimestampedValue<NitzData> baseSignal =
+ new TimestampedValue<>(baseElapsedRealtimeMillis, baseNitzData);
+
+ // Create a new NitzSignal that should be filtered.
+ int elapsedTimeIncrement = nitzSpacingThreshold - 1;
+ TimestampedValue<NitzData> intermediateNitzSignal =
+ createIncrementedNitzSignal(baseSignal, elapsedTimeIncrement);
+ NitzData intermediateNitzData = intermediateNitzSignal.getValue();
+ assertFalse(triPredicate.mustProcessNitzSignal(baseSignal, intermediateNitzSignal));
+
+ // Two signals spaced apart so that the second would be filtered, but they contain different
+ // offset information so should be detected as "different" and processed.
+ {
+ // Modifying the local offset should be enough to recognize the NitzData as different.
+ NitzData differentOffsetNitzData = NitzData.createForTests(
+ intermediateNitzData.getLocalOffsetMillis() + 1,
+ intermediateNitzData.getDstAdjustmentMillis(),
+ intermediateNitzData.getCurrentTimeInMillis(),
+ intermediateNitzData.getEmulatorHostTimeZone());
+ TimestampedValue<NitzData> differentOffsetSignal = new TimestampedValue<>(
+ baseSignal.getReferenceTimeMillis() + elapsedTimeIncrement,
+ differentOffsetNitzData);
+ assertTrue(triPredicate.mustProcessNitzSignal(baseSignal, differentOffsetSignal));
+ }
+ }
+
+ @Test
+ public void testTrivalentPredicate_rateLimitCheck_utcTimeDifferences() {
+ Scenario scenario = UNIQUE_US_ZONE_SCENARIO1;
+ int nitzSpacingThreshold = mFakeDeviceState.getNitzUpdateSpacingMillis();
+ int nitzUtcDiffThreshold = mFakeDeviceState.getNitzUpdateDiffMillis();
+ NitzData baseNitzData = scenario.createNitzData();
+
+ TrivalentPredicate triPredicate = createRateLimitCheck(mFakeDeviceState);
+
+ long baseElapsedRealtimeMillis = mFakeDeviceState.elapsedRealtime();
+ TimestampedValue<NitzData> baseSignal =
+ new TimestampedValue<>(baseElapsedRealtimeMillis, baseNitzData);
+
+ // Create a new NitzSignal that should be filtered.
+ int elapsedTimeIncrement = nitzSpacingThreshold - 1;
+ TimestampedValue<NitzData> intermediateSignal =
+ createIncrementedNitzSignal(baseSignal, elapsedTimeIncrement);
+ NitzData intermediateNitzData = intermediateSignal.getValue();
+ assertFalse(triPredicate.mustProcessNitzSignal(baseSignal, intermediateSignal));
+
+ // Two signals spaced apart so that the second would normally be filtered and it contains
+ // a UTC time that is not sufficiently different.
+ {
+ NitzData incrementedUtcTimeNitzData = NitzData.createForTests(
+ intermediateNitzData.getLocalOffsetMillis(),
+ intermediateNitzData.getDstAdjustmentMillis(),
+ intermediateNitzData.getCurrentTimeInMillis() + nitzUtcDiffThreshold - 1,
+ intermediateNitzData.getEmulatorHostTimeZone());
+
+ TimestampedValue<NitzData> incrementedNitzSignal = new TimestampedValue<>(
+ intermediateSignal.getReferenceTimeMillis(), incrementedUtcTimeNitzData);
+ assertFalse(triPredicate.mustProcessNitzSignal(baseSignal, incrementedNitzSignal));
+ }
+
+ // Two signals spaced apart so that the second would normally be filtered but it contains
+ // a UTC time that is sufficiently different.
+ {
+ NitzData incrementedUtcTimeNitzData = NitzData.createForTests(
+ intermediateNitzData.getLocalOffsetMillis(),
+ intermediateNitzData.getDstAdjustmentMillis(),
+ intermediateNitzData.getCurrentTimeInMillis() + nitzUtcDiffThreshold + 1,
+ intermediateNitzData.getEmulatorHostTimeZone());
+
+ TimestampedValue<NitzData> incrementedNitzSignal = new TimestampedValue<>(
+ intermediateSignal.getReferenceTimeMillis(), incrementedUtcTimeNitzData);
+ assertTrue(triPredicate.mustProcessNitzSignal(baseSignal, incrementedNitzSignal));
+ }
+
+ // Two signals spaced apart so that the second would normally be filtered and it contains
+ // a UTC time that is not sufficiently different.
+ {
+ NitzData decrementedUtcTimeNitzData = NitzData.createForTests(
+ intermediateNitzData.getLocalOffsetMillis(),
+ intermediateNitzData.getDstAdjustmentMillis(),
+ intermediateNitzData.getCurrentTimeInMillis() - nitzUtcDiffThreshold + 1,
+ intermediateNitzData.getEmulatorHostTimeZone());
+
+ TimestampedValue<NitzData> decrementedNitzSignal = new TimestampedValue<>(
+ intermediateSignal.getReferenceTimeMillis(), decrementedUtcTimeNitzData);
+ assertFalse(triPredicate.mustProcessNitzSignal(baseSignal, decrementedNitzSignal));
+ }
+
+ // Two signals spaced apart so that the second would normally be filtered but it contains
+ // a UTC time that is sufficiently different.
+ {
+ NitzData decrementedUtcTimeNitzData = NitzData.createForTests(
+ intermediateNitzData.getLocalOffsetMillis(),
+ intermediateNitzData.getDstAdjustmentMillis(),
+ intermediateNitzData.getCurrentTimeInMillis() + nitzUtcDiffThreshold + 1,
+ intermediateNitzData.getEmulatorHostTimeZone());
+
+ TimestampedValue<NitzData> decrementedNitzSignal = new TimestampedValue<>(
+ intermediateSignal.getReferenceTimeMillis(), decrementedUtcTimeNitzData);
+ assertTrue(triPredicate.mustProcessNitzSignal(baseSignal, decrementedNitzSignal));
+ }
+ }
+
+ /**
+ * Creates an NITZ signal based on the the supplied signal but with all the fields related to
+ * elapsed time incremented by the specified number of milliseconds.
+ */
+ private static TimestampedValue<NitzData> createIncrementedNitzSignal(
+ TimestampedValue<NitzData> baseSignal, int incrementMillis) {
+ NitzData baseData = baseSignal.getValue();
+ return new TimestampedValue<>(baseSignal.getReferenceTimeMillis() + incrementMillis,
+ NitzData.createForTests(
+ baseData.getLocalOffsetMillis(),
+ baseData.getDstAdjustmentMillis(),
+ baseData.getCurrentTimeInMillis() + incrementMillis,
+ baseData.getEmulatorHostTimeZone()));
+ }
+}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/nitz/TimeZoneSuggesterImplTest.java b/tests/telephonytests/src/com/android/internal/telephony/nitz/TimeZoneSuggesterImplTest.java
new file mode 100644
index 0000000..cdd30e4
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/nitz/TimeZoneSuggesterImplTest.java
@@ -0,0 +1,644 @@
+/*
+ * Copyright 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.internal.telephony.nitz;
+
+import static com.android.internal.telephony.NitzStateMachineTestSupport.ARBITRARY_REALTIME_MILLIS;
+import static com.android.internal.telephony.NitzStateMachineTestSupport.CZECHIA_SCENARIO;
+import static com.android.internal.telephony.NitzStateMachineTestSupport.NEW_ZEALAND_COUNTRY_DEFAULT_ZONE_ID;
+import static com.android.internal.telephony.NitzStateMachineTestSupport.NEW_ZEALAND_DEFAULT_SCENARIO;
+import static com.android.internal.telephony.NitzStateMachineTestSupport.NEW_ZEALAND_OTHER_SCENARIO;
+import static com.android.internal.telephony.NitzStateMachineTestSupport.NON_UNIQUE_US_ZONE_SCENARIO;
+import static com.android.internal.telephony.NitzStateMachineTestSupport.NON_UNIQUE_US_ZONE_SCENARIO_ZONES;
+import static com.android.internal.telephony.NitzStateMachineTestSupport.UNIQUE_US_ZONE_SCENARIO1;
+import static com.android.internal.telephony.NitzStateMachineTestSupport.UNIQUE_US_ZONE_SCENARIO2;
+import static com.android.internal.telephony.NitzStateMachineTestSupport.UNITED_KINGDOM_SCENARIO;
+import static com.android.internal.telephony.NitzStateMachineTestSupport.US_COUNTRY_DEFAULT_ZONE_ID;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.util.TimestampedValue;
+
+import com.android.internal.telephony.NitzData;
+import com.android.internal.telephony.NitzStateMachineTestSupport.FakeDeviceState;
+import com.android.internal.telephony.NitzStateMachineTestSupport.Scenario;
+import com.android.internal.telephony.TelephonyTest;
+import com.android.internal.telephony.TimeZoneLookupHelper;
+import com.android.internal.telephony.nitz.NewNitzStateMachineImpl.TimeZoneSuggester;
+import com.android.internal.telephony.nitz.service.PhoneTimeZoneSuggestion;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.List;
+
+public class TimeZoneSuggesterImplTest extends TelephonyTest {
+
+ private static final int PHONE_ID = 99999;
+ private static final PhoneTimeZoneSuggestion EMPTY_TIME_ZONE_SUGGESTION =
+ new PhoneTimeZoneSuggestion(PHONE_ID);
+
+ private FakeDeviceState mFakeDeviceState;
+ private TimeZoneSuggester mTimeZoneSuggester;
+
+ @Before
+ public void setUp() throws Exception {
+ TelephonyTest.logd("TimeZoneSuggesterImplTest +Setup!");
+ super.setUp("TimeZoneSuggesterImplTest");
+
+ // In tests a fake impl is used for DeviceState, which allows historic data to be used.
+ mFakeDeviceState = new FakeDeviceState();
+
+ // In tests the real TimeZoneLookupHelper implementation is used: this makes it easy to
+ // construct tests using known historic examples.
+ TimeZoneLookupHelper timeZoneLookupHelper = new TimeZoneLookupHelper();
+ mTimeZoneSuggester = new TimeZoneSuggesterImpl(mFakeDeviceState, timeZoneLookupHelper);
+
+ TelephonyTest.logd("TimeZoneSuggesterImplTest -Setup!");
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ super.tearDown();
+ }
+
+ @Test
+ public void test_emptySuggestionForNullCountryNullNitz() throws Exception {
+ assertEquals(EMPTY_TIME_ZONE_SUGGESTION,
+ mTimeZoneSuggester.getTimeZoneSuggestion(
+ PHONE_ID, null /* countryIsoCode */, null /* nitzSignal */));
+ }
+
+ @Test
+ public void test_emptySuggestionForNullCountryWithNitz() throws Exception {
+ Scenario scenario = UNIQUE_US_ZONE_SCENARIO1;
+ TimestampedValue<NitzData> nitzSignal =
+ scenario.createNitzSignal(ARBITRARY_REALTIME_MILLIS);
+ assertEquals(EMPTY_TIME_ZONE_SUGGESTION,
+ mTimeZoneSuggester.getTimeZoneSuggestion(
+ PHONE_ID, null /* countryIsoCode */, nitzSignal));
+ }
+
+ @Test
+ public void test_emptySuggestionForEmptyCountryNullNitz() throws Exception {
+ assertEquals(EMPTY_TIME_ZONE_SUGGESTION,
+ mTimeZoneSuggester.getTimeZoneSuggestion(
+ PHONE_ID, "" /* countryIsoCoe */, null /* nitzSignal */));
+ }
+
+ /**
+ * Tests behavior for various scenarios for a user in the US. The US is a complicated case
+ * with multiple time zones, some overlapping and with no good default. The scenario used here
+ * is a "unique" scenario, meaning it is possible to determine the correct zone using both
+ * country and NITZ information.
+ */
+ @Test
+ public void test_uniqueUsZone() throws Exception {
+ Scenario scenario = UNIQUE_US_ZONE_SCENARIO1;
+
+ // Country won't be enough to get a quality result for time zone detection but a suggestion
+ // will be made.
+ {
+ PhoneTimeZoneSuggestion expectedTimeZoneSuggestion =
+ new PhoneTimeZoneSuggestion(PHONE_ID);
+ expectedTimeZoneSuggestion.setZoneId(US_COUNTRY_DEFAULT_ZONE_ID);
+ expectedTimeZoneSuggestion.setMatchType(
+ PhoneTimeZoneSuggestion.NETWORK_COUNTRY_ONLY);
+ expectedTimeZoneSuggestion.setQuality(
+ PhoneTimeZoneSuggestion.MULTIPLE_ZONES_WITH_DIFFERENT_OFFSETS);
+
+ PhoneTimeZoneSuggestion actualSuggestion = mTimeZoneSuggester.getTimeZoneSuggestion(
+ PHONE_ID, scenario.getNetworkCountryIsoCode(), null /* nitzSignal */);
+ assertEquals(expectedTimeZoneSuggestion, actualSuggestion);
+ }
+
+ // NITZ with a "" country code is interpreted as a test network so only offset is used
+ // to get a match.
+ {
+ PhoneTimeZoneSuggestion actualSuggestion = mTimeZoneSuggester.getTimeZoneSuggestion(
+ PHONE_ID, "" /* countryIsoCode */,
+ scenario.createNitzSignal(mFakeDeviceState.elapsedRealtime()));
+ assertEquals(PHONE_ID, actualSuggestion.getPhoneId());
+ assertEquals(PhoneTimeZoneSuggestion.TEST_NETWORK_OFFSET_ONLY,
+ actualSuggestion.getMatchType());
+ assertEquals(PhoneTimeZoneSuggestion.MULTIPLE_ZONES_WITH_SAME_OFFSET,
+ actualSuggestion.getQuality());
+ }
+
+ // NITZ alone is not enough to get a result when the country is not available.
+ {
+ PhoneTimeZoneSuggestion actualSuggestion = mTimeZoneSuggester.getTimeZoneSuggestion(
+ PHONE_ID, null /* countryIsoCode */,
+ scenario.createNitzSignal(mFakeDeviceState.elapsedRealtime()));
+ assertEquals(EMPTY_TIME_ZONE_SUGGESTION, actualSuggestion);
+ }
+
+ // Country + NITZ is enough for a unique time zone detection result for this scenario.
+ {
+ PhoneTimeZoneSuggestion expectedTimeZoneSuggestion =
+ new PhoneTimeZoneSuggestion(PHONE_ID);
+ expectedTimeZoneSuggestion.setZoneId(scenario.getTimeZoneId());
+ expectedTimeZoneSuggestion.setMatchType(
+ PhoneTimeZoneSuggestion.NETWORK_COUNTRY_AND_OFFSET);
+ expectedTimeZoneSuggestion.setQuality(
+ PhoneTimeZoneSuggestion.SINGLE_ZONE);
+ PhoneTimeZoneSuggestion actualSuggestion = mTimeZoneSuggester.getTimeZoneSuggestion(
+ PHONE_ID, scenario.getNetworkCountryIsoCode(),
+ scenario.createNitzSignal(mFakeDeviceState.elapsedRealtime()));
+ assertEquals(expectedTimeZoneSuggestion, actualSuggestion);
+ }
+
+ // Country + NITZ with a bad offset should not trigger fall back, country-only behavior
+ // since there are multiple zones to choose from.
+ {
+ // We use an NITZ from CZ to generate an NITZ signal with a bad offset.
+ TimestampedValue<NitzData> badNitzSignal =
+ CZECHIA_SCENARIO.createNitzSignal(mFakeDeviceState.elapsedRealtime());
+ PhoneTimeZoneSuggestion expectedTimeZoneSuggestion = EMPTY_TIME_ZONE_SUGGESTION;
+ PhoneTimeZoneSuggestion actualSuggestion = mTimeZoneSuggester.getTimeZoneSuggestion(
+ PHONE_ID, scenario.getNetworkCountryIsoCode(),
+ badNitzSignal);
+ assertEquals(expectedTimeZoneSuggestion, actualSuggestion);
+ }
+ }
+
+ /**
+ * Tests behavior for various scenarios for a user in the US. The US is a complicated case
+ * with multiple time zones, some overlapping and with no good default. The scenario used here
+ * is a "non unique" scenario, meaning it is not possible to determine the a single zone using
+ * both country and NITZ information.
+ */
+ @Test
+ public void test_nonUniqueUsZone() throws Exception {
+ Scenario scenario = NON_UNIQUE_US_ZONE_SCENARIO;
+
+ // Country won't be enough to get a quality result for time zone detection but a suggestion
+ // will be made.
+ {
+ PhoneTimeZoneSuggestion expectedTimeZoneSuggestion =
+ new PhoneTimeZoneSuggestion(PHONE_ID);
+ expectedTimeZoneSuggestion.setZoneId(US_COUNTRY_DEFAULT_ZONE_ID);
+ expectedTimeZoneSuggestion.setMatchType(
+ PhoneTimeZoneSuggestion.NETWORK_COUNTRY_ONLY);
+ expectedTimeZoneSuggestion.setQuality(
+ PhoneTimeZoneSuggestion.MULTIPLE_ZONES_WITH_DIFFERENT_OFFSETS);
+
+ PhoneTimeZoneSuggestion actualSuggestion = mTimeZoneSuggester.getTimeZoneSuggestion(
+ PHONE_ID, scenario.getNetworkCountryIsoCode(), null /* nitzSignal */);
+ assertEquals(expectedTimeZoneSuggestion, actualSuggestion);
+ }
+
+ // NITZ with a "" country code is interpreted as a test network so only offset is used
+ // to get a match.
+ {
+ PhoneTimeZoneSuggestion actualSuggestion = mTimeZoneSuggester.getTimeZoneSuggestion(
+ PHONE_ID, "" /* countryIsoCode */,
+ scenario.createNitzSignal(mFakeDeviceState.elapsedRealtime()));
+ assertEquals(PHONE_ID, actualSuggestion.getPhoneId());
+ assertEquals(PhoneTimeZoneSuggestion.TEST_NETWORK_OFFSET_ONLY,
+ actualSuggestion.getMatchType());
+ assertEquals(PhoneTimeZoneSuggestion.MULTIPLE_ZONES_WITH_SAME_OFFSET,
+ actualSuggestion.getQuality());
+ }
+
+ // NITZ alone is not enough to get a result when the country is not available.
+ {
+ PhoneTimeZoneSuggestion actualSuggestion = mTimeZoneSuggester.getTimeZoneSuggestion(
+ PHONE_ID, null /* countryIsoCode */,
+ scenario.createNitzSignal(mFakeDeviceState.elapsedRealtime()));
+ assertEquals(EMPTY_TIME_ZONE_SUGGESTION, actualSuggestion);
+ }
+
+ // Country + NITZ is not enough for a unique time zone detection result for this scenario.
+ {
+ PhoneTimeZoneSuggestion actualSuggestion = mTimeZoneSuggester.getTimeZoneSuggestion(
+ PHONE_ID, scenario.getNetworkCountryIsoCode(),
+ scenario.createNitzSignal(mFakeDeviceState.elapsedRealtime()));
+ assertEquals(PHONE_ID, actualSuggestion.getPhoneId());
+ assertEquals(PhoneTimeZoneSuggestion.NETWORK_COUNTRY_AND_OFFSET,
+ actualSuggestion.getMatchType());
+ assertEquals(PhoneTimeZoneSuggestion.MULTIPLE_ZONES_WITH_SAME_OFFSET,
+ actualSuggestion.getQuality());
+ List<String> allowedZoneIds = Arrays.asList(NON_UNIQUE_US_ZONE_SCENARIO_ZONES);
+ assertTrue(allowedZoneIds.contains(actualSuggestion.getZoneId()));
+ }
+
+ // Country + NITZ with a bad offset should not trigger fall back, country-only behavior
+ // since there are multiple zones to choose from.
+ {
+ // We use an NITZ from CZ to generate an NITZ signal with a bad offset.
+ TimestampedValue<NitzData> badNitzSignal =
+ CZECHIA_SCENARIO.createNitzSignal(mFakeDeviceState.elapsedRealtime());
+ PhoneTimeZoneSuggestion expectedTimeZoneSuggestion = EMPTY_TIME_ZONE_SUGGESTION;
+ PhoneTimeZoneSuggestion actualSuggestion = mTimeZoneSuggester.getTimeZoneSuggestion(
+ PHONE_ID, scenario.getNetworkCountryIsoCode(),
+ badNitzSignal);
+ assertEquals(expectedTimeZoneSuggestion, actualSuggestion);
+ }
+ }
+
+ /**
+ * Tests behavior for various scenarios for a user in the UK. The UK is simple: it has a single
+ * time zone so only the country needs to be known to find a time zone. It is special in that
+ * it uses UTC for some of the year, which makes it difficult to detect bogus NITZ signals with
+ * zero'd offset information.
+ */
+ @Test
+ public void test_unitedKingdom() throws Exception {
+ Scenario scenario = UNITED_KINGDOM_SCENARIO;
+
+ // Country alone is enough to guess the time zone.
+ {
+ PhoneTimeZoneSuggestion expectedTimeZoneSuggestion =
+ new PhoneTimeZoneSuggestion(PHONE_ID);
+ expectedTimeZoneSuggestion.setZoneId(scenario.getTimeZoneId());
+ expectedTimeZoneSuggestion.setMatchType(
+ PhoneTimeZoneSuggestion.NETWORK_COUNTRY_ONLY);
+ expectedTimeZoneSuggestion.setQuality(
+ PhoneTimeZoneSuggestion.SINGLE_ZONE);
+
+ PhoneTimeZoneSuggestion actualSuggestion = mTimeZoneSuggester.getTimeZoneSuggestion(
+ PHONE_ID, scenario.getNetworkCountryIsoCode(), null /* nitzSignal */);
+ assertEquals(expectedTimeZoneSuggestion, actualSuggestion);
+ }
+
+ // NITZ with a "" country code is interpreted as a test network so only offset is used
+ // to get a match.
+ {
+ PhoneTimeZoneSuggestion actualSuggestion = mTimeZoneSuggester.getTimeZoneSuggestion(
+ PHONE_ID, "" /* countryIsoCode */,
+ scenario.createNitzSignal(mFakeDeviceState.elapsedRealtime()));
+ assertEquals(PHONE_ID, actualSuggestion.getPhoneId());
+ assertEquals(PhoneTimeZoneSuggestion.TEST_NETWORK_OFFSET_ONLY,
+ actualSuggestion.getMatchType());
+ assertEquals(PhoneTimeZoneSuggestion.MULTIPLE_ZONES_WITH_SAME_OFFSET,
+ actualSuggestion.getQuality());
+
+ }
+
+ // NITZ alone is not enough to get a result when the country is not available.
+ {
+ PhoneTimeZoneSuggestion actualSuggestion = mTimeZoneSuggester.getTimeZoneSuggestion(
+ PHONE_ID, null /* countryIsoCode */,
+ scenario.createNitzSignal(mFakeDeviceState.elapsedRealtime()));
+ assertEquals(EMPTY_TIME_ZONE_SUGGESTION, actualSuggestion);
+ }
+
+ // Country + NITZ is enough for both time + time zone detection.
+ {
+ PhoneTimeZoneSuggestion expectedTimeZoneSuggestion =
+ new PhoneTimeZoneSuggestion(PHONE_ID);
+ expectedTimeZoneSuggestion.setZoneId(scenario.getTimeZoneId());
+ expectedTimeZoneSuggestion.setMatchType(
+ PhoneTimeZoneSuggestion.NETWORK_COUNTRY_AND_OFFSET);
+ expectedTimeZoneSuggestion.setQuality(
+ PhoneTimeZoneSuggestion.SINGLE_ZONE);
+ PhoneTimeZoneSuggestion actualSuggestion = mTimeZoneSuggester.getTimeZoneSuggestion(
+ PHONE_ID, scenario.getNetworkCountryIsoCode(),
+ scenario.createNitzSignal(mFakeDeviceState.elapsedRealtime()));
+ assertEquals(expectedTimeZoneSuggestion, actualSuggestion);
+ }
+
+ // Country + NITZ with a bad offset should trigger fall back, country-only behavior since
+ // there's only one zone.
+ {
+ // We use an NITZ from Czechia to generate an NITZ signal with a bad offset.
+ TimestampedValue<NitzData> badNitzSignal =
+ CZECHIA_SCENARIO.createNitzSignal(mFakeDeviceState.elapsedRealtime());
+ PhoneTimeZoneSuggestion expectedTimeZoneSuggestion =
+ new PhoneTimeZoneSuggestion(PHONE_ID);
+ expectedTimeZoneSuggestion.setZoneId(scenario.getTimeZoneId());
+ expectedTimeZoneSuggestion.setMatchType(
+ PhoneTimeZoneSuggestion.NETWORK_COUNTRY_ONLY);
+ expectedTimeZoneSuggestion.setQuality(
+ PhoneTimeZoneSuggestion.SINGLE_ZONE);
+ PhoneTimeZoneSuggestion actualSuggestion = mTimeZoneSuggester.getTimeZoneSuggestion(
+ PHONE_ID, scenario.getNetworkCountryIsoCode(),
+ badNitzSignal);
+ assertEquals(expectedTimeZoneSuggestion, actualSuggestion);
+ }
+ }
+
+ /**
+ * Tests behavior for various scenarios for a user in Czechia. CZ is simple: it has a single
+ * time zone so only the country needs to be known to find a time zone. It never uses UTC so it
+ * is useful to contrast with the UK and can be used for bogus signal detection.
+ */
+ @Test
+ public void test_cz() throws Exception {
+ Scenario scenario = CZECHIA_SCENARIO;
+
+ // Country alone is enough to guess the time zone.
+ {
+ PhoneTimeZoneSuggestion expectedTimeZoneSuggestion =
+ new PhoneTimeZoneSuggestion(PHONE_ID);
+ expectedTimeZoneSuggestion.setZoneId(scenario.getTimeZoneId());
+ expectedTimeZoneSuggestion.setMatchType(
+ PhoneTimeZoneSuggestion.NETWORK_COUNTRY_ONLY);
+ expectedTimeZoneSuggestion.setQuality(
+ PhoneTimeZoneSuggestion.SINGLE_ZONE);
+
+ PhoneTimeZoneSuggestion actualSuggestion = mTimeZoneSuggester.getTimeZoneSuggestion(
+ PHONE_ID, scenario.getNetworkCountryIsoCode(), null /* nitzSignal */);
+ assertEquals(expectedTimeZoneSuggestion, actualSuggestion);
+ }
+
+ // NITZ with a "" country code is interpreted as a test network so only offset is used
+ // to get a match.
+ {
+ PhoneTimeZoneSuggestion actualSuggestion =
+ mTimeZoneSuggester.getTimeZoneSuggestion(
+ PHONE_ID, "" /* countryIsoCode */,
+ scenario.createNitzSignal(mFakeDeviceState.elapsedRealtime()));
+ assertEquals(PHONE_ID, actualSuggestion.getPhoneId());
+ assertEquals(PhoneTimeZoneSuggestion.TEST_NETWORK_OFFSET_ONLY,
+ actualSuggestion.getMatchType());
+ assertEquals(PhoneTimeZoneSuggestion.MULTIPLE_ZONES_WITH_SAME_OFFSET,
+ actualSuggestion.getQuality());
+
+ }
+
+ // NITZ alone is not enough to get a result when the country is not available.
+ {
+ PhoneTimeZoneSuggestion actualSuggestion = mTimeZoneSuggester.getTimeZoneSuggestion(
+ PHONE_ID, null /* countryIsoCode */,
+ scenario.createNitzSignal(mFakeDeviceState.elapsedRealtime()));
+ assertEquals(EMPTY_TIME_ZONE_SUGGESTION, actualSuggestion);
+ }
+
+ // Country + NITZ is enough for both time + time zone detection.
+ {
+ PhoneTimeZoneSuggestion expectedTimeZoneSuggestion =
+ new PhoneTimeZoneSuggestion(PHONE_ID);
+ expectedTimeZoneSuggestion.setZoneId(scenario.getTimeZoneId());
+ expectedTimeZoneSuggestion.setMatchType(
+ PhoneTimeZoneSuggestion.NETWORK_COUNTRY_AND_OFFSET);
+ expectedTimeZoneSuggestion.setQuality(
+ PhoneTimeZoneSuggestion.SINGLE_ZONE);
+ PhoneTimeZoneSuggestion actualSuggestion = mTimeZoneSuggester.getTimeZoneSuggestion(
+ PHONE_ID, scenario.getNetworkCountryIsoCode(),
+ scenario.createNitzSignal(mFakeDeviceState.elapsedRealtime()));
+ assertEquals(expectedTimeZoneSuggestion, actualSuggestion);
+ }
+
+ // Country + NITZ with a bad offset should trigger fall back, country-only behavior since
+ // there's only one zone.
+ {
+ // We use an NITZ from the US to generate an NITZ signal with a bad offset.
+ TimestampedValue<NitzData> badNitzSignal =
+ UNIQUE_US_ZONE_SCENARIO1.createNitzSignal(mFakeDeviceState.elapsedRealtime());
+ PhoneTimeZoneSuggestion expectedTimeZoneSuggestion =
+ new PhoneTimeZoneSuggestion(PHONE_ID);
+ expectedTimeZoneSuggestion.setZoneId(scenario.getTimeZoneId());
+ expectedTimeZoneSuggestion.setMatchType(
+ PhoneTimeZoneSuggestion.NETWORK_COUNTRY_ONLY);
+ expectedTimeZoneSuggestion.setQuality(
+ PhoneTimeZoneSuggestion.SINGLE_ZONE);
+ PhoneTimeZoneSuggestion actualSuggestion = mTimeZoneSuggester.getTimeZoneSuggestion(
+ PHONE_ID, scenario.getNetworkCountryIsoCode(),
+ badNitzSignal);
+ assertEquals(expectedTimeZoneSuggestion, actualSuggestion);
+ }
+ }
+
+ @Test
+ public void test_bogusCzNitzSignal() throws Exception {
+ Scenario scenario = CZECHIA_SCENARIO;
+
+ // Country alone is enough to guess the time zone.
+ {
+ PhoneTimeZoneSuggestion expectedTimeZoneSuggestion =
+ new PhoneTimeZoneSuggestion(PHONE_ID);
+ expectedTimeZoneSuggestion.setZoneId(scenario.getTimeZoneId());
+ expectedTimeZoneSuggestion.setMatchType(
+ PhoneTimeZoneSuggestion.NETWORK_COUNTRY_ONLY);
+ expectedTimeZoneSuggestion.setQuality(
+ PhoneTimeZoneSuggestion.SINGLE_ZONE);
+
+ PhoneTimeZoneSuggestion actualSuggestion = mTimeZoneSuggester.getTimeZoneSuggestion(
+ PHONE_ID, scenario.getNetworkCountryIsoCode(), null /* nitzSignal */);
+ assertEquals(expectedTimeZoneSuggestion, actualSuggestion);
+ }
+
+ // NITZ + bogus NITZ is not enough to get a result.
+ {
+ // Create a corrupted NITZ signal, where the offset information has been lost.
+ TimestampedValue<NitzData> goodNitzSignal =
+ scenario.createNitzSignal(mFakeDeviceState.elapsedRealtime());
+ NitzData bogusNitzData = NitzData.createForTests(
+ 0 /* UTC! */, null /* dstOffsetMillis */,
+ goodNitzSignal.getValue().getCurrentTimeInMillis(),
+ null /* emulatorHostTimeZone */);
+ TimestampedValue<NitzData> badNitzSignal = new TimestampedValue<>(
+ goodNitzSignal.getReferenceTimeMillis(), bogusNitzData);
+
+ PhoneTimeZoneSuggestion actualSuggestion = mTimeZoneSuggester.getTimeZoneSuggestion(
+ PHONE_ID, scenario.getNetworkCountryIsoCode(), badNitzSignal);
+ assertEquals(EMPTY_TIME_ZONE_SUGGESTION, actualSuggestion);
+ }
+ }
+
+ @Test
+ public void test_bogusUniqueUsNitzSignal() throws Exception {
+ Scenario scenario = UNIQUE_US_ZONE_SCENARIO1;
+
+ // Country alone is not enough to guess the time zone.
+ {
+ PhoneTimeZoneSuggestion expectedTimeZoneSuggestion =
+ new PhoneTimeZoneSuggestion(PHONE_ID);
+ expectedTimeZoneSuggestion.setZoneId(US_COUNTRY_DEFAULT_ZONE_ID);
+ expectedTimeZoneSuggestion.setMatchType(
+ PhoneTimeZoneSuggestion.NETWORK_COUNTRY_ONLY);
+ expectedTimeZoneSuggestion.setQuality(
+ PhoneTimeZoneSuggestion.MULTIPLE_ZONES_WITH_DIFFERENT_OFFSETS);
+
+ PhoneTimeZoneSuggestion actualSuggestion = mTimeZoneSuggester.getTimeZoneSuggestion(
+ PHONE_ID, scenario.getNetworkCountryIsoCode(), null /* nitzSignal */);
+ assertEquals(expectedTimeZoneSuggestion, actualSuggestion);
+ }
+
+ // NITZ + bogus NITZ is not enough to get a result.
+ {
+ // Create a corrupted NITZ signal, where the offset information has been lost.
+ TimestampedValue<NitzData> goodNitzSignal =
+ scenario.createNitzSignal(mFakeDeviceState.elapsedRealtime());
+ NitzData bogusNitzData = NitzData.createForTests(
+ 0 /* UTC! */, null /* dstOffsetMillis */,
+ goodNitzSignal.getValue().getCurrentTimeInMillis(),
+ null /* emulatorHostTimeZone */);
+ TimestampedValue<NitzData> badNitzSignal = new TimestampedValue<>(
+ goodNitzSignal.getReferenceTimeMillis(), bogusNitzData);
+
+ PhoneTimeZoneSuggestion actualSuggestion = mTimeZoneSuggester.getTimeZoneSuggestion(
+ PHONE_ID, scenario.getNetworkCountryIsoCode(), badNitzSignal);
+ assertEquals(EMPTY_TIME_ZONE_SUGGESTION, actualSuggestion);
+ }
+ }
+
+ @Test
+ public void test_emulatorNitzExtensionUsedForTimeZone() throws Exception {
+ Scenario scenario = UNIQUE_US_ZONE_SCENARIO1;
+
+ TimestampedValue<NitzData> originalNitzSignal =
+ scenario.createNitzSignal(mFakeDeviceState.elapsedRealtime());
+
+ // Create an NITZ signal with an explicit time zone (as can happen on emulators).
+ NitzData originalNitzData = originalNitzSignal.getValue();
+
+ // A time zone that is obviously not in the US, but because the explicit value is present it
+ // should not be questioned.
+ String emulatorTimeZoneId = "Europe/London";
+ NitzData emulatorNitzData = NitzData.createForTests(
+ originalNitzData.getLocalOffsetMillis(),
+ originalNitzData.getDstAdjustmentMillis(),
+ originalNitzData.getCurrentTimeInMillis(),
+ java.util.TimeZone.getTimeZone(emulatorTimeZoneId) /* emulatorHostTimeZone */);
+ TimestampedValue<NitzData> emulatorNitzSignal = new TimestampedValue<>(
+ originalNitzSignal.getReferenceTimeMillis(), emulatorNitzData);
+
+ PhoneTimeZoneSuggestion expectedTimeZoneSuggestion =
+ new PhoneTimeZoneSuggestion(PHONE_ID);
+ expectedTimeZoneSuggestion.setZoneId(emulatorTimeZoneId);
+ expectedTimeZoneSuggestion.setMatchType(PhoneTimeZoneSuggestion.EMULATOR_ZONE_ID);
+ expectedTimeZoneSuggestion.setQuality(PhoneTimeZoneSuggestion.SINGLE_ZONE);
+
+ PhoneTimeZoneSuggestion actualSuggestion = mTimeZoneSuggester.getTimeZoneSuggestion(
+ PHONE_ID, scenario.getNetworkCountryIsoCode(), emulatorNitzSignal);
+ assertEquals(expectedTimeZoneSuggestion, actualSuggestion);
+ }
+
+ @Test
+ public void test_countryDefaultBoost() throws Exception {
+ // Demonstrate the defaultTimeZoneBoost behavior: we can get a zone only from the
+ // countryIsoCode.
+ {
+ Scenario scenario = NEW_ZEALAND_DEFAULT_SCENARIO;
+ PhoneTimeZoneSuggestion expectedSuggestion = new PhoneTimeZoneSuggestion(PHONE_ID);
+ expectedSuggestion.setZoneId(NEW_ZEALAND_COUNTRY_DEFAULT_ZONE_ID);
+ expectedSuggestion.setMatchType(PhoneTimeZoneSuggestion.NETWORK_COUNTRY_ONLY);
+ expectedSuggestion.setQuality(PhoneTimeZoneSuggestion.SINGLE_ZONE);
+ PhoneTimeZoneSuggestion actualSuggestion = mTimeZoneSuggester.getTimeZoneSuggestion(
+ PHONE_ID, scenario.getNetworkCountryIsoCode(), null /* nitzSignal */);
+ assertEquals(expectedSuggestion, actualSuggestion);
+ }
+
+ // Confirm what happens when NITZ is correct for the country default.
+ {
+ Scenario scenario = NEW_ZEALAND_DEFAULT_SCENARIO;
+ TimestampedValue<NitzData> nitzSignal =
+ scenario.createNitzSignal(mFakeDeviceState.elapsedRealtime());
+ PhoneTimeZoneSuggestion expectedSuggestion = new PhoneTimeZoneSuggestion(PHONE_ID);
+ expectedSuggestion.setZoneId(scenario.getTimeZoneId());
+ expectedSuggestion.setMatchType(PhoneTimeZoneSuggestion.NETWORK_COUNTRY_AND_OFFSET);
+ expectedSuggestion.setQuality(PhoneTimeZoneSuggestion.SINGLE_ZONE);
+ PhoneTimeZoneSuggestion actualSuggestion = mTimeZoneSuggester.getTimeZoneSuggestion(
+ PHONE_ID, scenario.getNetworkCountryIsoCode(), nitzSignal);
+ assertEquals(expectedSuggestion, actualSuggestion);
+ }
+
+ // A valid NITZ signal for the non-default zone should still be correctly detected.
+ {
+ Scenario scenario = NEW_ZEALAND_OTHER_SCENARIO;
+ TimestampedValue<NitzData> nitzSignal =
+ scenario.createNitzSignal(mFakeDeviceState.elapsedRealtime());
+ PhoneTimeZoneSuggestion expectedSuggestion = new PhoneTimeZoneSuggestion(PHONE_ID);
+ expectedSuggestion.setZoneId(scenario.getTimeZoneId());
+ expectedSuggestion.setMatchType(PhoneTimeZoneSuggestion.NETWORK_COUNTRY_AND_OFFSET);
+ expectedSuggestion.setQuality(PhoneTimeZoneSuggestion.SINGLE_ZONE);
+ PhoneTimeZoneSuggestion actualSuggestion = mTimeZoneSuggester.getTimeZoneSuggestion(
+ PHONE_ID, scenario.getNetworkCountryIsoCode(), nitzSignal);
+ assertEquals(expectedSuggestion, actualSuggestion);
+ }
+
+ // Demonstrate what happens with a bogus NITZ for NZ: because the default zone is boosted
+ // then we should return to the country default zone.
+ {
+ Scenario scenario = NEW_ZEALAND_DEFAULT_SCENARIO;
+ // Use a scenario that has a different offset than NZ to generate the NITZ signal.
+ TimestampedValue<NitzData> nitzSignal =
+ CZECHIA_SCENARIO.createNitzSignal(mFakeDeviceState.elapsedRealtime());
+ PhoneTimeZoneSuggestion expectedSuggestion = new PhoneTimeZoneSuggestion(PHONE_ID);
+ expectedSuggestion.setZoneId(NEW_ZEALAND_COUNTRY_DEFAULT_ZONE_ID);
+ expectedSuggestion.setMatchType(PhoneTimeZoneSuggestion.NETWORK_COUNTRY_ONLY);
+ expectedSuggestion.setQuality(PhoneTimeZoneSuggestion.SINGLE_ZONE);
+ PhoneTimeZoneSuggestion actualSuggestion = mTimeZoneSuggester.getTimeZoneSuggestion(
+ PHONE_ID, scenario.getNetworkCountryIsoCode(), nitzSignal);
+ assertEquals(expectedSuggestion, actualSuggestion);
+ }
+ }
+
+ @Test
+ public void test_noCountryDefaultBoost() throws Exception {
+ // Demonstrate the behavior without default country boost for a country with multiple zones:
+ // we cannot get a zone only from the countryIsoCode.
+ {
+ Scenario scenario = UNIQUE_US_ZONE_SCENARIO1;
+ PhoneTimeZoneSuggestion expectedSuggestion = new PhoneTimeZoneSuggestion(PHONE_ID);
+ expectedSuggestion.setZoneId(US_COUNTRY_DEFAULT_ZONE_ID);
+ expectedSuggestion.setMatchType(PhoneTimeZoneSuggestion.NETWORK_COUNTRY_ONLY);
+ expectedSuggestion.setQuality(
+ PhoneTimeZoneSuggestion.MULTIPLE_ZONES_WITH_DIFFERENT_OFFSETS);
+ PhoneTimeZoneSuggestion actualSuggestion = mTimeZoneSuggester.getTimeZoneSuggestion(
+ PHONE_ID, scenario.getNetworkCountryIsoCode(), null /* nitzSignal */);
+ assertEquals(expectedSuggestion, actualSuggestion);
+ }
+
+ // Confirm what happens when NITZ is correct for the country default.
+ {
+ Scenario scenario = UNIQUE_US_ZONE_SCENARIO1;
+ TimestampedValue<NitzData> nitzSignal =
+ scenario.createNitzSignal(mFakeDeviceState.elapsedRealtime());
+ PhoneTimeZoneSuggestion expectedSuggestion = new PhoneTimeZoneSuggestion(PHONE_ID);
+ expectedSuggestion.setZoneId(scenario.getTimeZoneId());
+ expectedSuggestion.setMatchType(PhoneTimeZoneSuggestion.NETWORK_COUNTRY_AND_OFFSET);
+ expectedSuggestion.setQuality(PhoneTimeZoneSuggestion.SINGLE_ZONE);
+ PhoneTimeZoneSuggestion actualSuggestion = mTimeZoneSuggester.getTimeZoneSuggestion(
+ PHONE_ID, scenario.getNetworkCountryIsoCode(), nitzSignal);
+ assertEquals(expectedSuggestion, actualSuggestion);
+ }
+
+ // A valid NITZ signal for the non-default zone should still be correctly detected.
+ {
+ Scenario scenario = UNIQUE_US_ZONE_SCENARIO2;
+ TimestampedValue<NitzData> nitzSignal =
+ scenario.createNitzSignal(mFakeDeviceState.elapsedRealtime());
+ PhoneTimeZoneSuggestion expectedSuggestion = new PhoneTimeZoneSuggestion(PHONE_ID);
+ expectedSuggestion.setZoneId(scenario.getTimeZoneId());
+ expectedSuggestion.setMatchType(PhoneTimeZoneSuggestion.NETWORK_COUNTRY_AND_OFFSET);
+ expectedSuggestion.setQuality(PhoneTimeZoneSuggestion.SINGLE_ZONE);
+ PhoneTimeZoneSuggestion actualSuggestion = mTimeZoneSuggester.getTimeZoneSuggestion(
+ PHONE_ID, scenario.getNetworkCountryIsoCode(), nitzSignal);
+ assertEquals(expectedSuggestion, actualSuggestion);
+ }
+
+ // Demonstrate what happens with a bogus NITZ for US: because the default zone is not
+ // boosted we should not get a suggestion.
+ {
+ // A scenario that has a different offset than US.
+ Scenario scenario = UNIQUE_US_ZONE_SCENARIO1;
+ // Use a scenario that has a different offset than the US to generate the NITZ signal.
+ TimestampedValue<NitzData> nitzSignal =
+ CZECHIA_SCENARIO.createNitzSignal(mFakeDeviceState.elapsedRealtime());
+ PhoneTimeZoneSuggestion expectedSuggestion = EMPTY_TIME_ZONE_SUGGESTION;
+ PhoneTimeZoneSuggestion actualSuggestion = mTimeZoneSuggester.getTimeZoneSuggestion(
+ PHONE_ID, scenario.getNetworkCountryIsoCode(), nitzSignal);
+ assertEquals(expectedSuggestion, actualSuggestion);
+ }
+ }
+}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/nitz/service/PhoneTimeZoneSuggestionTest.java b/tests/telephonytests/src/com/android/internal/telephony/nitz/service/PhoneTimeZoneSuggestionTest.java
new file mode 100644
index 0000000..54838ae
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/nitz/service/PhoneTimeZoneSuggestionTest.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright 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.internal.telephony.nitz.service;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import org.junit.Test;
+
+public class PhoneTimeZoneSuggestionTest {
+ private static final int PHONE_ID = 99999;
+
+ @Test
+ public void testEquals() {
+ PhoneTimeZoneSuggestion one = new PhoneTimeZoneSuggestion(PHONE_ID);
+ assertEquals(one, one);
+
+ PhoneTimeZoneSuggestion two = new PhoneTimeZoneSuggestion(PHONE_ID);
+ assertEquals(one, two);
+ assertEquals(two, one);
+
+ PhoneTimeZoneSuggestion three = new PhoneTimeZoneSuggestion(PHONE_ID + 1);
+ assertNotEquals(one, three);
+ assertNotEquals(three, one);
+
+ one.setZoneId("Europe/London");
+ assertNotEquals(one, two);
+ two.setZoneId("Europe/Paris");
+ assertNotEquals(one, two);
+ one.setZoneId(two.getZoneId());
+ assertEquals(one, two);
+
+ one.setMatchType(PhoneTimeZoneSuggestion.EMULATOR_ZONE_ID);
+ two.setMatchType(PhoneTimeZoneSuggestion.NETWORK_COUNTRY_ONLY);
+ assertNotEquals(one, two);
+ one.setMatchType(PhoneTimeZoneSuggestion.NETWORK_COUNTRY_ONLY);
+ assertEquals(one, two);
+
+ one.setQuality(PhoneTimeZoneSuggestion.SINGLE_ZONE);
+ two.setQuality(PhoneTimeZoneSuggestion.MULTIPLE_ZONES_WITH_DIFFERENT_OFFSETS);
+ assertNotEquals(one, two);
+ one.setQuality(PhoneTimeZoneSuggestion.MULTIPLE_ZONES_WITH_DIFFERENT_OFFSETS);
+ assertEquals(one, two);
+
+ // DebugInfo must not be considered in equals().
+ one.addDebugInfo("Debug info 1");
+ two.addDebugInfo("Debug info 2");
+ assertEquals(one, two);
+ }
+
+ @Test
+ public void testParcelable() {
+ PhoneTimeZoneSuggestion one = new PhoneTimeZoneSuggestion(PHONE_ID);
+ assertEquals(one, roundTripParcelable(one));
+
+ one.setZoneId("Europe/London");
+ one.setMatchType(PhoneTimeZoneSuggestion.EMULATOR_ZONE_ID);
+ one.setQuality(PhoneTimeZoneSuggestion.SINGLE_ZONE);
+ assertEquals(one, roundTripParcelable(one));
+
+ // DebugInfo should also be stored (but is not checked by equals()
+ one.addDebugInfo("This is debug info");
+ PhoneTimeZoneSuggestion two = roundTripParcelable(one);
+ assertEquals(one.getDebugInfo(), two.getDebugInfo());
+ }
+
+ @SuppressWarnings("unchecked")
+ private static <T extends Parcelable> T roundTripParcelable(T one) {
+ Parcel parcel = Parcel.obtain();
+ parcel.writeTypedObject(one, 0);
+ parcel.setDataPosition(0);
+
+ T toReturn = (T) parcel.readTypedObject(PhoneTimeZoneSuggestion.CREATOR);
+ parcel.recycle();
+ return toReturn;
+ }
+}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/nitz/service/TimeZoneDetectionServiceTest.java b/tests/telephonytests/src/com/android/internal/telephony/nitz/service/TimeZoneDetectionServiceTest.java
new file mode 100644
index 0000000..f268501
--- /dev/null
+++ b/tests/telephonytests/src/com/android/internal/telephony/nitz/service/TimeZoneDetectionServiceTest.java
@@ -0,0 +1,587 @@
+/*
+ * Copyright 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.internal.telephony.nitz.service;
+
+import static com.android.internal.telephony.nitz.service.PhoneTimeZoneSuggestion.EMULATOR_ZONE_ID;
+import static com.android.internal.telephony.nitz.service.PhoneTimeZoneSuggestion.MULTIPLE_ZONES_WITH_DIFFERENT_OFFSETS;
+import static com.android.internal.telephony.nitz.service.PhoneTimeZoneSuggestion.MULTIPLE_ZONES_WITH_SAME_OFFSET;
+import static com.android.internal.telephony.nitz.service.PhoneTimeZoneSuggestion.NETWORK_COUNTRY_AND_OFFSET;
+import static com.android.internal.telephony.nitz.service.PhoneTimeZoneSuggestion.NETWORK_COUNTRY_ONLY;
+import static com.android.internal.telephony.nitz.service.PhoneTimeZoneSuggestion.SINGLE_ZONE;
+import static com.android.internal.telephony.nitz.service.PhoneTimeZoneSuggestion.TEST_NETWORK_OFFSET_ONLY;
+import static com.android.internal.telephony.nitz.service.TimeZoneDetectionService.SCORE_HIGH;
+import static com.android.internal.telephony.nitz.service.TimeZoneDetectionService.SCORE_HIGHEST;
+import static com.android.internal.telephony.nitz.service.TimeZoneDetectionService.SCORE_LOW;
+import static com.android.internal.telephony.nitz.service.TimeZoneDetectionService.SCORE_MEDIUM;
+import static com.android.internal.telephony.nitz.service.TimeZoneDetectionService.SCORE_NONE;
+import static com.android.internal.telephony.nitz.service.TimeZoneDetectionService.SCORE_USAGE_THRESHOLD;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import com.android.internal.telephony.nitz.service.PhoneTimeZoneSuggestion.MatchType;
+import com.android.internal.telephony.nitz.service.PhoneTimeZoneSuggestion.Quality;
+import com.android.internal.telephony.nitz.service.TimeZoneDetectionService.QualifiedPhoneTimeZoneSuggestion;
+import com.android.internal.util.IndentingPrintWriter;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.LinkedList;
+
+/**
+ * White-box unit tests for {@link TimeZoneDetectionService}.
+ */
+public class TimeZoneDetectionServiceTest {
+
+ private static final int PHONE1_ID = 10000;
+ private static final int PHONE2_ID = 20000;
+
+ // Suggestion test cases are ordered so that each successive one is of the same or higher score
+ // than the previous.
+ private static final SuggestionTestCase[] TEST_CASES = new SuggestionTestCase[] {
+ newTestCase(NETWORK_COUNTRY_ONLY, MULTIPLE_ZONES_WITH_DIFFERENT_OFFSETS, SCORE_LOW),
+ newTestCase(NETWORK_COUNTRY_ONLY, MULTIPLE_ZONES_WITH_SAME_OFFSET, SCORE_MEDIUM),
+ newTestCase(NETWORK_COUNTRY_AND_OFFSET, MULTIPLE_ZONES_WITH_SAME_OFFSET, SCORE_MEDIUM),
+ newTestCase(NETWORK_COUNTRY_ONLY, SINGLE_ZONE, SCORE_HIGH),
+ newTestCase(NETWORK_COUNTRY_AND_OFFSET, SINGLE_ZONE, SCORE_HIGH),
+ newTestCase(TEST_NETWORK_OFFSET_ONLY, MULTIPLE_ZONES_WITH_SAME_OFFSET, SCORE_HIGHEST),
+ newTestCase(EMULATOR_ZONE_ID, SINGLE_ZONE, SCORE_HIGHEST),
+ };
+
+ private TimeZoneDetectionService mTimeZoneDetectionService;
+ private FakeTimeZoneDetectionServiceHelper mFakeTimeZoneDetectionServiceHelper;
+
+ @Before
+ public void setUp() {
+ mFakeTimeZoneDetectionServiceHelper = new FakeTimeZoneDetectionServiceHelper();
+ mTimeZoneDetectionService =
+ new TimeZoneDetectionService(mFakeTimeZoneDetectionServiceHelper);
+ }
+
+ @Test
+ public void testEmptySuggestions() {
+ PhoneTimeZoneSuggestion phone1TimeZoneSuggestion = createEmptyPhone1Suggestion();
+ PhoneTimeZoneSuggestion phone2TimeZoneSuggestion = createEmptyPhone2Suggestion();
+ Script script = new Script()
+ .initializeTimeZoneDetectionEnabled(true)
+ .initializeTimeZoneSetting(true);
+
+ script.suggestPhoneTimeZone(phone1TimeZoneSuggestion)
+ .verifyTimeZoneNotSet();
+
+ // Assert internal service state.
+ QualifiedPhoneTimeZoneSuggestion expectedPhone1ScoredSuggestion =
+ new QualifiedPhoneTimeZoneSuggestion(phone1TimeZoneSuggestion, SCORE_NONE);
+ assertEquals(expectedPhone1ScoredSuggestion,
+ mTimeZoneDetectionService.getLatestPhoneSuggestion(PHONE1_ID));
+ assertNull(mTimeZoneDetectionService.getLatestPhoneSuggestion(PHONE2_ID));
+ assertEquals(expectedPhone1ScoredSuggestion,
+ mTimeZoneDetectionService.findBestSuggestionForTests());
+
+ script.suggestPhoneTimeZone(phone2TimeZoneSuggestion)
+ .verifyTimeZoneNotSet();
+
+ // Assert internal service state.
+ QualifiedPhoneTimeZoneSuggestion expectedPhone2ScoredSuggestion =
+ new QualifiedPhoneTimeZoneSuggestion(phone2TimeZoneSuggestion, SCORE_NONE);
+ assertEquals(expectedPhone1ScoredSuggestion,
+ mTimeZoneDetectionService.getLatestPhoneSuggestion(PHONE1_ID));
+ assertEquals(expectedPhone2ScoredSuggestion,
+ mTimeZoneDetectionService.getLatestPhoneSuggestion(PHONE2_ID));
+ // Phone 1 should always beat phone 2, all other things being equal.
+ assertEquals(expectedPhone1ScoredSuggestion,
+ mTimeZoneDetectionService.findBestSuggestionForTests());
+ }
+
+ @Test
+ public void testFirstPlausibleSuggestionAcceptedWhenTimeZoneUninitialized() {
+ SuggestionTestCase testCase =
+ newTestCase(NETWORK_COUNTRY_ONLY, MULTIPLE_ZONES_WITH_DIFFERENT_OFFSETS, SCORE_LOW);
+ PhoneTimeZoneSuggestion lowQualitySuggestion =
+ testCase.createSuggestion(PHONE1_ID, "America/New_York");
+ Script script = new Script()
+ .initializeTimeZoneDetectionEnabled(true);
+
+ // The device is uninitialized.
+ script.initializeTimeZoneSetting(false);
+
+ // The very first suggestion will be taken.
+ script.suggestPhoneTimeZone(lowQualitySuggestion)
+ .verifyTimeZoneSetAndReset(lowQualitySuggestion);
+
+ // Assert internal service state.
+ QualifiedPhoneTimeZoneSuggestion expectedScoredSuggestion =
+ new QualifiedPhoneTimeZoneSuggestion(lowQualitySuggestion, testCase.expectedScore);
+ assertEquals(expectedScoredSuggestion,
+ mTimeZoneDetectionService.getLatestPhoneSuggestion(PHONE1_ID));
+ assertEquals(expectedScoredSuggestion,
+ mTimeZoneDetectionService.findBestSuggestionForTests());
+
+ // Another low quality suggestion will be ignored now that the setting is initialized.
+ PhoneTimeZoneSuggestion lowQualitySuggestion2 =
+ testCase.createSuggestion(PHONE1_ID, "America/Los_Angeles");
+ script.suggestPhoneTimeZone(lowQualitySuggestion2)
+ .verifyTimeZoneNotSet();
+
+ // Assert internal service state.
+ QualifiedPhoneTimeZoneSuggestion expectedScoredSuggestion2 =
+ new QualifiedPhoneTimeZoneSuggestion(lowQualitySuggestion2, testCase.expectedScore);
+ assertEquals(expectedScoredSuggestion2,
+ mTimeZoneDetectionService.getLatestPhoneSuggestion(PHONE1_ID));
+ assertEquals(expectedScoredSuggestion2,
+ mTimeZoneDetectionService.findBestSuggestionForTests());
+ }
+
+ @Test
+ public void testTogglingTimeZoneDetection() {
+ Script script = new Script()
+ .initializeTimeZoneSetting(true);
+
+ boolean timeZoneDetectionEnabled = false;
+ script.initializeTimeZoneDetectionEnabled(timeZoneDetectionEnabled);
+
+ for (int i = 0; i < TEST_CASES.length; i++) {
+ SuggestionTestCase testCase = TEST_CASES[i];
+
+ PhoneTimeZoneSuggestion suggestion =
+ testCase.createSuggestion(PHONE1_ID, "Europe/London");
+ script.suggestPhoneTimeZone(suggestion);
+
+ // When time zone detection is already enabled the suggestion (if it scores highly
+ // enough) should be set immediately.
+ if (timeZoneDetectionEnabled) {
+ if (testCase.expectedScore >= SCORE_USAGE_THRESHOLD) {
+ script.verifyTimeZoneSetAndReset(suggestion);
+ } else {
+ script.verifyTimeZoneNotSet();
+ }
+ } else {
+ script.verifyTimeZoneNotSet();
+ }
+
+ // Assert internal service state.
+ QualifiedPhoneTimeZoneSuggestion expectedScoredSuggestion =
+ new QualifiedPhoneTimeZoneSuggestion(suggestion, testCase.expectedScore);
+ assertEquals(expectedScoredSuggestion,
+ mTimeZoneDetectionService.getLatestPhoneSuggestion(PHONE1_ID));
+ assertEquals(expectedScoredSuggestion,
+ mTimeZoneDetectionService.findBestSuggestionForTests());
+
+ // Now toggle the time zone detection setting: when it is toggled to on and the most
+ // recent suggestion scores highly enough, the time zone should be set.
+ timeZoneDetectionEnabled = !timeZoneDetectionEnabled;
+ script.timeZoneDetectionEnabled(timeZoneDetectionEnabled);
+ if (timeZoneDetectionEnabled) {
+ if (testCase.expectedScore >= SCORE_USAGE_THRESHOLD) {
+ script.verifyTimeZoneSetAndReset(suggestion);
+ } else {
+ script.verifyTimeZoneNotSet();
+ }
+ } else {
+ script.verifyTimeZoneNotSet();
+ }
+
+ // Assert internal service state.
+ assertEquals(expectedScoredSuggestion,
+ mTimeZoneDetectionService.getLatestPhoneSuggestion(PHONE1_ID));
+ assertEquals(expectedScoredSuggestion,
+ mTimeZoneDetectionService.findBestSuggestionForTests());
+ }
+ }
+
+ @Test
+ public void testSuggestionsSinglePhone() {
+ Script script = new Script()
+ .initializeTimeZoneDetectionEnabled(true)
+ .initializeTimeZoneSetting(true);
+
+ for (SuggestionTestCase testCase : TEST_CASES) {
+ makePhone1SuggestionAndCheckState(script, testCase);
+ }
+
+ /*
+ * This is the same test as above but the test cases are in
+ * reverse order of their expected score. New suggestions always replace previous ones:
+ * there's effectively no history and so ordering shouldn't make any difference.
+ */
+
+ // Each test case will have the same or lower score than the last.
+ ArrayList<SuggestionTestCase> descendingCasesByScore =
+ new ArrayList<>(Arrays.asList(TEST_CASES));
+ Collections.reverse(descendingCasesByScore);
+
+ for (SuggestionTestCase testCase : descendingCasesByScore) {
+ makePhone1SuggestionAndCheckState(script, testCase);
+ }
+ }
+
+ private void makePhone1SuggestionAndCheckState(Script script, SuggestionTestCase testCase) {
+ String zoneId = "Europe/London";
+ PhoneTimeZoneSuggestion zonePhone1Suggestion = testCase.createSuggestion(PHONE1_ID, zoneId);
+ QualifiedPhoneTimeZoneSuggestion expectedZonePhone1ScoredSuggestion =
+ new QualifiedPhoneTimeZoneSuggestion(zonePhone1Suggestion, testCase.expectedScore);
+
+ script.suggestPhoneTimeZone(zonePhone1Suggestion);
+ if (testCase.expectedScore >= SCORE_USAGE_THRESHOLD) {
+ script.verifyTimeZoneSetAndReset(zonePhone1Suggestion);
+ } else {
+ script.verifyTimeZoneNotSet();
+ }
+
+ // Assert internal service state.
+ assertEquals(expectedZonePhone1ScoredSuggestion,
+ mTimeZoneDetectionService.getLatestPhoneSuggestion(PHONE1_ID));
+ assertEquals(expectedZonePhone1ScoredSuggestion,
+ mTimeZoneDetectionService.findBestSuggestionForTests());
+ }
+
+ /**
+ * Tries a set of test cases to see if the phone with the lowest ID is given preference. This
+ * test also confirms that the time zone setting would only be set if a suggestion is of
+ * sufficient quality.
+ */
+ @Test
+ public void testMultiplePhoneSuggestionScoringAndPhoneIdBias() {
+ String[] zoneIds = { "Europe/London", "Europe/Paris" };
+ PhoneTimeZoneSuggestion emptyPhone1Suggestion = createEmptyPhone1Suggestion();
+ PhoneTimeZoneSuggestion emptyPhone2Suggestion = createEmptyPhone2Suggestion();
+ QualifiedPhoneTimeZoneSuggestion expectedEmptyPhone1ScoredSuggestion =
+ new QualifiedPhoneTimeZoneSuggestion(emptyPhone1Suggestion, SCORE_NONE);
+ QualifiedPhoneTimeZoneSuggestion expectedEmptyPhone2ScoredSuggestion =
+ new QualifiedPhoneTimeZoneSuggestion(emptyPhone2Suggestion, SCORE_NONE);
+
+ Script script = new Script()
+ .initializeTimeZoneDetectionEnabled(true)
+ .initializeTimeZoneSetting(true)
+ // Initialize the latest suggestions as empty so we don't need to worry about nulls
+ // below for the first loop.
+ .suggestPhoneTimeZone(emptyPhone1Suggestion)
+ .suggestPhoneTimeZone(emptyPhone2Suggestion)
+ .resetState();
+
+ for (SuggestionTestCase testCase : TEST_CASES) {
+ PhoneTimeZoneSuggestion zonePhone1Suggestion =
+ testCase.createSuggestion(PHONE1_ID, zoneIds[0]);
+ PhoneTimeZoneSuggestion zonePhone2Suggestion =
+ testCase.createSuggestion(PHONE2_ID, zoneIds[1]);
+ QualifiedPhoneTimeZoneSuggestion expectedZonePhone1ScoredSuggestion =
+ new QualifiedPhoneTimeZoneSuggestion(zonePhone1Suggestion,
+ testCase.expectedScore);
+ QualifiedPhoneTimeZoneSuggestion expectedZonePhone2ScoredSuggestion =
+ new QualifiedPhoneTimeZoneSuggestion(zonePhone2Suggestion,
+ testCase.expectedScore);
+
+ // Start the test by making a suggestion for phone 1.
+ script.suggestPhoneTimeZone(zonePhone1Suggestion);
+ if (testCase.expectedScore >= SCORE_USAGE_THRESHOLD) {
+ script.verifyTimeZoneSetAndReset(zonePhone1Suggestion);
+ } else {
+ script.verifyTimeZoneNotSet();
+ }
+
+ // Assert internal service state.
+ assertEquals(expectedZonePhone1ScoredSuggestion,
+ mTimeZoneDetectionService.getLatestPhoneSuggestion(PHONE1_ID));
+ assertEquals(expectedEmptyPhone2ScoredSuggestion,
+ mTimeZoneDetectionService.getLatestPhoneSuggestion(PHONE2_ID));
+ assertEquals(expectedZonePhone1ScoredSuggestion,
+ mTimeZoneDetectionService.findBestSuggestionForTests());
+
+ // Phone 2 then makes an identical suggestion. Phone 1's suggestion should still "win"
+ // if it is above the required threshold.
+ script.suggestPhoneTimeZone(zonePhone2Suggestion);
+ if (testCase.expectedScore >= SCORE_USAGE_THRESHOLD) {
+ script.verifyTimeZoneSetAndReset(zonePhone1Suggestion);
+ } else {
+ script.verifyTimeZoneNotSet();
+ }
+
+ // Assert internal service state.
+ assertEquals(expectedZonePhone1ScoredSuggestion,
+ mTimeZoneDetectionService.getLatestPhoneSuggestion(PHONE1_ID));
+ assertEquals(expectedZonePhone2ScoredSuggestion,
+ mTimeZoneDetectionService.getLatestPhoneSuggestion(PHONE2_ID));
+ // Phone 1 should always beat phone 2, all other things being equal.
+ assertEquals(expectedZonePhone1ScoredSuggestion,
+ mTimeZoneDetectionService.findBestSuggestionForTests());
+
+ // Withdrawing phone 1's suggestion should leave phone 2 as the new winner. Since the
+ // zoneId is different, the time zone setting should be updated.
+ script.suggestPhoneTimeZone(emptyPhone1Suggestion);
+ if (testCase.expectedScore >= SCORE_USAGE_THRESHOLD) {
+ script.verifyTimeZoneSetAndReset(zonePhone2Suggestion);
+ } else {
+ script.verifyTimeZoneNotSet();
+ }
+
+ // Assert internal service state.
+ assertEquals(expectedEmptyPhone1ScoredSuggestion,
+ mTimeZoneDetectionService.getLatestPhoneSuggestion(PHONE1_ID));
+ assertEquals(expectedZonePhone2ScoredSuggestion,
+ mTimeZoneDetectionService.getLatestPhoneSuggestion(PHONE2_ID));
+ assertEquals(expectedZonePhone2ScoredSuggestion,
+ mTimeZoneDetectionService.findBestSuggestionForTests());
+
+ // Reset the state for the next loop.
+ script.suggestPhoneTimeZone(emptyPhone2Suggestion)
+ .verifyTimeZoneNotSet();
+ assertEquals(expectedEmptyPhone1ScoredSuggestion,
+ mTimeZoneDetectionService.getLatestPhoneSuggestion(PHONE1_ID));
+ assertEquals(expectedEmptyPhone2ScoredSuggestion,
+ mTimeZoneDetectionService.getLatestPhoneSuggestion(PHONE2_ID));
+ }
+ }
+
+ /**
+ * The {@link TimeZoneDetectionService.Helper} is left to detect whether changing the the time
+ * zone is actually necessary. This test proves that the service doesn't assume it knows the
+ * current setting.
+ */
+ @Test
+ public void testTimeZoneDetectionServiceDoesNotAssumeCurrentSetting() {
+ Script script = new Script()
+ .initializeTimeZoneDetectionEnabled(true);
+
+ SuggestionTestCase testCase =
+ newTestCase(NETWORK_COUNTRY_AND_OFFSET, SINGLE_ZONE, SCORE_HIGH);
+ PhoneTimeZoneSuggestion losAngelesSuggestion =
+ testCase.createSuggestion(PHONE1_ID, "America/Los_Angeles");
+ PhoneTimeZoneSuggestion newYorkSuggestion =
+ testCase.createSuggestion(PHONE1_ID, "America/New_York");
+
+ // Initialization.
+ script.suggestPhoneTimeZone(losAngelesSuggestion)
+ .verifyTimeZoneSetAndReset(losAngelesSuggestion);
+ // Suggest it again - it should be set.
+ script.suggestPhoneTimeZone(losAngelesSuggestion)
+ .verifyTimeZoneSetAndReset(losAngelesSuggestion);
+
+ // Toggling time zone detection should set it.
+ script.timeZoneDetectionEnabled(false)
+ .timeZoneDetectionEnabled(true)
+ .verifyTimeZoneSetAndReset(losAngelesSuggestion);
+
+ // Simulate a user turning detection off, a new suggestion being made, and the user turning
+ // it on again.
+ script.timeZoneDetectionEnabled(false)
+ .suggestPhoneTimeZone(newYorkSuggestion)
+ .verifyTimeZoneNotSet();
+ // Latest suggestion should be used.
+ script.timeZoneDetectionEnabled(true)
+ .verifyTimeZoneSetAndReset(newYorkSuggestion);
+ }
+
+ private static PhoneTimeZoneSuggestion createEmptyPhone1Suggestion() {
+ return new PhoneTimeZoneSuggestion(PHONE1_ID);
+ }
+
+ private static PhoneTimeZoneSuggestion createEmptyPhone2Suggestion() {
+ return new PhoneTimeZoneSuggestion(PHONE2_ID);
+ }
+
+ class FakeTimeZoneDetectionServiceHelper implements TimeZoneDetectionService.Helper {
+
+ private Listener mListener;
+ private boolean mTimeZoneDetectionEnabled;
+ private boolean mTimeZoneInitialized = false;
+ private TestState<PhoneTimeZoneSuggestion> mTimeZoneSuggestion = new TestState<>();
+
+ @Override
+ public void setListener(Listener listener) {
+ this.mListener = listener;
+ }
+
+ @Override
+ public boolean isTimeZoneDetectionEnabled() {
+ return mTimeZoneDetectionEnabled;
+ }
+
+ @Override
+ public boolean isTimeZoneSettingInitialized() {
+ return mTimeZoneInitialized;
+ }
+
+ @Override
+ public void setDeviceTimeZoneFromSuggestion(PhoneTimeZoneSuggestion timeZoneSuggestion) {
+ mTimeZoneInitialized = true;
+ mTimeZoneSuggestion.set(timeZoneSuggestion);
+ }
+
+ @Override
+ public void dumpState(PrintWriter pw) {
+ // No-op for fake
+ }
+
+ @Override
+ public void dumpLogs(IndentingPrintWriter ipw) {
+ // No-op for fake
+ }
+
+ void initializeTimeZoneDetectionEnabled(boolean enabled) {
+ mTimeZoneDetectionEnabled = enabled;
+ }
+
+ void initializeTimeZone(boolean initialized) {
+ mTimeZoneInitialized = initialized;
+ }
+
+ void simulateTimeZoneDetectionEnabled(boolean enabled) {
+ mTimeZoneDetectionEnabled = enabled;
+ mListener.onTimeZoneDetectionChange(enabled);
+ }
+
+ void assertTimeZoneNotSet() {
+ mTimeZoneSuggestion.assertHasNotBeenSet();
+ }
+
+ void assertTimeZoneSuggested(PhoneTimeZoneSuggestion timeZoneSuggestion) {
+ mTimeZoneSuggestion.assertHasBeenSet();
+ mTimeZoneSuggestion.assertChangeCount(1);
+ mTimeZoneSuggestion.assertLatestEquals(timeZoneSuggestion);
+ }
+
+ void commitAllChanges() {
+ mTimeZoneSuggestion.commitLatest();
+ }
+ }
+
+ /** Some piece of state that tests want to track. */
+ private static class TestState<T> {
+ private T mInitialValue;
+ private LinkedList<T> mValues = new LinkedList<>();
+
+ void init(T value) {
+ mValues.clear();
+ mInitialValue = value;
+ }
+
+ void set(T value) {
+ mValues.addFirst(value);
+ }
+
+ boolean hasBeenSet() {
+ return mValues.size() > 0;
+ }
+
+ void assertHasNotBeenSet() {
+ assertFalse(hasBeenSet());
+ }
+
+ void assertHasBeenSet() {
+ assertTrue(hasBeenSet());
+ }
+
+ void commitLatest() {
+ if (hasBeenSet()) {
+ mInitialValue = mValues.getLast();
+ mValues.clear();
+ }
+ }
+
+ void assertLatestEquals(T expected) {
+ assertEquals(expected, getLatest());
+ }
+
+ void assertChangeCount(int expectedCount) {
+ assertEquals(expectedCount, mValues.size());
+ }
+
+ public T getLatest() {
+ if (hasBeenSet()) {
+ return mValues.getFirst();
+ }
+ return mInitialValue;
+ }
+ }
+
+ /**
+ * A "fluent" class allows reuse of code in tests: initialization, simulation and verification
+ * logic.
+ */
+ private class Script {
+
+ Script initializeTimeZoneDetectionEnabled(boolean enabled) {
+ mFakeTimeZoneDetectionServiceHelper.initializeTimeZoneDetectionEnabled(enabled);
+ return this;
+ }
+
+ Script initializeTimeZoneSetting(boolean initialized) {
+ mFakeTimeZoneDetectionServiceHelper.initializeTimeZone(initialized);
+ return this;
+ }
+
+ Script timeZoneDetectionEnabled(boolean timeZoneDetectionEnabled) {
+ mFakeTimeZoneDetectionServiceHelper.simulateTimeZoneDetectionEnabled(
+ timeZoneDetectionEnabled);
+ return this;
+ }
+
+ /** Simulates the time zone detection service receiving a phone-originated suggestion. */
+ Script suggestPhoneTimeZone(PhoneTimeZoneSuggestion phoneTimeZoneSuggestion) {
+ mTimeZoneDetectionService.suggestPhoneTimeZone(phoneTimeZoneSuggestion);
+ return this;
+ }
+
+ Script verifyTimeZoneNotSet() {
+ mFakeTimeZoneDetectionServiceHelper.assertTimeZoneNotSet();
+ return this;
+ }
+
+ Script verifyTimeZoneSetAndReset(PhoneTimeZoneSuggestion timeZoneSuggestion) {
+ mFakeTimeZoneDetectionServiceHelper.assertTimeZoneSuggested(timeZoneSuggestion);
+ mFakeTimeZoneDetectionServiceHelper.commitAllChanges();
+ return this;
+ }
+
+ Script resetState() {
+ mFakeTimeZoneDetectionServiceHelper.commitAllChanges();
+ return this;
+ }
+ }
+
+ private static class SuggestionTestCase {
+ public final int matchType;
+ public final int quality;
+ public final int expectedScore;
+
+ SuggestionTestCase(int matchType, int quality, int expectedScore) {
+ this.matchType = matchType;
+ this.quality = quality;
+ this.expectedScore = expectedScore;
+ }
+
+ private PhoneTimeZoneSuggestion createSuggestion(int phoneId, String zoneId) {
+ PhoneTimeZoneSuggestion suggestion = new PhoneTimeZoneSuggestion(phoneId);
+ suggestion.setZoneId(zoneId);
+ suggestion.setMatchType(matchType);
+ suggestion.setQuality(quality);
+ return suggestion;
+ }
+ }
+
+ private static SuggestionTestCase newTestCase(
+ @MatchType int matchType, @Quality int quality, int expectedScore) {
+ return new SuggestionTestCase(matchType, quality, expectedScore);
+ }
+}