From 6229fd40fe911d9ab4765f9942832d24d6a38e1d Mon Sep 17 00:00:00 2001 From: Amit Mahajan Date: Fri, 7 Sep 2018 13:53:17 -0700 Subject: Improve duplicate detection/dropping logic. This change is to discard older segment that is already present in the db (when a duplicate is detected) instead of dropping the new one. Test: unit testing Bug: 111449999 Change-Id: Iac86198ede2ed8a965d82d5b6c59b5ec7a4a81b7 --- .../internal/telephony/InboundSmsHandler.java | 128 ++++++++++++++------- .../internal/telephony/InboundSmsTracker.java | 85 ++++++++++---- .../telephony/gsm/GsmInboundSmsHandlerTest.java | 4 + 3 files changed, 153 insertions(+), 64 deletions(-) diff --git a/src/java/com/android/internal/telephony/InboundSmsHandler.java b/src/java/com/android/internal/telephony/InboundSmsHandler.java index b7792992dd..b2c3e898c3 100644 --- a/src/java/com/android/internal/telephony/InboundSmsHandler.java +++ b/src/java/com/android/internal/telephony/InboundSmsHandler.java @@ -61,6 +61,7 @@ import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; import android.text.TextUtils; import android.util.LocalLog; +import android.util.Pair; import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; @@ -99,13 +100,21 @@ import java.util.Map; */ public abstract class InboundSmsHandler extends StateMachine { protected static final boolean DBG = true; - private static final boolean VDBG = false; // STOPSHIP if true, logs user data + protected static final boolean VDBG = false; // STOPSHIP if true, logs user data /** Query projection for checking for duplicate message segments. */ - private static final String[] PDU_PROJECTION = { - "pdu" + private static final String[] PDU_DELETED_FLAG_PROJECTION = { + "pdu", + "deleted" }; + /** Mapping from DB COLUMN to PDU_SEQUENCE_PORT PROJECTION index */ + private static final Map PDU_DELETED_FLAG_PROJECTION_INDEX_MAPPING = + new HashMap() {{ + put(PDU_COLUMN, 0); + put(DELETED_FLAG_COLUMN, 1); + }}; + /** Query projection for combining concatenated message segments. */ private static final String[] PDU_SEQUENCE_PORT_PROJECTION = { "pdu", @@ -133,6 +142,7 @@ public abstract class InboundSmsHandler extends StateMachine { public static final int ID_COLUMN = 7; public static final int MESSAGE_BODY_COLUMN = 8; public static final int DISPLAY_ADDRESS_COLUMN = 9; + public static final int DELETED_FLAG_COLUMN = 10; public static final String SELECT_BY_ID = "_id=?"; @@ -1183,55 +1193,47 @@ public abstract class InboundSmsHandler extends StateMachine { } /** - * Function to check if message should be dropped because same message has already been - * received. In certain cases it checks for similar messages instead of exact same (cases where - * keeping both messages in db can cause ambiguity) - * @return true if duplicate exists, false otherwise + * Function to detect and handle duplicate messages. If the received message should replace an + * existing message in the raw db, this function deletes the existing message. If an existing + * message takes priority (for eg, existing message has already been broadcast), then this new + * message should be dropped. + * @return true if the message represented by the passed in tracker should be dropped, + * false otherwise */ - private boolean duplicateExists(InboundSmsTracker tracker) throws SQLException { - String address = tracker.getAddress(); - // convert to strings for query - String refNumber = Integer.toString(tracker.getReferenceNumber()); - String count = Integer.toString(tracker.getMessageCount()); - // sequence numbers are 1-based except for CDMA WAP, which is 0-based - int sequence = tracker.getSequenceNumber(); - String seqNumber = Integer.toString(sequence); - String date = Long.toString(tracker.getTimestamp()); - String messageBody = tracker.getMessageBody(); - String where; - if (tracker.getMessageCount() == 1) { - where = "address=? AND reference_number=? AND count=? AND sequence=? AND " + - "date=? AND message_body=?"; - } else { - // for multi-part messages, deduping should also be done against undeleted - // segments that can cause ambiguity when contacenating the segments, that is, - // segments with same address, reference_number, count, sequence and message type. - where = tracker.getQueryForMultiPartDuplicates(); - } + private boolean checkAndHandleDuplicate(InboundSmsTracker tracker) throws SQLException { + Pair exactMatchQuery = tracker.getExactMatchDupDetectQuery(); Cursor cursor = null; try { // Check for duplicate message segments - cursor = mResolver.query(sRawUri, PDU_PROJECTION, where, - new String[]{address, refNumber, count, seqNumber, date, messageBody}, - null); + cursor = mResolver.query(sRawUri, PDU_DELETED_FLAG_PROJECTION, exactMatchQuery.first, + exactMatchQuery.second, null); // moveToNext() returns false if no duplicates were found if (cursor != null && cursor.moveToNext()) { - loge("Discarding duplicate message segment, refNumber=" + refNumber - + " seqNumber=" + seqNumber + " count=" + count); - if (VDBG) { - loge("address=" + address + " date=" + date + " messageBody=" + - messageBody); + if (cursor.getCount() != 1) { + loge("Exact match query returned " + cursor.getCount() + " rows"); } - String oldPduString = cursor.getString(PDU_COLUMN); - byte[] pdu = tracker.getPdu(); - byte[] oldPdu = HexDump.hexStringToByteArray(oldPduString); - if (!Arrays.equals(oldPdu, tracker.getPdu())) { - loge("Warning: dup message segment PDU of length " + pdu.length - + " is different from existing PDU of length " + oldPdu.length); + + // if the exact matching row is marked deleted, that means this message has already + // been received and processed, and can be discarded as dup + if (cursor.getInt( + PDU_DELETED_FLAG_PROJECTION_INDEX_MAPPING.get(DELETED_FLAG_COLUMN)) == 1) { + loge("Discarding duplicate message segment: " + tracker); + logDupPduMismatch(cursor, tracker); + return true; // reject message + } else { + // exact match duplicate is not marked deleted. If it is a multi-part segment, + // the code below for inexact match will take care of it. If it is a single + // part message, handle it here. + if (tracker.getMessageCount() == 1) { + // delete the old message segment permanently + deleteFromRawTable(exactMatchQuery.first, exactMatchQuery.second, + DELETE_PERMANENTLY); + loge("Replacing duplicate message: " + tracker); + logDupPduMismatch(cursor, tracker); + } } - return true; // reject message } } finally { if (cursor != null) { @@ -1239,9 +1241,49 @@ public abstract class InboundSmsHandler extends StateMachine { } } + // The code above does an exact match. Multi-part message segments need an additional check + // on top of that: if there is a message segment that conflicts this new one (may not be an + // exact match), replace the old message segment with this one. + if (tracker.getMessageCount() > 1) { + Pair inexactMatchQuery = tracker.getInexactMatchDupDetectQuery(); + cursor = null; + try { + // Check for duplicate message segments + cursor = mResolver.query(sRawUri, PDU_DELETED_FLAG_PROJECTION, + inexactMatchQuery.first, inexactMatchQuery.second, null); + + // moveToNext() returns false if no duplicates were found + if (cursor != null && cursor.moveToNext()) { + if (cursor.getCount() != 1) { + loge("Inexact match query returned " + cursor.getCount() + " rows"); + } + // delete the old message segment permanently + deleteFromRawTable(inexactMatchQuery.first, inexactMatchQuery.second, + DELETE_PERMANENTLY); + loge("Replacing duplicate message segment: " + tracker); + logDupPduMismatch(cursor, tracker); + } + } finally { + if (cursor != null) { + cursor.close(); + } + } + } + return false; } + private void logDupPduMismatch(Cursor cursor, InboundSmsTracker tracker) { + String oldPduString = cursor.getString( + PDU_DELETED_FLAG_PROJECTION_INDEX_MAPPING.get(PDU_COLUMN)); + byte[] pdu = tracker.getPdu(); + byte[] oldPdu = HexDump.hexStringToByteArray(oldPduString); + if (!Arrays.equals(oldPdu, tracker.getPdu())) { + loge("Warning: dup message PDU of length " + pdu.length + + " is different from existing PDU of length " + oldPdu.length); + } + } + /** * Insert a message PDU into the raw table so we can acknowledge it immediately. * If the device crashes before the broadcast to listeners completes, it will be delivered @@ -1255,7 +1297,7 @@ public abstract class InboundSmsHandler extends StateMachine { private int addTrackerToRawTable(InboundSmsTracker tracker, boolean deDup) { if (deDup) { try { - if (duplicateExists(tracker)) { + if (checkAndHandleDuplicate(tracker)) { return Intents.RESULT_SMS_DUPLICATED; // reject message } } catch (SQLException e) { diff --git a/src/java/com/android/internal/telephony/InboundSmsTracker.java b/src/java/com/android/internal/telephony/InboundSmsTracker.java index 36c699610d..50c84c86a9 100644 --- a/src/java/com/android/internal/telephony/InboundSmsTracker.java +++ b/src/java/com/android/internal/telephony/InboundSmsTracker.java @@ -18,6 +18,7 @@ package com.android.internal.telephony; import android.content.ContentValues; import android.database.Cursor; +import android.util.Pair; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.HexDump; @@ -88,19 +89,6 @@ public class InboundSmsTracker { + "AND count=? AND (destination_port & " + DEST_PORT_FLAG_3GPP2_WAP_PDU + "=" + DEST_PORT_FLAG_3GPP2_WAP_PDU + ") AND deleted=0"; - @VisibleForTesting - public static final String SELECT_BY_DUPLICATE_REFERENCE = "address=? AND " - + "reference_number=? AND count=? AND sequence=? AND " - + "((date=? AND message_body=?) OR deleted=0) AND (destination_port & " - + DEST_PORT_FLAG_3GPP2_WAP_PDU + "=0)"; - - @VisibleForTesting - public static final String SELECT_BY_DUPLICATE_REFERENCE_3GPP2WAP = "address=? AND " - + "reference_number=? " + "AND count=? AND sequence=? AND " - + "((date=? AND message_body=?) OR deleted=0) AND " - + "(destination_port & " + DEST_PORT_FLAG_3GPP2_WAP_PDU + "=" - + DEST_PORT_FLAG_3GPP2_WAP_PDU + ")"; - /** * Create a tracker for a single-part SMS. * @@ -284,13 +272,15 @@ public class InboundSmsTracker { builder.append(new Date(mTimestamp)); builder.append(" destPort=").append(mDestPort); builder.append(" is3gpp2=").append(mIs3gpp2); - if (mAddress != null) { + if (InboundSmsHandler.VDBG) { builder.append(" address=").append(mAddress); - builder.append(" display_originating_addr=").append(mDisplayAddress); - builder.append(" refNumber=").append(mReferenceNumber); - builder.append(" seqNumber=").append(mSequenceNumber); - builder.append(" msgCount=").append(mMessageCount); + builder.append(" timestamp=").append(mTimestamp); + builder.append(" messageBody=").append(mMessageBody); } + builder.append(" display_originating_addr=").append(mDisplayAddress); + builder.append(" refNumber=").append(mReferenceNumber); + builder.append(" seqNumber=").append(mSequenceNumber); + builder.append(" msgCount=").append(mMessageCount); if (mDeleteWhere != null) { builder.append(" deleteWhere(").append(mDeleteWhere); builder.append(") deleteArgs=(").append(Arrays.toString(mDeleteWhereArgs)); @@ -324,9 +314,62 @@ public class InboundSmsTracker { return mIs3gpp2WapPdu ? SELECT_BY_REFERENCE_3GPP2WAP : SELECT_BY_REFERENCE; } - public String getQueryForMultiPartDuplicates() { - return mIs3gpp2WapPdu ? SELECT_BY_DUPLICATE_REFERENCE_3GPP2WAP : - SELECT_BY_DUPLICATE_REFERENCE; + /** + * Get the query to find the exact same message/message segment in the db. + * @return Pair with where as Pair.first and whereArgs as Pair.second + */ + public Pair getExactMatchDupDetectQuery() { + // convert to strings for query + String address = getAddress(); + String refNumber = Integer.toString(getReferenceNumber()); + String count = Integer.toString(getMessageCount()); + String seqNumber = Integer.toString(getSequenceNumber()); + String date = Long.toString(getTimestamp()); + String messageBody = getMessageBody(); + + String where = "address=? AND reference_number=? AND count=? AND sequence=? AND " + + "date=? AND message_body=?"; + where = addDestPortQuery(where); + String[] whereArgs = new String[]{address, refNumber, count, seqNumber, date, messageBody}; + + return new Pair<>(where, whereArgs); + } + + /** + * The key differences here compared to exact match are: + * - this is applicable only for multi-part message segments + * - this does not match date or message_body + * - this matches deleted=0 (undeleted segments) + * The only difference as compared to getQueryForSegments() is that this checks for sequence as + * well. + * @return Pair with where as Pair.first and whereArgs as Pair.second + */ + public Pair getInexactMatchDupDetectQuery() { + if (getMessageCount() == 1) return null; + + // convert to strings for query + String address = getAddress(); + String refNumber = Integer.toString(getReferenceNumber()); + String count = Integer.toString(getMessageCount()); + String seqNumber = Integer.toString(getSequenceNumber()); + + String where = "address=? AND reference_number=? AND count=? AND sequence=? AND " + + "deleted=0"; + where = addDestPortQuery(where); + String[] whereArgs = new String[]{address, refNumber, count, seqNumber}; + + return new Pair<>(where, whereArgs); + } + + private String addDestPortQuery(String where) { + String whereDestPort; + if (mIs3gpp2WapPdu) { + whereDestPort = "destination_port & " + DEST_PORT_FLAG_3GPP2_WAP_PDU + "=" + + DEST_PORT_FLAG_3GPP2_WAP_PDU; + } else { + whereDestPort = "destination_port & " + DEST_PORT_FLAG_3GPP2_WAP_PDU + "=0"; + } + return where + " AND (" + whereDestPort + ")"; } /** diff --git a/tests/telephonytests/src/com/android/internal/telephony/gsm/GsmInboundSmsHandlerTest.java b/tests/telephonytests/src/com/android/internal/telephony/gsm/GsmInboundSmsHandlerTest.java index 851a2175c0..1e29908990 100644 --- a/tests/telephonytests/src/com/android/internal/telephony/gsm/GsmInboundSmsHandlerTest.java +++ b/tests/telephonytests/src/com/android/internal/telephony/gsm/GsmInboundSmsHandlerTest.java @@ -570,6 +570,10 @@ public class GsmInboundSmsHandlerTest extends TelephonyTest { verify(mContext, never()).sendBroadcast(any(Intent.class)); // verify there's only 1 of the segments in the db (other should be discarded as dup) assertEquals(1, mContentProvider.getNumRows()); + // verify the first one is discarded, and second message is present in the db + Cursor c = mContentProvider.query(sRawUri, null, null, null, null); + c.moveToFirst(); + assertEquals(mMessageBodyPart2, c.getString(c.getColumnIndex("message_body"))); // State machine should go back to idle assertEquals("IdleState", getCurrentState().getName()); } -- cgit v1.2.3 From 0edd55aa92c69f709fa25fbd2f0433f89f3d204e Mon Sep 17 00:00:00 2001 From: Nathan Harold Date: Mon, 8 Oct 2018 18:06:38 -0700 Subject: Always call processResponseDone() NATT Responses In case of errors, stopkeepaliveReponse wasn't calling processResponseDone(), which leads to a wakelock being held incorrectly. Move the processResponseDone() to finally blocks to ensure that it's always called for any non-null response message. Bug: 116766779 Test: compilation Change-Id: I0ced3fa19804f2fa422aaebd039dcaac40156dd9 --- .../android/internal/telephony/RadioResponse.java | 69 +++++++++++----------- 1 file changed, 34 insertions(+), 35 deletions(-) diff --git a/src/java/com/android/internal/telephony/RadioResponse.java b/src/java/com/android/internal/telephony/RadioResponse.java index 0fc527903b..dda8b605bc 100644 --- a/src/java/com/android/internal/telephony/RadioResponse.java +++ b/src/java/com/android/internal/telephony/RadioResponse.java @@ -1313,37 +1313,36 @@ public class RadioResponse extends IRadioResponse.Stub { android.hardware.radio.V1_1.KeepaliveStatus keepaliveStatus) { RILRequest rr = mRil.processResponse(responseInfo); - - if (rr == null) { - return; - } + if (rr == null) return; KeepaliveStatus ret = null; - - switch(responseInfo.error) { - case RadioError.NONE: - int convertedStatus = convertHalKeepaliveStatusCode(keepaliveStatus.code); - if (convertedStatus < 0) { + try { + switch(responseInfo.error) { + case RadioError.NONE: + int convertedStatus = convertHalKeepaliveStatusCode(keepaliveStatus.code); + if (convertedStatus < 0) { + ret = new KeepaliveStatus(KeepaliveStatus.ERROR_UNSUPPORTED); + } else { + ret = new KeepaliveStatus(keepaliveStatus.sessionHandle, convertedStatus); + } + // If responseInfo.error is NONE, response function sends the response message + // even if result is actually an error. + sendMessageResponse(rr.mResult, ret); + break; + case RadioError.REQUEST_NOT_SUPPORTED: ret = new KeepaliveStatus(KeepaliveStatus.ERROR_UNSUPPORTED); - } else { - ret = new KeepaliveStatus(keepaliveStatus.sessionHandle, convertedStatus); - } - // If responseInfo.error is NONE, response function sends the response message - // even if result is actually an error. - sendMessageResponse(rr.mResult, ret); - break; - case RadioError.REQUEST_NOT_SUPPORTED: - ret = new KeepaliveStatus(KeepaliveStatus.ERROR_UNSUPPORTED); - break; - case RadioError.NO_RESOURCES: - ret = new KeepaliveStatus(KeepaliveStatus.ERROR_NO_RESOURCES); - break; - default: - ret = new KeepaliveStatus(KeepaliveStatus.ERROR_UNKNOWN); - break; + break; + case RadioError.NO_RESOURCES: + ret = new KeepaliveStatus(KeepaliveStatus.ERROR_NO_RESOURCES); + break; + default: + ret = new KeepaliveStatus(KeepaliveStatus.ERROR_UNKNOWN); + break; + } + } finally { + // If responseInfo.error != NONE, the processResponseDone sends the response message. + mRil.processResponseDone(rr, responseInfo, ret); } - // If responseInfo.error != NONE, the processResponseDone sends the response message. - mRil.processResponseDone(rr, responseInfo, ret); } /** @@ -1351,16 +1350,16 @@ public class RadioResponse extends IRadioResponse.Stub { */ public void stopKeepaliveResponse(RadioResponseInfo responseInfo) { RILRequest rr = mRil.processResponse(responseInfo); + if (rr == null) return; - if (rr == null) { - return; - } - - if (responseInfo.error == RadioError.NONE) { - sendMessageResponse(rr.mResult, null); + try { + if (responseInfo.error == RadioError.NONE) { + sendMessageResponse(rr.mResult, null); + } else { + //TODO: Error code translation + } + } finally { mRil.processResponseDone(rr, responseInfo, null); - } else { - //TODO: Error code translation } } -- cgit v1.2.3 From b50571d473b4633da404b73fc55ea690a7c72845 Mon Sep 17 00:00:00 2001 From: Mengjun Leng Date: Fri, 10 Aug 2018 11:14:09 +0800 Subject: Fix emergency call fails in APM ON While the dialing request is delivered into CallTracker, ServiceStateTracker does not complete network polling, so that it fails to check power off state from service state. To fix it, check the radio off state from RIL interface instead. Bug: 112442763 Change-Id: Ied8a240f42f7537b2868f772ea4ce8d1293eb5fb --- src/java/com/android/internal/telephony/GsmCdmaCallTracker.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/java/com/android/internal/telephony/GsmCdmaCallTracker.java b/src/java/com/android/internal/telephony/GsmCdmaCallTracker.java index 2b399bab11..025e31c0f5 100755 --- a/src/java/com/android/internal/telephony/GsmCdmaCallTracker.java +++ b/src/java/com/android/internal/telephony/GsmCdmaCallTracker.java @@ -617,11 +617,10 @@ public class GsmCdmaCallTracker extends CallTracker { * @throws CallStateException */ public void checkForDialIssues() throws CallStateException { - int serviceState = mPhone.getServiceState().getState(); String disableCall = SystemProperties.get( TelephonyProperties.PROPERTY_DISABLE_CALL, "false"); - if (serviceState == ServiceState.STATE_POWER_OFF) { + if (!mCi.getRadioState().isOn()) { throw new CallStateException(CallStateException.ERROR_POWER_OFF, "Modem not powered"); } -- cgit v1.2.3 From e4b72db153c53f520b50a7a6f3678febe311a949 Mon Sep 17 00:00:00 2001 From: Malcolm Chen Date: Fri, 5 Oct 2018 16:19:12 -0700 Subject: Refactor PhoneSwitcherTest to user proper Mocks. Bug: 116530877 Test: unittest Change-Id: I16aa305ac083c24161a83e7ea28bd92c700cee0c Merged-In: I16aa305ac083c24161a83e7ea28bd92c700cee0c --- .../internal/telephony/PhoneSwitcherTest.java | 755 +++++++++------------ 1 file changed, 339 insertions(+), 416 deletions(-) diff --git a/tests/telephonytests/src/com/android/internal/telephony/PhoneSwitcherTest.java b/tests/telephonytests/src/com/android/internal/telephony/PhoneSwitcherTest.java index 23af2e511b..4f450d0612 100644 --- a/tests/telephonytests/src/com/android/internal/telephony/PhoneSwitcherTest.java +++ b/tests/telephonytests/src/com/android/internal/telephony/PhoneSwitcherTest.java @@ -16,47 +16,38 @@ package com.android.internal.telephony; -import static org.junit.Assert.fail; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import android.content.Context; import android.content.Intent; import android.net.ConnectivityManager; -import android.net.IConnectivityManager; import android.net.NetworkCapabilities; import android.net.NetworkRequest; import android.net.StringNetworkSpecifier; -import android.os.AsyncResult; -import android.os.Binder; import android.os.Handler; import android.os.HandlerThread; -import android.os.Looper; import android.os.Message; -import android.telephony.Rlog; +import android.os.Messenger; import android.telephony.SubscriptionManager; import android.test.suitebuilder.annotation.SmallTest; -import com.android.internal.telephony.mocks.ConnectivityServiceMock; -import com.android.internal.telephony.mocks.SubscriptionControllerMock; -import com.android.internal.telephony.mocks.TelephonyRegistryMock; -import com.android.internal.telephony.test.SimulatedCommands; - import org.junit.After; import org.junit.Before; import org.junit.Test; import org.mockito.Mock; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.atomic.AtomicReference; - public class PhoneSwitcherTest extends TelephonyTest { - private static final String LOG_TAG = "PhoneSwitcherTest"; - private static final String[] sNetworkAttributes = new String[] { "mobile,0,0,0,-1,true", "mobile_mms,2,0,2,60000,true", "mobile_supl,3,0,2,60000,true", "mobile_dun,4,0,2,60000,true", @@ -64,110 +55,8 @@ public class PhoneSwitcherTest extends TelephonyTest { "mobile_ims,11,0,2,60000,true", "mobile_cbs,12,0,2,60000,true", "mobile_ia,14,0,2,-1,true", "mobile_emergency,15,0,2,-1,true"}; - static void failAndStack(String str) { - fail(str + "\n" + SubscriptionMonitorTest.stack()); - } - - static String stack() { - StringBuilder sb = new StringBuilder(); - for(StackTraceElement e : Thread.currentThread().getStackTrace()) { - sb.append(e.toString()).append("\n"); - } - return sb.toString(); - } - - private static class TestHandler extends Handler { - public final static int ACTIVE_PHONE_SWITCH = 1; - public final static int IN_IDLE = 2; - - HandlerThread handlerThread; - - public TestHandler(Looper looper) { - super(looper); - } - - public void die() { - if(handlerThread != null) { - handlerThread.quit(); - handlerThread = null; - } - } - - public void blockTilIdle() { - Object lock = new Object(); - synchronized (lock) { - Message msg = this.obtainMessage(IN_IDLE, lock); - msg.sendToTarget(); - try { - lock.wait(); - } catch (InterruptedException e) {} - } - } - - public static TestHandler makeHandler() { - final HandlerThread handlerThread = new HandlerThread("TestHandler"); - handlerThread.start(); - final TestHandler result = new TestHandler(handlerThread.getLooper()); - result.handlerThread = handlerThread; - return result; - } - - private boolean objectEquals(Object o1, Object o2) { - if (o1 == null) return (o2 == null); - return o1.equals(o2); - } - - private void failAndStack(String str) { - SubscriptionMonitorTest.failAndStack(str); - } - - @Override - public void handleMessage(Message msg) { - switch (msg.what) { - case ACTIVE_PHONE_SWITCH: { - AsyncResult ar = (AsyncResult)(msg.obj); - if (objectEquals(ar.userObj, mActivePhoneSwitchObject.get()) == false) { - failAndStack("Active Phone Switch object is incorrect!"); - } - int count = mActivePhoneSwitchCount.incrementAndGet(); - Rlog.d(LOG_TAG, "ACTIVE_PHONE_SWITCH, inc to " + count); - break; - } - case IN_IDLE: { - Object lock = msg.obj; - synchronized (lock) { - lock.notify(); - } - break; - } - } - } - - private final AtomicInteger mActivePhoneSwitchCount = new AtomicInteger(0); - private final AtomicReference mActivePhoneSwitchObject = - new AtomicReference(); - - public void reset() { - mActivePhoneSwitchCount.set(0); - mActivePhoneSwitchObject.set(null); - } - - public void setActivePhoneSwitchObject(Object o) { - mActivePhoneSwitchObject.set(o); - } - - public int getActivePhoneSwitchCount() { - return mActivePhoneSwitchCount.get(); - } - } + private static final int ACTIVE_PHONE_SWITCH = 1; - private void waitABit() { - try { - Thread.sleep(250); - } catch (Exception e) {} - } - - private String mTestName = ""; @Mock private ITelephonyRegistry.Stub mTelRegistryMock; @Mock @@ -175,35 +64,21 @@ public class PhoneSwitcherTest extends TelephonyTest { @Mock private CommandsInterface mCommandsInterface1; @Mock - private IConnectivityManager.Stub mConnectivityService; - - - private void log(String str) { - Rlog.d(LOG_TAG + " " + mTestName, str); - } - - private NetworkRequest makeDefaultRequest(ConnectivityServiceMock cs, Integer subId) { - NetworkCapabilities netCap = (new NetworkCapabilities()). - addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET). - addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED). - addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR); - if (subId != null) { - netCap.setNetworkSpecifier(new StringNetworkSpecifier(Integer.toString(subId))); - } - return cs.requestNetwork(netCap, null, 0, new Binder(), -1); - } - - private NetworkRequest makeMmsRequest(ConnectivityServiceMock cs, Integer subId) { - NetworkCapabilities netCap = (new NetworkCapabilities()). - addCapability(NetworkCapabilities.NET_CAPABILITY_MMS). - addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED). - addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR); - netCap.setNetworkSpecifier(new StringNetworkSpecifier(Integer.toString(subId))); - if (subId != null) { - netCap.setNetworkSpecifier(new StringNetworkSpecifier(Integer.toString(subId))); - } - return cs.requestNetwork(netCap, null, 0, new Binder(), -1); - } + private Phone mPhone2; // mPhone as phone 1 is already defined in TelephonyTest. + @Mock + private Handler mActivePhoneSwitchHandler; + + // The thread that mPhoneSwitcher will handle events in. + private HandlerThread mHandlerThread; + private PhoneSwitcher mPhoneSwitcher; + private IOnSubscriptionsChangedListener mSubChangedListener; + private ConnectivityManager mConnectivityManager; + // The messenger of PhoneSwitcher used to receive network requests. + private Messenger mNetworkFactoryMessenger = null; + private int mDefaultDataSub = SubscriptionManager.INVALID_SUBSCRIPTION_ID; + private CommandsInterface[] mCommandsInterfaces; + private int[][] mSlotIndexToSubId; + private boolean[] mDataAllowed; @Before public void setUp() throws Exception { @@ -221,87 +96,51 @@ public class PhoneSwitcherTest extends TelephonyTest { @Test @SmallTest public void testRegister() throws Exception { - mTestName = "testRegister"; final int numPhones = 2; final int maxActivePhones = 1; - final HandlerThread handlerThread = new HandlerThread("PhoneSwitcherTestThread"); - handlerThread.start(); - mContextFixture.putStringArrayResource(com.android.internal.R.array.networkAttributes, - sNetworkAttributes); - final Context contextMock = mContextFixture.getTestDouble(); - final ConnectivityServiceMock connectivityServiceMock = - new ConnectivityServiceMock(contextMock); - final ConnectivityManager cm = - new ConnectivityManager(contextMock, connectivityServiceMock); - mContextFixture.setSystemService(Context.CONNECTIVITY_SERVICE, cm); - final ITelephonyRegistry.Stub telRegistryMock = new TelephonyRegistryMock(); - final SubscriptionControllerMock subControllerMock = - new SubscriptionControllerMock(contextMock, telRegistryMock, numPhones); - final SimulatedCommands[] commandsInterfaces = new SimulatedCommands[numPhones]; - final PhoneMock[] phones = new PhoneMock[numPhones]; - for (int i = 0; i < numPhones; i++) { - commandsInterfaces[i] = new SimulatedCommands(); - // phones[i] = new PhoneMock(contextMock, commandsInterfaces[i]); - } - - PhoneSwitcher phoneSwitcher = new PhoneSwitcher(maxActivePhones, numPhones, - contextMock, subControllerMock, handlerThread.getLooper(), telRegistryMock, - commandsInterfaces, phones); + initialize(numPhones, maxActivePhones); // verify nothing has been done while there are no inputs - if (commandsInterfaces[0].isDataAllowed()) fail("data allowed initially"); - if (phoneSwitcher.isPhoneActive(0)) fail("phone active initially"); + assertFalse("data allowed initially", mDataAllowed[0]); + assertFalse("data allowed initially", mDataAllowed[0]); + assertFalse("phone active initially", mPhoneSwitcher.isPhoneActive(0)); - connectivityServiceMock.addDefaultRequest(); + NetworkRequest internetNetworkRequest = addInternetNetworkRequest(null, 50); waitABit(); - if (commandsInterfaces[0].isDataAllowed()) fail("data allowed after request"); - if (phoneSwitcher.isPhoneActive(0)) fail("phone active after request"); - - TestHandler testHandler = TestHandler.makeHandler(); - Object activePhoneSwitchObject = new Object(); - testHandler.setActivePhoneSwitchObject(activePhoneSwitchObject); - - testHandler.blockTilIdle(); + assertFalse("data allowed after request", mDataAllowed[0]); + assertFalse("phone active after request", mPhoneSwitcher.isPhoneActive(0)); // not registered yet - shouldn't inc - if (testHandler.getActivePhoneSwitchCount() != 0) { - fail("pretest of ActivePhoneSwitchCount"); - } + verify(mActivePhoneSwitchHandler, never()).sendMessageAtTime(any(), anyLong()); + boolean threw = false; try { // should throw - phoneSwitcher.registerForActivePhoneSwitch(2, testHandler, - TestHandler.ACTIVE_PHONE_SWITCH, activePhoneSwitchObject); + mPhoneSwitcher.registerForActivePhoneSwitch(2, mActivePhoneSwitchHandler, + ACTIVE_PHONE_SWITCH, null); } catch (IllegalArgumentException e) { threw = true; } - if (threw == false) fail("register with bad phoneId didn't throw"); + assertTrue("register with bad phoneId didn't throw", threw); - phoneSwitcher.registerForActivePhoneSwitch(0, testHandler, - TestHandler.ACTIVE_PHONE_SWITCH, - activePhoneSwitchObject); - testHandler.blockTilIdle(); + mPhoneSwitcher.registerForActivePhoneSwitch(0, mActivePhoneSwitchHandler, + ACTIVE_PHONE_SWITCH, null); - if (testHandler.getActivePhoneSwitchCount() != 1) { - fail("post register of ActivePhoneSwitchCount not 1!"); - } + verify(mActivePhoneSwitchHandler, times(1)).sendMessageAtTime(any(), anyLong()); - subControllerMock.setDefaultDataSubId(0); - testHandler.blockTilIdle(); - if (testHandler.getActivePhoneSwitchCount() != 1) { - fail("after set of default to 0, ActivePhoneSwitchCount not 1!"); - } - if (commandsInterfaces[0].isDataAllowed()) fail("data allowed"); + setDefaultDataSubId(0); + + verify(mActivePhoneSwitchHandler, times(1)).sendMessageAtTime(any(), anyLong()); + assertFalse("data allowed", mDataAllowed[0]); - subControllerMock.setSlotSubId(0, 0); + setSlotIndexToSubId(0, 0); + mSubChangedListener.onSubscriptionsChanged(); waitABit(); - if (testHandler.getActivePhoneSwitchCount() != 2) { - fail("after mapping of 0 to 0, ActivePhoneSwitchCount not 2!"); - } - if (commandsInterfaces[0].isDataAllowed() == false) fail("data not allowed"); + verify(mActivePhoneSwitchHandler, times(2)).sendMessageAtTime(any(), anyLong()); + assertTrue("data not allowed", mDataAllowed[0]); // now try various things that should cause the active phone to switch: // 1 lose default via default sub change @@ -316,93 +155,87 @@ public class PhoneSwitcherTest extends TelephonyTest { // 10 don't switch phones when in emergency mode // 1 lose default via default sub change - subControllerMock.setDefaultDataSubId(1); + setDefaultDataSubId(1); waitABit(); - if (testHandler.getActivePhoneSwitchCount() != 3) { - fail("after set of default to 1, ActivePhoneSwitchCount not 3!"); - } - if (commandsInterfaces[0].isDataAllowed()) fail("data allowed"); - subControllerMock.setSlotSubId(1, 1); + verify(mActivePhoneSwitchHandler, times(3)).sendMessageAtTime(any(), anyLong()); + assertFalse("data allowed", mDataAllowed[0]); + + setSlotIndexToSubId(1, 1); + mSubChangedListener.onSubscriptionsChanged(); waitABit(); - if (testHandler.getActivePhoneSwitchCount() != 3) { - fail("after mapping of 1 to 1, ActivePhoneSwitchCount not 3!"); - } - if (commandsInterfaces[0].isDataAllowed()) fail("data allowed"); - if (commandsInterfaces[1].isDataAllowed() == false) fail("data not allowed"); + + verify(mActivePhoneSwitchHandler, times(3)).sendMessageAtTime(any(), anyLong()); + assertFalse("data allowed", mDataAllowed[0]); + assertTrue("data not allowed", mDataAllowed[1]); // 2 gain default via default sub change - subControllerMock.setDefaultDataSubId(0); + setDefaultDataSubId(0); waitABit(); - if (testHandler.getActivePhoneSwitchCount() != 4) { - fail("after set of default to 0, ActivePhoneSwitchCount not 4!"); - } - if (commandsInterfaces[1].isDataAllowed()) fail("data allowed"); - if (commandsInterfaces[0].isDataAllowed() == false) fail("data not allowed"); + + verify(mActivePhoneSwitchHandler, times(4)).sendMessageAtTime(any(), anyLong()); + assertFalse("data allowed", mDataAllowed[1]); + assertTrue("data not allowed", mDataAllowed[0]); // 3 lose default via sub->phone change - subControllerMock.setSlotSubId(0, 2); + setSlotIndexToSubId(0, 2); + mSubChangedListener.onSubscriptionsChanged(); waitABit(); - if (testHandler.getActivePhoneSwitchCount() != 5) { - fail("after mapping of 0 to 2, ActivePhoneSwitchCount not 5!"); - } - if (commandsInterfaces[0].isDataAllowed()) fail("data allowed"); - if (commandsInterfaces[1].isDataAllowed()) fail("data allowed"); + verify(mActivePhoneSwitchHandler, times(5)).sendMessageAtTime(any(), anyLong()); + assertFalse("data allowed", mDataAllowed[0]); + assertFalse("data allowed", mDataAllowed[1]); // 4 gain default via sub->phone change - subControllerMock.setSlotSubId(0, 0); + setSlotIndexToSubId(0, 0); + mSubChangedListener.onSubscriptionsChanged(); waitABit(); - if (testHandler.getActivePhoneSwitchCount() != 6) { - fail("after mapping of 0 to 0, ActivePhoneSwitchCount not 6!"); - } - if (commandsInterfaces[0].isDataAllowed() == false) fail("data not allowed"); - if (commandsInterfaces[1].isDataAllowed()) fail("data allowed"); + + verify(mActivePhoneSwitchHandler, times(6)).sendMessageAtTime(any(), anyLong()); + assertTrue("data not allowed", mDataAllowed[0]); + assertFalse("data allowed", mDataAllowed[1]); // 5 lose default network request - connectivityServiceMock.removeDefaultRequest(); + releaseNetworkRequest(internetNetworkRequest); waitABit(); - if (testHandler.getActivePhoneSwitchCount() != 7) { - fail("after loss of network request, ActivePhoneSwitchCount not 7!"); - } - if (commandsInterfaces[0].isDataAllowed()) fail("data allowed"); - if (commandsInterfaces[1].isDataAllowed()) fail("data allowed"); + + verify(mActivePhoneSwitchHandler, times(7)).sendMessageAtTime(any(), anyLong()); + assertFalse("data allowed", mDataAllowed[0]); + assertFalse("data allowed", mDataAllowed[1]); // 6 gain subscription-specific request - NetworkRequest request = makeDefaultRequest(connectivityServiceMock, 0); + NetworkRequest specificInternetRequest = addInternetNetworkRequest(0, 50); waitABit(); - if (testHandler.getActivePhoneSwitchCount() != 8) { - fail("after gain of network request, ActivePhoneSwitchCount not 8!"); - } - if (commandsInterfaces[0].isDataAllowed() == false) fail("data not allowed"); - if (commandsInterfaces[1].isDataAllowed()) fail("data allowed"); + + verify(mActivePhoneSwitchHandler, times(8)).sendMessageAtTime(any(), anyLong()); + assertTrue("data not allowed", mDataAllowed[0]); + assertFalse("data allowed", mDataAllowed[1]); // 7 lose via sub->phone change - subControllerMock.setSlotSubId(0, 1); + setSlotIndexToSubId(0, 1); + mSubChangedListener.onSubscriptionsChanged(); waitABit(); - if (testHandler.getActivePhoneSwitchCount() != 9) { - fail("after loss of request due to subId map change, ActivePhoneSwitchCount not 9!"); - } - if (commandsInterfaces[0].isDataAllowed()) fail("data allowed"); - if (commandsInterfaces[1].isDataAllowed()) fail("data allowed"); + + verify(mActivePhoneSwitchHandler, times(9)).sendMessageAtTime(any(), anyLong()); + assertFalse("data allowed", mDataAllowed[0]); + assertFalse("data allowed", mDataAllowed[1]); // 8 gain via sub->phone change - subControllerMock.setSlotSubId(0, 0); + setSlotIndexToSubId(0, 0); + mSubChangedListener.onSubscriptionsChanged(); waitABit(); - if (testHandler.getActivePhoneSwitchCount() != 10) { - fail("after gain of request due to subId map change, ActivePhoneSwitchCount not 10!"); - } - if (commandsInterfaces[0].isDataAllowed() == false) fail("data not allowed"); - if (commandsInterfaces[1].isDataAllowed()) fail("data allowed"); + + verify(mActivePhoneSwitchHandler, times(10)).sendMessageAtTime(any(), anyLong()); + assertTrue("data not allowed", mDataAllowed[0]); + assertFalse("data allowed", mDataAllowed[1]); // 9 lose subscription-specific request - connectivityServiceMock.releaseNetworkRequest(request); + releaseNetworkRequest(specificInternetRequest); waitABit(); - if (testHandler.getActivePhoneSwitchCount() != 11) { - fail("after release of request, ActivePhoneSwitchCount not 11!"); - } - if (commandsInterfaces[0].isDataAllowed()) fail("data allowed"); - if (commandsInterfaces[1].isDataAllowed()) fail("data allowed"); + + verify(mActivePhoneSwitchHandler, times(11)).sendMessageAtTime(any(), anyLong()); + assertFalse("data allowed", mDataAllowed[0]); + assertFalse("data allowed", mDataAllowed[1]); // 10 don't switch phones when in emergency mode // not ready yet - Phone turns out to be hard to stub out @@ -424,13 +257,7 @@ public class PhoneSwitcherTest extends TelephonyTest { // if (commandsInterfaces[0].isDataAllowed()) fail("data allowed"); // if (commandsInterfaces[1].isDataAllowed()) fail("data allowed"); - for (int i = 0; i < numPhones; i++) { - commandsInterfaces[i].dispose(); - } - - connectivityServiceMock.die(); - testHandler.die(); - handlerThread.quit(); + mHandlerThread.quit(); } /** @@ -452,68 +279,34 @@ public class PhoneSwitcherTest extends TelephonyTest { @Test @SmallTest public void testPrioritization() throws Exception { - mTestName = "testPrioritization"; final int numPhones = 2; final int maxActivePhones = 1; - final HandlerThread handlerThread = new HandlerThread("PhoneSwitcherTestThread"); - handlerThread.start(); - mContextFixture.putStringArrayResource(com.android.internal.R.array.networkAttributes, - sNetworkAttributes); - final Context contextMock = mContextFixture.getTestDouble(); - final ConnectivityServiceMock connectivityServiceMock = - new ConnectivityServiceMock(contextMock); - final ConnectivityManager cm = - new ConnectivityManager(contextMock, connectivityServiceMock); - mContextFixture.setSystemService(Context.CONNECTIVITY_SERVICE, cm); - final ITelephonyRegistry.Stub telRegistryMock = new TelephonyRegistryMock(); - final SubscriptionControllerMock subControllerMock = - new SubscriptionControllerMock(contextMock, telRegistryMock, numPhones); - final SimulatedCommands[] commandsInterfaces = new SimulatedCommands[numPhones]; - final PhoneMock[] phones = new PhoneMock[numPhones]; - for (int i = 0; i < numPhones; i++) { - commandsInterfaces[i] = new SimulatedCommands(); - } - - PhoneSwitcher phoneSwitcher = new PhoneSwitcher(maxActivePhones, numPhones, - contextMock, subControllerMock, handlerThread.getLooper(), telRegistryMock, - commandsInterfaces, phones); - - TestHandler testHandler = TestHandler.makeHandler(); - Object activePhoneSwitchObject = new Object(); - testHandler.setActivePhoneSwitchObject(activePhoneSwitchObject); + initialize(numPhones, maxActivePhones); - connectivityServiceMock.addDefaultRequest(); - subControllerMock.setSlotSubId(0, 0); - subControllerMock.setSlotSubId(1, 1); - subControllerMock.setDefaultDataSubId(0); + addInternetNetworkRequest(null, 50); + setSlotIndexToSubId(0, 0); + setSlotIndexToSubId(1, 1); + setDefaultDataSubId(0); waitABit(); - phoneSwitcher.registerForActivePhoneSwitch(0, testHandler, TestHandler.ACTIVE_PHONE_SWITCH, - activePhoneSwitchObject); + mPhoneSwitcher.registerForActivePhoneSwitch(0, mActivePhoneSwitchHandler, + ACTIVE_PHONE_SWITCH, null); waitABit(); // verify initial conditions - if (testHandler.getActivePhoneSwitchCount() != 1) { - fail("Initial conditions not met: ActivePhoneSwitchCount not 1! " + - testHandler.getActivePhoneSwitchCount()); - } - if (commandsInterfaces[0].isDataAllowed() == false) fail("data not allowed"); - if (commandsInterfaces[1].isDataAllowed()) fail("data allowed"); + verify(mActivePhoneSwitchHandler, times(1)).sendMessageAtTime(any(), anyLong()); + + assertTrue("data not allowed", mDataAllowed[0]); + assertFalse("data allowed", mDataAllowed[1]); // now start a higher priority conneciton on the other sub - NetworkRequest request = makeMmsRequest(connectivityServiceMock, 1); + addMmsNetworkRequest(1); waitABit(); - if (testHandler.getActivePhoneSwitchCount() != 2) { - fail("after gain of network request, ActivePhoneSwitchCount not 2!"); - } - if (commandsInterfaces[0].isDataAllowed()) fail("data allowed"); - if (commandsInterfaces[1].isDataAllowed() == false) fail("data not allowed"); - for (int i = 0; i < numPhones; i++) { - commandsInterfaces[i].dispose(); - } + // After gain of network request, mActivePhoneSwitchHandler should be notified 2 times. + verify(mActivePhoneSwitchHandler, times(2)).sendMessageAtTime(any(), anyLong()); + assertFalse("data allowed", mDataAllowed[0]); + assertTrue("data not allowed", mDataAllowed[1]); - connectivityServiceMock.die(); - testHandler.die(); - handlerThread.quit(); + mHandlerThread.quit(); } /** @@ -523,69 +316,35 @@ public class PhoneSwitcherTest extends TelephonyTest { @Test @SmallTest public void testHigherPriorityDefault() throws Exception { - mTestName = "testPrioritization"; final int numPhones = 2; final int maxActivePhones = 1; - final HandlerThread handlerThread = new HandlerThread("PhoneSwitcherTestThread"); - handlerThread.start(); - mContextFixture.putStringArrayResource(com.android.internal.R.array.networkAttributes, - sNetworkAttributes); - final Context contextMock = mContextFixture.getTestDouble(); - final ConnectivityServiceMock connectivityServiceMock = - new ConnectivityServiceMock(contextMock); - final ConnectivityManager cm = - new ConnectivityManager(contextMock, connectivityServiceMock); - mContextFixture.setSystemService(Context.CONNECTIVITY_SERVICE, cm); - final ITelephonyRegistry.Stub telRegistryMock = new TelephonyRegistryMock(); - final SubscriptionControllerMock subControllerMock = - new SubscriptionControllerMock(contextMock, telRegistryMock, numPhones); - final SimulatedCommands[] commandsInterfaces = new SimulatedCommands[numPhones]; - final PhoneMock[] phones = new PhoneMock[numPhones]; - for (int i = 0; i < numPhones; i++) { - commandsInterfaces[i] = new SimulatedCommands(); - } + initialize(numPhones, maxActivePhones); - PhoneSwitcher phoneSwitcher = new PhoneSwitcher(maxActivePhones, numPhones, - contextMock, subControllerMock, handlerThread.getLooper(), telRegistryMock, - commandsInterfaces, phones); - - TestHandler testHandler = TestHandler.makeHandler(); - Object activePhoneSwitchObject = new Object(); - testHandler.setActivePhoneSwitchObject(activePhoneSwitchObject); - - connectivityServiceMock.addDefaultRequest(); - subControllerMock.setSlotSubId(0, 0); - subControllerMock.setSlotSubId(1, 1); - subControllerMock.setDefaultDataSubId(0); + addInternetNetworkRequest(null, 50); + setSlotIndexToSubId(0, 0); + setSlotIndexToSubId(1, 1); + setDefaultDataSubId(0); waitABit(); // Phone 0 should be active - if (commandsInterfaces[0].isDataAllowed() == false) fail("data not allowed"); - if (commandsInterfaces[1].isDataAllowed()) fail("data allowed"); + assertTrue("data not allowed", mDataAllowed[0]); + assertFalse("data allowed", mDataAllowed[1]); - connectivityServiceMock.setCurrentScoreForRequest(connectivityServiceMock.defaultRequest, - 100); + addInternetNetworkRequest(null, 100); waitABit(); // should be no change - if (commandsInterfaces[0].isDataAllowed() == false) fail("data not allowed"); - if (commandsInterfaces[1].isDataAllowed()) fail("data allowed"); + assertTrue("data not allowed", mDataAllowed[0]); + assertFalse("data allowed", mDataAllowed[1]); - connectivityServiceMock.setCurrentScoreForRequest(connectivityServiceMock.defaultRequest, - 0); + addInternetNetworkRequest(null, 0); waitABit(); // should be no change - if (commandsInterfaces[0].isDataAllowed() == false) fail("data not allowed"); - if (commandsInterfaces[1].isDataAllowed()) fail("data allowed"); - - for (int i = 0; i < numPhones; i++) { - commandsInterfaces[i].dispose(); - } + assertTrue("data not allowed", mDataAllowed[0]); + assertFalse("data allowed", mDataAllowed[1]); - connectivityServiceMock.die(); - testHandler.die(); - handlerThread.quit(); + mHandlerThread.quit(); } /** @@ -597,70 +356,234 @@ public class PhoneSwitcherTest extends TelephonyTest { @Test @SmallTest public void testSetPreferredData() throws Exception { - mTestName = "testSetPreferredData"; final int numPhones = 2; final int maxActivePhones = 1; - mContextFixture.putStringArrayResource(com.android.internal.R.array.networkAttributes, - sNetworkAttributes); - final CommandsInterface[] commandsInterfaces = new CommandsInterface[numPhones]; - commandsInterfaces[0] = mCommandsInterface0; - commandsInterfaces[1] = mCommandsInterface1; - - final ConnectivityServiceMock connectivityServiceMock = - new ConnectivityServiceMock(mContext); - final ConnectivityManager cm = - new ConnectivityManager(mContext, connectivityServiceMock); - mContextFixture.setSystemService(Context.CONNECTIVITY_SERVICE, cm); - final HandlerThread handlerThread = new HandlerThread("PhoneSwitcherTestThread"); - final PhoneMock[] phones = new PhoneMock[numPhones]; + initialize(numPhones, maxActivePhones); // Phone 0 has sub 1, phone 1 has sub 2. // Sub 1 is default data sub. // Both are active subscriptions are active sub, as they are in both active slots. - doReturn(1).when(mSubscriptionController) - .getDefaultDataSubId(); - doReturn(1).when(mSubscriptionController) - .getSubIdUsingPhoneId(0); - doReturn(2).when(mSubscriptionController) - .getSubIdUsingPhoneId(1); - doReturn(true).when(mSubscriptionController) - .isActiveSubId(1); - doReturn(true).when(mSubscriptionController) - .isActiveSubId(2); - - handlerThread.start(); - - PhoneSwitcher phoneSwitcher = new PhoneSwitcher(maxActivePhones, numPhones, - mContext, mSubscriptionController, handlerThread.getLooper(), mTelRegistryMock, - commandsInterfaces, phones); + setSlotIndexToSubId(0, 1); + setSlotIndexToSubId(1, 2); + setDefaultDataSubId(1); // Notify phoneSwitcher about default data sub and default network request. - sendDefaultDataSubChanged(); - NetworkRequest request = makeDefaultRequest(connectivityServiceMock, null); + addInternetNetworkRequest(null, 50); waitABit(); // Phone 0 (sub 1) should be activated as it has default data sub. - verify(mCommandsInterface0).setDataAllowed(eq(true), any()); - reset(mCommandsInterface0); + assertTrue(mDataAllowed[0]); // Set sub 2 as preferred sub should make phone 1 activated and phone 0 deactivated. - phoneSwitcher.setPreferredData(2); + mPhoneSwitcher.setPreferredData(2); waitABit(); - verify(mCommandsInterface0).setDataAllowed(eq(false), any()); - verify(mCommandsInterface1).setDataAllowed(eq(true), any()); - reset(mCommandsInterface0); - reset(mCommandsInterface1); + assertFalse(mDataAllowed[0]); + assertTrue(mDataAllowed[1]); // Unset preferred sub should make default data sub (phone 0 / sub 1) activated again. - phoneSwitcher.setPreferredData(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID); + mPhoneSwitcher.setPreferredData(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID); waitABit(); - verify(mCommandsInterface0, times(1)).setDataAllowed(eq(true), any()); - verify(mCommandsInterface1, times(1)).setDataAllowed(eq(false), any()); + assertTrue(mDataAllowed[0]); + assertFalse(mDataAllowed[1]); - handlerThread.quit(); + mHandlerThread.quit(); } + /* Private utility methods start here */ + private void sendDefaultDataSubChanged() { final Intent intent = new Intent(TelephonyIntents.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED); mContext.sendBroadcast(intent); } + + private void initialize(int numPhones, int maxActivePhones) throws Exception { + mHandlerThread = new HandlerThread("PhoneSwitcherTestThread"); + mHandlerThread.start(); + + mContextFixture.putStringArrayResource(com.android.internal.R.array.networkAttributes, + sNetworkAttributes); + + setNumPhones(numPhones); + + initializeSubControllerMock(); + initializeCommandInterfacesMock(numPhones); + initializeTelRegistryMock(); + initializeConnManagerMock(); + + mPhoneSwitcher = new PhoneSwitcher(maxActivePhones, numPhones, + mContext, mSubscriptionController, mHandlerThread.getLooper(), + mTelRegistryMock, mCommandsInterfaces, mPhones); + + verify(mTelRegistryMock).addOnSubscriptionsChangedListener( + eq(mContext.getOpPackageName()), any()); + } + + /** + * Certain variables needs initialized depending on number of phones. + */ + private void setNumPhones(int numPhones) { + mDataAllowed = new boolean[numPhones]; + mSlotIndexToSubId = new int[numPhones][]; + for (int i = 0; i < numPhones; i++) { + mSlotIndexToSubId[i] = new int[1]; + mSlotIndexToSubId[i][0] = SubscriptionManager.INVALID_SUBSCRIPTION_ID; + } + + if (numPhones == 1) { + mCommandsInterfaces = new CommandsInterface[] {mCommandsInterface0}; + mPhones = new Phone[] {mPhone}; + } else if (numPhones == 2) { + mCommandsInterfaces = + new CommandsInterface[] {mCommandsInterface0, mCommandsInterface1}; + mPhones = new Phone[] {mPhone, mPhone2}; + } + } + + private void initializeCommandInterfacesMock(int numPhones) { + // Tell PhoneSwitcher that radio is on. + doAnswer(invocation -> { + Handler handler = (Handler) invocation.getArguments()[0]; + int message = (int) invocation.getArguments()[1]; + Object obj = invocation.getArguments()[2]; + handler.obtainMessage(message, obj).sendToTarget(); + return null; + }).when(mCommandsInterface0).registerForAvailable(any(), anyInt(), any()); + + // Store values of dataAllowed in mDataAllowed[] for easier checking. + doAnswer(invocation -> { + mDataAllowed[0] = (boolean) invocation.getArguments()[0]; + return null; + }).when(mCommandsInterface0).setDataAllowed(anyBoolean(), any()); + + if (numPhones == 2) { + doAnswer(invocation -> { + mDataAllowed[1] = (boolean) invocation.getArguments()[0]; + return null; + }).when(mCommandsInterface1).setDataAllowed(anyBoolean(), any()); + } + } + + /** + * Store subChangedListener of PhoneSwitcher so that testing can notify + * PhoneSwitcher of sub change. + */ + private void initializeTelRegistryMock() throws Exception { + doAnswer(invocation -> { + IOnSubscriptionsChangedListener subChangedListener = + (IOnSubscriptionsChangedListener) invocation.getArguments()[1]; + mSubChangedListener = subChangedListener; + mSubChangedListener.onSubscriptionsChanged(); + return null; + }).when(mTelRegistryMock).addOnSubscriptionsChangedListener(any(), any()); + } + + /** + * Capture mNetworkFactoryMessenger so that testing can request or release + * network requests on PhoneSwitcher. + */ + private void initializeConnManagerMock() { + mConnectivityManager = (ConnectivityManager) + mContext.getSystemService(Context.CONNECTIVITY_SERVICE); + + doAnswer(invocation -> { + mNetworkFactoryMessenger = invocation.getArgument(0); + return null; + }).when(mConnectivityManager).registerNetworkFactory(any(), any()); + } + + /** + * Capture mNetworkFactoryMessenger so that testing can request or release + * network requests on PhoneSwitcher. + */ + private void initializeSubControllerMock() { + doReturn(mDefaultDataSub).when(mSubscriptionController).getDefaultDataSubId(); + doAnswer(invocation -> { + int phoneId = (int) invocation.getArguments()[0]; + return mSlotIndexToSubId[phoneId][0]; + }).when(mSubscriptionController).getSubIdUsingPhoneId(anyInt()); + + doAnswer(invocation -> { + int subId = (int) invocation.getArguments()[0]; + + if (!SubscriptionManager.isUsableSubIdValue(subId)) return false; + + for (int i = 0; i < mSlotIndexToSubId.length; i++) { + if (mSlotIndexToSubId[i][0] == subId) return true; + } + return false; + }).when(mSubscriptionController).isActiveSubId(anyInt()); + } + + private void setDefaultDataSubId(int defaultDataSub) { + mDefaultDataSub = defaultDataSub; + doReturn(mDefaultDataSub).when(mSubscriptionController).getDefaultDataSubId(); + sendDefaultDataSubChanged(); + } + + private void setSlotIndexToSubId(int slotId, int subId) { + mSlotIndexToSubId[slotId][0] = subId; + } + + /** + * Create an internet PDN network request and send it to PhoneSwitcher. + */ + private NetworkRequest addInternetNetworkRequest(Integer subId, int score) throws Exception { + NetworkCapabilities netCap = (new NetworkCapabilities()) + .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) + .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED) + .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR); + if (subId != null) { + netCap.setNetworkSpecifier(new StringNetworkSpecifier(Integer.toString(subId))); + } + NetworkRequest networkRequest = new NetworkRequest(netCap, ConnectivityManager.TYPE_NONE, + 0, NetworkRequest.Type.REQUEST); + + Message message = Message.obtain(); + message.what = android.net.NetworkFactory.CMD_REQUEST_NETWORK; + message.arg1 = score; + message.obj = networkRequest; + mNetworkFactoryMessenger.send(message); + + return networkRequest; + } + + /** + * Create a mms PDN network request and send it to PhoneSwitcher. + */ + private NetworkRequest addMmsNetworkRequest(Integer subId) throws Exception { + NetworkCapabilities netCap = (new NetworkCapabilities()) + .addCapability(NetworkCapabilities.NET_CAPABILITY_MMS) + .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED) + .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR); + netCap.setNetworkSpecifier(new StringNetworkSpecifier(Integer.toString(subId))); + if (subId != null) { + netCap.setNetworkSpecifier(new StringNetworkSpecifier(Integer.toString(subId))); + } + NetworkRequest networkRequest = new NetworkRequest(netCap, ConnectivityManager.TYPE_NONE, + 1, NetworkRequest.Type.REQUEST); + + Message message = Message.obtain(); + message.what = android.net.NetworkFactory.CMD_REQUEST_NETWORK; + message.arg1 = 50; // Score + message.obj = networkRequest; + mNetworkFactoryMessenger.send(message); + + return networkRequest; + } + + /** + * Tell PhoneSwitcher to release a network request. + */ + private void releaseNetworkRequest(NetworkRequest networkRequest) throws Exception { + Message message = Message.obtain(); + message.what = android.net.NetworkFactory.CMD_CANCEL_REQUEST; + message.obj = networkRequest; + mNetworkFactoryMessenger.send(message); + } + + private void waitABit() { + try { + Thread.sleep(250); + } catch (Exception e) { + } + } } -- cgit v1.2.3 From 405aeecc518a46e18a9df851444c73ba055aa73f Mon Sep 17 00:00:00 2001 From: Malcolm Chen Date: Mon, 27 Aug 2018 20:41:54 -0700 Subject: Add TelephonyRegistryTest. It's unit-test of TelephonyRegistry module. In first step it will only have testPhoneCapabilityChanged, as it's new functionality recently added. More tests will be added later. Bug: 113305153 Test: unittest Change-Id: I23648e164d4afdb682feb9ec6d8ca75097b550c3 Merged-In: I23648e164d4afdb682feb9ec6d8ca75097b550c3 --- .../android/internal/telephony/ContextFixture.java | 2 + .../internal/telephony/TelephonyRegistryTest.java | 100 +++++++++++++++++++++ 2 files changed, 102 insertions(+) create mode 100644 tests/telephonytests/src/com/android/internal/telephony/TelephonyRegistryTest.java diff --git a/tests/telephonytests/src/com/android/internal/telephony/ContextFixture.java b/tests/telephonytests/src/com/android/internal/telephony/ContextFixture.java index c671f4fe26..6313633cc3 100644 --- a/tests/telephonytests/src/com/android/internal/telephony/ContextFixture.java +++ b/tests/telephonytests/src/com/android/internal/telephony/ContextFixture.java @@ -259,6 +259,8 @@ public class ContextFixture implements TestFixture { public String getSystemServiceName(Class serviceClass) { if (serviceClass == SubscriptionManager.class) { return Context.TELEPHONY_SUBSCRIPTION_SERVICE; + } else if (serviceClass == AppOpsManager.class) { + return Context.APP_OPS_SERVICE; } return super.getSystemServiceName(serviceClass); } diff --git a/tests/telephonytests/src/com/android/internal/telephony/TelephonyRegistryTest.java b/tests/telephonytests/src/com/android/internal/telephony/TelephonyRegistryTest.java new file mode 100644 index 0000000000..42a8ad1a74 --- /dev/null +++ b/tests/telephonytests/src/com/android/internal/telephony/TelephonyRegistryTest.java @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2018 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; + +import static android.telephony.PhoneStateListener.LISTEN_PHONE_CAPABILITY_CHANGE; + +import static org.junit.Assert.assertEquals; + +import android.os.HandlerThread; +import android.os.ServiceManager; +import android.telephony.PhoneCapability; +import android.telephony.PhoneStateListener; +import android.test.suitebuilder.annotation.SmallTest; + +import com.android.server.TelephonyRegistry; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; + +public class TelephonyRegistryTest extends TelephonyTest { + @Mock + private ISub.Stub mISubStub; + private PhoneStateListener mPhoneStateListener; + private TelephonyRegistry mTelephonyRegistry; + private PhoneCapability mPhoneCapability; + + public class PhoneStateListenerWrapper extends PhoneStateListener { + @Override + public void onPhoneCapabilityChanged(PhoneCapability capability) { + mPhoneCapability = capability; + setReady(true); + } + } + + private void addTelephonyRegistryService() { + mServiceManagerMockedServices.put("telephony.registry", mTelephonyRegistry.asBinder()); + } + + private HandlerThread mHandlerThread = new HandlerThread("ListenerThread") { + @Override + public void onLooperPrepared() { + mTelephonyRegistry = new TelephonyRegistry(mContext); + addTelephonyRegistryService(); + mPhoneStateListener = new PhoneStateListenerWrapper(); + setReady(true); + } + }; + + @Before + public void setUp() throws Exception { + super.setUp("TelephonyRegistryTest"); + mServiceManagerMockedServices.put("isub", mISubStub); + mHandlerThread.start(); + waitUntilReady(); + assertEquals(mTelephonyRegistry.asBinder(), + ServiceManager.getService("telephony.registry")); + } + + @After + public void tearDown() throws Exception { + mTelephonyRegistry = null; + mHandlerThread.quit(); + super.tearDown(); + } + + @Test @SmallTest + public void testPhoneCapabilityChanged() { + // mTelephonyRegistry.listen with notifyNow = true should trigger callback immediately. + setReady(false); + PhoneCapability phoneCapability = new PhoneCapability(1, 2, 3, null); + mTelephonyRegistry.notifyPhoneCapabilityChanged(phoneCapability); + mTelephonyRegistry.listen(mContext.getOpPackageName(), + mPhoneStateListener.callback, + LISTEN_PHONE_CAPABILITY_CHANGE, true); + waitUntilReady(); + assertEquals(phoneCapability, mPhoneCapability); + + // notifyPhoneCapabilityChanged with a new capability. Callback should be triggered. + setReady(false); + phoneCapability = new PhoneCapability(3, 2, 2, null); + mTelephonyRegistry.notifyPhoneCapabilityChanged(phoneCapability); + waitUntilReady(); + assertEquals(phoneCapability, mPhoneCapability); + } +} -- cgit v1.2.3 From 5b5f0a08c54b0f88ce46af2943c7d4b35c6290cc Mon Sep 17 00:00:00 2001 From: Nazanin Bakhshi Date: Tue, 9 Oct 2018 18:12:22 -0700 Subject: remove extra dumping of UiccProfile from PhoneFactory. Bug: 111994200 Test: compared and validated before/after bugreports Change-Id: Ib9a12d6bd67b7edae94682bb89e8687d6a54a5a5 --- src/java/com/android/internal/telephony/PhoneFactory.java | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/src/java/com/android/internal/telephony/PhoneFactory.java b/src/java/com/android/internal/telephony/PhoneFactory.java index 4c16f272de..b5eb9a7508 100644 --- a/src/java/com/android/internal/telephony/PhoneFactory.java +++ b/src/java/com/android/internal/telephony/PhoneFactory.java @@ -43,7 +43,6 @@ import com.android.internal.telephony.imsphone.ImsPhoneFactory; import com.android.internal.telephony.sip.SipPhone; import com.android.internal.telephony.sip.SipPhoneFactory; import com.android.internal.telephony.uicc.UiccController; -import com.android.internal.telephony.uicc.UiccProfile; import com.android.internal.telephony.util.NotificationChannelController; import com.android.internal.util.IndentingPrintWriter; @@ -457,17 +456,6 @@ public class PhoneFactory { sTelephonyNetworkFactories[i].dump(fd, pw, args); - pw.flush(); - pw.println("++++++++++++++++++++++++++++++++"); - - try { - UiccProfile uiccProfile = (UiccProfile) phone.getIccCard(); - if (uiccProfile != null) { - uiccProfile.dump(fd, pw, args); - } - } catch (Exception e) { - e.printStackTrace(); - } pw.flush(); pw.decreaseIndent(); pw.println("++++++++++++++++++++++++++++++++"); -- cgit v1.2.3