Merge "Add single-pdn arbitration." into klp-dev
diff --git a/src/java/com/android/internal/telephony/CellBroadcastHandler.java b/src/java/com/android/internal/telephony/CellBroadcastHandler.java
index 678f3e0..f7e0e3a 100644
--- a/src/java/com/android/internal/telephony/CellBroadcastHandler.java
+++ b/src/java/com/android/internal/telephony/CellBroadcastHandler.java
@@ -30,15 +30,13 @@
  * completes and our result receiver is called.
  */
 public class CellBroadcastHandler extends WakeLockStateMachine {
-    private final Context mContext;
 
     private CellBroadcastHandler(Context context) {
-        this("CellBroadcastHandler", context);
+        this("CellBroadcastHandler", context, null);
     }
 
-    protected CellBroadcastHandler(String debugTag, Context context) {
-        super(debugTag, context);
-        mContext = context;
+    protected CellBroadcastHandler(String debugTag, Context context, PhoneBase phone) {
+        super(debugTag, context, phone);
     }
 
     /**
diff --git a/src/java/com/android/internal/telephony/ImsSMSDispatcher.java b/src/java/com/android/internal/telephony/ImsSMSDispatcher.java
index 28919a0..d49b974 100644
--- a/src/java/com/android/internal/telephony/ImsSMSDispatcher.java
+++ b/src/java/com/android/internal/telephony/ImsSMSDispatcher.java
@@ -79,6 +79,8 @@
         super.updatePhoneObject(phone);
         mCdmaDispatcher.updatePhoneObject(phone);
         mGsmDispatcher.updatePhoneObject(phone);
+        mGsmInboundSmsHandler.updatePhoneObject(phone);
+        mCdmaInboundSmsHandler.updatePhoneObject(phone);
     }
 
     public void dispose() {
diff --git a/src/java/com/android/internal/telephony/InboundSmsHandler.java b/src/java/com/android/internal/telephony/InboundSmsHandler.java
index 4288fcc..95a650f 100644
--- a/src/java/com/android/internal/telephony/InboundSmsHandler.java
+++ b/src/java/com/android/internal/telephony/InboundSmsHandler.java
@@ -114,6 +114,9 @@
     /** Sent by {@link SmsBroadcastUndelivered} after cleaning the raw table. */
     static final int EVENT_START_ACCEPTING_SMS = 6;
 
+    /** Update phone object */
+    static final int EVENT_UPDATE_PHONE_OBJECT = 7;
+
     /** Wakelock release delay when returning to idle state. */
     private static final int WAKELOCK_TIMEOUT = 3000;
 
@@ -145,21 +148,29 @@
     final WaitingState mWaitingState = new WaitingState();
 
     /** Helper class to check whether storage is available for incoming messages. */
-    protected final SmsStorageMonitor mStorageMonitor;
+    protected SmsStorageMonitor mStorageMonitor;
 
     private final boolean mSmsReceiveDisabled;
 
+    protected PhoneBase mPhone;
+
+    protected CellBroadcastHandler mCellBroadcastHandler;
+
+
     /**
      * Create a new SMS broadcast helper.
      * @param name the class name for logging
      * @param context the context of the phone app
      * @param storageMonitor the SmsStorageMonitor to check for storage availability
      */
-    protected InboundSmsHandler(String name, Context context, SmsStorageMonitor storageMonitor) {
+    protected InboundSmsHandler(String name, Context context, SmsStorageMonitor storageMonitor,
+            PhoneBase phone, CellBroadcastHandler cellBroadcastHandler) {
         super(name);
 
         mContext = context;
         mStorageMonitor = storageMonitor;
+        mPhone = phone;
+        mCellBroadcastHandler = cellBroadcastHandler;
         mResolver = context.getContentResolver();
         mWapPush = new WapPushOverSms(context);
 
@@ -190,6 +201,13 @@
     }
 
     /**
+     * Update the phone object when it changes.
+     */
+    public void updatePhoneObject(PhoneBase phone) {
+        sendMessage(EVENT_UPDATE_PHONE_OBJECT, phone);
+    }
+
+    /**
      * Dispose of the WAP push object and release the wakelock.
      */
     @Override
@@ -208,13 +226,22 @@
     class DefaultState extends State {
         @Override
         public boolean processMessage(Message msg) {
-            String errorText = "processMessage: unhandled message type " + msg.what;
-            if (Build.IS_DEBUGGABLE) {
-                throw new RuntimeException(errorText);
-              } else {
-                loge(errorText);
-                return HANDLED;
-              }
+            switch (msg.what) {
+                case EVENT_UPDATE_PHONE_OBJECT: {
+                    onUpdatePhoneObject((PhoneBase) msg.obj);
+                    break;
+                }
+                default: {
+                    String errorText = "processMessage: unhandled message type " + msg.what;
+                    if (Build.IS_DEBUGGABLE) {
+                        throw new RuntimeException(errorText);
+                    } else {
+                        loge(errorText);
+                    }
+                    break;
+                }
+            }
+            return HANDLED;
         }
     }
 
@@ -455,6 +482,19 @@
             int result, Message response);
 
     /**
+     * Called when the phone changes the default method updates mPhone
+     * mStorageMonitor and mCellBroadcastHandler.updatePhoneObject.
+     * Override if different or other behavior is desired.
+     *
+     * @param phone
+     */
+    protected void onUpdatePhoneObject(PhoneBase phone) {
+        mPhone = phone;
+        mStorageMonitor = mPhone.mSmsStorageMonitor;
+        log("onUpdatePhoneObject: phone=" + mPhone.getClass().getSimpleName());
+    }
+
+    /**
      * Notify interested apps if the framework has rejected an incoming SMS,
      * and send an acknowledge message to the network.
      * @param success indicates that last message was successfully received.
diff --git a/src/java/com/android/internal/telephony/SMSDispatcher.java b/src/java/com/android/internal/telephony/SMSDispatcher.java
index b760d37..64af435 100644
--- a/src/java/com/android/internal/telephony/SMSDispatcher.java
+++ b/src/java/com/android/internal/telephony/SMSDispatcher.java
@@ -341,11 +341,10 @@
         if (ar.exception == null) {
             if (DBG) Rlog.d(TAG, "SMS send complete. Broadcasting intent: " + sentIntent);
 
-            String defaultSmsPackage = Sms.getDefaultSmsPackage(mContext);
-            if (defaultSmsPackage == null ||
-                    !defaultSmsPackage.equals(tracker.mAppInfo.applicationInfo.packageName)) {
-                // Someone other than the default SMS app sent this message. Persist it into the
-                // SMS database as a sent message so the user can see it in their default app.
+            if (SmsApplication.shouldWriteMessageForPackage(
+                    tracker.mAppInfo.applicationInfo.packageName, mContext)) {
+                // Persist it into the SMS database as a sent message
+                // so the user can see it in their default app.
                 tracker.writeSentMessage(mContext);
             }
 
diff --git a/src/java/com/android/internal/telephony/SmsApplication.java b/src/java/com/android/internal/telephony/SmsApplication.java
index dc0bf29..05a12b3 100644
--- a/src/java/com/android/internal/telephony/SmsApplication.java
+++ b/src/java/com/android/internal/telephony/SmsApplication.java
@@ -46,6 +46,7 @@
 public final class SmsApplication {
     static final String LOG_TAG = "SmsApplication";
     private static final String PHONE_PACKAGE_NAME = "com.android.phone";
+    private static final String BLUETOOTH_PACKAGE_NAME = "com.android.bluetooth";
 
     public static class SmsApplicationData {
         /**
@@ -308,9 +309,9 @@
                 }
             }
 
-            // We can only verify the phone app's permissions from a privileged caller
+            // We can only verify the phone and BT app's permissions from a privileged caller
             if (updateIfNeeded) {
-                // Verify that the phone app has permissions
+                // Verify that the phone and BT app has permissions
                 PackageManager packageManager = context.getPackageManager();
                 try {
                     PackageInfo info = packageManager.getPackageInfo(PHONE_PACKAGE_NAME, 0);
@@ -326,6 +327,20 @@
                     Rlog.e(LOG_TAG, "Phone package not found: " + PHONE_PACKAGE_NAME);
                     applicationData = null;
                 }
+
+                try {
+                    PackageInfo info = packageManager.getPackageInfo(BLUETOOTH_PACKAGE_NAME, 0);
+                    int mode = appOps.checkOp(AppOpsManager.OP_WRITE_SMS, info.applicationInfo.uid,
+                            BLUETOOTH_PACKAGE_NAME);
+                    if (mode != AppOpsManager.MODE_ALLOWED) {
+                        Rlog.e(LOG_TAG, BLUETOOTH_PACKAGE_NAME + " lost OP_WRITE_SMS:  (fixing)");
+                        appOps.setMode(AppOpsManager.OP_WRITE_SMS, info.applicationInfo.uid,
+                                BLUETOOTH_PACKAGE_NAME, AppOpsManager.MODE_ALLOWED);
+                    }
+                } catch (NameNotFoundException e) {
+                    // No BT app on this device
+                    Rlog.e(LOG_TAG, "Bluetooth package not found: " + BLUETOOTH_PACKAGE_NAME);
+                }
             }
         }
         return applicationData;
@@ -386,6 +401,16 @@
                 // No phone app on this device (unexpected, even for non-phone devices)
                 Rlog.e(LOG_TAG, "Phone package not found: " + PHONE_PACKAGE_NAME);
             }
+
+            // BT needs to always have this permission to write to the sms database
+            try {
+                PackageInfo info = packageManager.getPackageInfo(BLUETOOTH_PACKAGE_NAME, 0);
+                appOps.setMode(AppOpsManager.OP_WRITE_SMS, info.applicationInfo.uid,
+                        BLUETOOTH_PACKAGE_NAME, AppOpsManager.MODE_ALLOWED);
+            } catch (NameNotFoundException e) {
+                // No BT app on this device
+                Rlog.e(LOG_TAG, "Bluetooth package not found: " + BLUETOOTH_PACKAGE_NAME);
+            }
         }
     }
 
@@ -463,4 +488,25 @@
         }
         return component;
     }
+
+    /**
+     * Returns whether need to write the SMS message to SMS database for this package.
+     */
+    public static boolean shouldWriteMessageForPackage(String packageName, Context context) {
+        if (packageName == null) return true;
+
+        String defaultSmsPackage = null;
+        ComponentName component = getDefaultSmsApplication(context, false);
+        if (component != null) {
+            defaultSmsPackage = component.getPackageName();
+        }
+
+        if ((defaultSmsPackage == null || !defaultSmsPackage.equals(packageName)) &&
+                !packageName.equals(BLUETOOTH_PACKAGE_NAME)) {
+            // To write the message for someone other than the default SMS and BT app
+            return true;
+        }
+
+        return false;
+    }
 }
diff --git a/src/java/com/android/internal/telephony/WakeLockStateMachine.java b/src/java/com/android/internal/telephony/WakeLockStateMachine.java
index ef1b68e..404b0cc 100644
--- a/src/java/com/android/internal/telephony/WakeLockStateMachine.java
+++ b/src/java/com/android/internal/telephony/WakeLockStateMachine.java
@@ -48,6 +48,12 @@
     /** Release wakelock after a short timeout when returning to idle state. */
     static final int EVENT_RELEASE_WAKE_LOCK = 3;
 
+    static final int EVENT_UPDATE_PHONE_OBJECT = 4;
+
+    protected PhoneBase mPhone;
+
+    protected Context mContext;
+
     /** Wakelock release delay when returning to idle state. */
     private static final int WAKE_LOCK_TIMEOUT = 3000;
 
@@ -55,9 +61,12 @@
     private final IdleState mIdleState = new IdleState();
     private final WaitingState mWaitingState = new WaitingState();
 
-    protected WakeLockStateMachine(String debugTag, Context context) {
+    protected WakeLockStateMachine(String debugTag, Context context, PhoneBase phone) {
         super(debugTag);
 
+        mContext = context;
+        mPhone = phone;
+
         PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
         mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, debugTag);
         mWakeLock.acquire();    // wake lock released after we enter idle state
@@ -68,6 +77,10 @@
         setInitialState(mIdleState);
     }
 
+    public void updatePhoneObject(PhoneBase phone) {
+        sendMessage(EVENT_UPDATE_PHONE_OBJECT, phone);
+    }
+
     /**
      * Tell the state machine to quit after processing all messages.
      */
@@ -98,13 +111,23 @@
     class DefaultState extends State {
         @Override
         public boolean processMessage(Message msg) {
-            String errorText = "processMessage: unhandled message type " + msg.what;
-            if (Build.IS_DEBUGGABLE) {
-                throw new RuntimeException(errorText);
-              } else {
-                loge(errorText);
-                return HANDLED;
-              }
+            switch (msg.what) {
+                case EVENT_UPDATE_PHONE_OBJECT: {
+                    mPhone = (PhoneBase) msg.obj;
+                    log("updatePhoneObject: phone=" + mPhone.getClass().getSimpleName());
+                    break;
+                }
+                default: {
+                    String errorText = "processMessage: unhandled message type " + msg.what;
+                    if (Build.IS_DEBUGGABLE) {
+                        throw new RuntimeException(errorText);
+                    } else {
+                        loge(errorText);
+                    }
+                    break;
+                }
+            }
+            return HANDLED;
         }
     }
 
diff --git a/src/java/com/android/internal/telephony/cdma/CdmaInboundSmsHandler.java b/src/java/com/android/internal/telephony/cdma/CdmaInboundSmsHandler.java
index 99e790f..9352639 100644
--- a/src/java/com/android/internal/telephony/cdma/CdmaInboundSmsHandler.java
+++ b/src/java/com/android/internal/telephony/cdma/CdmaInboundSmsHandler.java
@@ -45,9 +45,7 @@
  */
 public class CdmaInboundSmsHandler extends InboundSmsHandler {
 
-    private final PhoneBase mPhone;
     private final CdmaSMSDispatcher mSmsDispatcher;
-    private final CellBroadcastHandler mCellBroadcastHandler;
     private final CdmaServiceCategoryProgramHandler mServiceCategoryProgramHandler;
 
     private byte[] mLastDispatchedSmsFingerprint;
@@ -61,12 +59,11 @@
      */
     private CdmaInboundSmsHandler(Context context, SmsStorageMonitor storageMonitor,
             PhoneBase phone, CdmaSMSDispatcher smsDispatcher) {
-        super("CdmaInboundSmsHandler", context, storageMonitor);
+        super("CdmaInboundSmsHandler", context, storageMonitor, phone,
+                CellBroadcastHandler.makeCellBroadcastHandler(context));
         mSmsDispatcher = smsDispatcher;
-        mCellBroadcastHandler = CellBroadcastHandler.makeCellBroadcastHandler(context);
         mServiceCategoryProgramHandler = CdmaServiceCategoryProgramHandler.makeScpHandler(context,
                 phone.mCi);
-        mPhone = phone;
         phone.mCi.setOnNewCdmaSms(getHandler(), EVENT_NEW_SMS, null);
     }
 
@@ -214,6 +211,19 @@
     }
 
     /**
+     * Called when the phone changes the default method updates mPhone
+     * mStorageMonitor and mCellBroadcastHandler.updatePhoneObject.
+     * Override if different or other behavior is desired.
+     *
+     * @param phone
+     */
+    @Override
+    protected void onUpdatePhoneObject(PhoneBase phone) {
+        super.onUpdatePhoneObject(phone);
+        mCellBroadcastHandler.updatePhoneObject(phone);
+    }
+
+    /**
      * 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
diff --git a/src/java/com/android/internal/telephony/cdma/CdmaMmiCode.java b/src/java/com/android/internal/telephony/cdma/CdmaMmiCode.java
index 07c137b..3cb8bc7 100644
--- a/src/java/com/android/internal/telephony/cdma/CdmaMmiCode.java
+++ b/src/java/com/android/internal/telephony/cdma/CdmaMmiCode.java
@@ -315,7 +315,11 @@
                         }
                         // Get the No. of retries remaining to unlock PUK/PUK2
                         int attemptsRemaining = msg.arg1;
-                        if (attemptsRemaining >= 0) {
+                        if (attemptsRemaining <= 0) {
+                            Rlog.d(LOG_TAG, "onSetComplete: PUK locked,"
+                                    + " cancel as lock screen will handle this");
+                            mState = State.CANCELLED;
+                        } else if (attemptsRemaining > 0) {
                             Rlog.d(LOG_TAG, "onSetComplete: attemptsRemaining="+attemptsRemaining);
                             sb.append(mContext.getResources().getQuantityString(
                                     com.android.internal.R.plurals.pinpuk_attempts,
diff --git a/src/java/com/android/internal/telephony/cdma/CdmaServiceCategoryProgramHandler.java b/src/java/com/android/internal/telephony/cdma/CdmaServiceCategoryProgramHandler.java
index f11c681..bb9f9d5 100644
--- a/src/java/com/android/internal/telephony/cdma/CdmaServiceCategoryProgramHandler.java
+++ b/src/java/com/android/internal/telephony/cdma/CdmaServiceCategoryProgramHandler.java
@@ -45,14 +45,13 @@
  */
 public final class CdmaServiceCategoryProgramHandler extends WakeLockStateMachine {
 
-    private final Context mContext;
     final CommandsInterface mCi;
 
     /**
      * Create a new CDMA inbound SMS handler.
      */
     CdmaServiceCategoryProgramHandler(Context context, CommandsInterface commandsInterface) {
-        super("CdmaServiceCategoryProgramHandler", context);
+        super("CdmaServiceCategoryProgramHandler", context, null);
         mContext = context;
         mCi = commandsInterface;
     }
@@ -159,7 +158,7 @@
                 dos.writeInt(0); //servicePresent
                 dos.writeInt(0); //serviceCategory
                 CdmaSmsAddress destAddr = CdmaSmsAddress.parse(
-                        PhoneNumberUtils.cdmaCheckAndProcessPlusCode(sender));
+                        PhoneNumberUtils.cdmaCheckAndProcessPlusCodeForSms(sender));
                 dos.write(destAddr.digitMode);
                 dos.write(destAddr.numberMode);
                 dos.write(destAddr.ton); // number_type
diff --git a/src/java/com/android/internal/telephony/cdma/SmsMessage.java b/src/java/com/android/internal/telephony/cdma/SmsMessage.java
index e36ac45..6f202c1 100644
--- a/src/java/com/android/internal/telephony/cdma/SmsMessage.java
+++ b/src/java/com/android/internal/telephony/cdma/SmsMessage.java
@@ -851,7 +851,7 @@
          * Convert + code to 011 and dial out for international SMS
          */
         CdmaSmsAddress destAddr = CdmaSmsAddress.parse(
-                PhoneNumberUtils.cdmaCheckAndProcessPlusCode(destAddrStr));
+                PhoneNumberUtils.cdmaCheckAndProcessPlusCodeForSms(destAddrStr));
         if (destAddr == null) return null;
 
         BearerData bearerData = new BearerData();
diff --git a/src/java/com/android/internal/telephony/gsm/GsmCellBroadcastHandler.java b/src/java/com/android/internal/telephony/gsm/GsmCellBroadcastHandler.java
index de98458..6671a58 100644
--- a/src/java/com/android/internal/telephony/gsm/GsmCellBroadcastHandler.java
+++ b/src/java/com/android/internal/telephony/gsm/GsmCellBroadcastHandler.java
@@ -42,11 +42,8 @@
     private final HashMap<SmsCbConcatInfo, byte[][]> mSmsCbPageMap =
             new HashMap<SmsCbConcatInfo, byte[][]>(4);
 
-    private final PhoneBase mPhone;
-
     protected GsmCellBroadcastHandler(Context context, PhoneBase phone) {
-        super("GsmCellBroadcastHandler", context);
-        mPhone = phone;
+        super("GsmCellBroadcastHandler", context, phone);
         phone.mCi.setOnNewGsmBroadcastSms(getHandler(), EVENT_NEW_SMS_MESSAGE, null);
     }
 
diff --git a/src/java/com/android/internal/telephony/gsm/GsmInboundSmsHandler.java b/src/java/com/android/internal/telephony/gsm/GsmInboundSmsHandler.java
index 2c63bf0..7e48cc2 100644
--- a/src/java/com/android/internal/telephony/gsm/GsmInboundSmsHandler.java
+++ b/src/java/com/android/internal/telephony/gsm/GsmInboundSmsHandler.java
@@ -38,20 +38,14 @@
     /** Handler for SMS-PP data download messages to UICC. */
     private final UsimDataDownloadHandler mDataDownloadHandler;
 
-    private final GsmCellBroadcastHandler mCellBroadcastDispatcher;
-
-    private final PhoneBase mPhone;
-
     /**
      * Create a new GSM inbound SMS handler.
      */
     private GsmInboundSmsHandler(Context context, SmsStorageMonitor storageMonitor,
             PhoneBase phone) {
-        super("GsmInboundSmsHandler", context, storageMonitor);
-        mPhone = phone;
+        super("GsmInboundSmsHandler", context, storageMonitor, phone,
+                GsmCellBroadcastHandler.makeGsmCellBroadcastHandler(context, phone));
         phone.mCi.setOnNewGsmSms(getHandler(), EVENT_NEW_SMS, null);
-        mCellBroadcastDispatcher = GsmCellBroadcastHandler.makeGsmCellBroadcastHandler(context,
-                phone);
         mDataDownloadHandler = new UsimDataDownloadHandler(phone.mCi);
     }
 
@@ -61,7 +55,7 @@
     @Override
     protected void onQuitting() {
         mPhone.mCi.unSetOnNewGsmSms(getHandler());
-        mCellBroadcastDispatcher.dispose();
+        mCellBroadcastHandler.dispose();
 
         if (DBG) log("unregistered for 3GPP SMS");
         super.onQuitting();     // release wakelock
@@ -147,6 +141,22 @@
     }
 
     /**
+     * Called when the phone changes the default method updates mPhone
+     * mStorageMonitor and mCellBroadcastHandler.updatePhoneObject.
+     * Override if different or other behavior is desired.
+     *
+     * @param phone
+     */
+    @Override
+    protected void onUpdatePhoneObject(PhoneBase phone) {
+        super.onUpdatePhoneObject(phone);
+        log("onUpdatePhoneObject: dispose of old CellBroadcastHandler and make a new one");
+        mCellBroadcastHandler.dispose();
+        mCellBroadcastHandler = GsmCellBroadcastHandler
+                .makeGsmCellBroadcastHandler(mContext, phone);
+    }
+
+    /**
      * Convert Android result code to 3GPP SMS failure cause.
      * @param rc the Android SMS intent result value
      * @return 0 for success, or a 3GPP SMS failure cause value
diff --git a/src/java/com/android/internal/telephony/gsm/GsmMmiCode.java b/src/java/com/android/internal/telephony/gsm/GsmMmiCode.java
index 1dd958f..c2bafbe 100644
--- a/src/java/com/android/internal/telephony/gsm/GsmMmiCode.java
+++ b/src/java/com/android/internal/telephony/gsm/GsmMmiCode.java
@@ -1030,7 +1030,12 @@
                         }
                         // Get the No. of retries remaining to unlock PUK/PUK2
                         int attemptsRemaining = msg.arg1;
-                        if (attemptsRemaining >= 0) {
+                        if (attemptsRemaining <= 0) {
+                            Rlog.d(LOG_TAG, "onSetComplete: PUK locked,"
+                                    + " cancel as lock screen will handle this");
+                            mState = State.CANCELLED;
+                        } else if (attemptsRemaining > 0) {
+                            Rlog.d(LOG_TAG, "onSetComplete: attemptsRemaining="+attemptsRemaining);
                             sb.append(mContext.getResources().getQuantityString(
                                     com.android.internal.R.plurals.pinpuk_attempts,
                                     attemptsRemaining, attemptsRemaining));